blob: 853dccf400297558e4b92733042797aad17a5c3b (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
#!/hint/bash
#
# SPDX-License-Identifier: GPL-3.0-or-later
[[ -z ${DEVTOOLS_INCLUDE_UTIL_TERM_SH:-} ]] || return 0
DEVTOOLS_INCLUDE_UTIL_TERM_SH=1
set -eo pipefail
readonly PKGCTL_TERM_SPINNER_DOTS=Dots
export PKGCTL_TERM_SPINNER_DOTS
readonly PKGCTL_TERM_SPINNER_DOTS12=Dots12
export PKGCTL_TERM_SPINNER_DOTS12
readonly PKGCTL_TERM_SPINNER_LINE=Line
export PKGCTL_TERM_SPINNER_LINE
readonly PKGCTL_TERM_SPINNER_SIMPLE_DOTS_SCROLLING=SimpleDotsScrolling
export PKGCTL_TERM_SPINNER_SIMPLE_DOTS_SCROLLING
readonly PKGCTL_TERM_SPINNER_TRIANGLE=Triangle
export PKGCTL_TERM_SPINNER_TRIANGLE
readonly PKGCTL_TERM_SPINNER_RANDOM=Random
export PKGCTL_TERM_SPINNER_RANDOM
readonly PKGCTL_TERM_SPINNER_TYPES=(
"${PKGCTL_TERM_SPINNER_DOTS}"
"${PKGCTL_TERM_SPINNER_DOTS12}"
"${PKGCTL_TERM_SPINNER_LINE}"
"${PKGCTL_TERM_SPINNER_SIMPLE_DOTS_SCROLLING}"
"${PKGCTL_TERM_SPINNER_TRIANGLE}"
)
export PKGCTL_TERM_SPINNER_TYPES
term_cursor_hide() {
tput civis >&2
}
term_cursor_show() {
tput cnorm >&2
}
term_cursor_up() {
tput cuu1
}
term_carriage_return() {
tput cr
}
term_erase_line() {
tput el
}
term_erase_lines() {
local lines=$1
local cursor_up erase_line
cursor_up=$(term_cursor_up)
erase_line="$(term_carriage_return)$(term_erase_line)"
local prefix=''
for _ in $(seq 1 "${lines}"); do
printf '%s' "${prefix}${erase_line}"
prefix="${cursor_up}"
done
}
_pkgctl_spinner_type=${PKGCTL_TERM_SPINNER_RANDOM}
term_spinner_set_type() {
_pkgctl_spinner_type=$1
}
# takes a status directory that can be used to dynamically update the spinner
# by writing to the `status` file inside that directory atomically.
# replace the placeholder %spinner% with the currently configured spinner type
term_spinner_start() {
local status_dir=$1
local parent_pid=$$
(
local spinner_type=${_pkgctl_spinner_type}
local spinner_offset=0
local frame_buffer=''
local spinner status_message line
local status_file="${status_dir}/status"
local next_file="${status_dir}/next"
local drawn_file="${status_dir}/drawn"
# assign random spinner type
if [[ ${spinner_type} == "${PKGCTL_TERM_SPINNER_RANDOM}" ]]; then
spinner_type=${PKGCTL_TERM_SPINNER_TYPES[$((RANDOM % ${#PKGCTL_TERM_SPINNER_TYPES[@]}))]}
fi
# select spinner based on the named type
case "${spinner_type}" in
"${PKGCTL_TERM_SPINNER_DOTS}")
spinner=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
update_interval=0.08
;;
"${PKGCTL_TERM_SPINNER_DOTS12}")
spinner=("⢀⠀" "⡀⠀" "⠄⠀" "⢂⠀" "⡂⠀" "⠅⠀" "⢃⠀" "⡃⠀" "⠍⠀" "⢋⠀" "⡋⠀" "⠍⠁" "⢋⠁" "⡋⠁" "⠍⠉" "⠋⠉" "⠋⠉" "⠉⠙" "⠉⠙" "⠉⠩" "⠈⢙" "⠈⡙" "⢈⠩" "⡀⢙" "⠄⡙" "⢂⠩" "⡂⢘" "⠅⡘" "⢃⠨" "⡃⢐" "⠍⡐" "⢋⠠" "⡋⢀" "⠍⡁" "⢋⠁" "⡋⠁" "⠍⠉" "⠋⠉" "⠋⠉" "⠉⠙" "⠉⠙" "⠉⠩" "⠈⢙" "⠈⡙" "⠈⠩" "⠀⢙" "⠀⡙" "⠀⠩" "⠀⢘" "⠀⡘" "⠀⠨" "⠀⢐" "⠀⡐" "⠀⠠" "⠀⢀" "⠀⡀")
update_interval=0.08
;;
"${PKGCTL_TERM_SPINNER_LINE}")
spinner=("⎯" "\\" "|" "/")
update_interval=0.13
;;
"${PKGCTL_TERM_SPINNER_SIMPLE_DOTS_SCROLLING}")
spinner=(". " ".. " "..." " .." " ." " ")
update_interval=0.2
;;
"${PKGCTL_TERM_SPINNER_TRIANGLE}")
spinner=("◢" "◣" "◤" "◥")
update_interval=0.05
;;
esac
# hide the cursor while spinning
term_cursor_hide
# run the spinner as long as the parent process didn't terminate
while ps -p "${parent_pid}" &>/dev/null; do
# cache the new status template if it exists
if mv "${status_file}" "${next_file}" &>/dev/null; then
status_message="$(cat "$next_file")"
elif [[ -z "${status_message}" ]]; then
# wait until we either have a new or cached status
sleep 0.05
fi
# fill the frame buffer with the current status
local prefix=''
while IFS= read -r line; do
# replace spinner placeholder
line=${line//%spinner%/${spinner[spinner_offset%${#spinner[@]}]}}
# append the current line to the frame buffer
frame_buffer+="${prefix}${line}"
prefix=$'\n'
done <<< "${status_message}"
# print current frame buffer
echo -n "${frame_buffer}" >&2
mv "${next_file}" "${drawn_file}" &>/dev/null ||:
# setup next frame buffer to clear current content
frame_buffer=$(term_erase_lines "$(awk 'END {print NR}' <<< "${status_message}")")
# advance the spinner animation offset
(( ++spinner_offset ))
# sleep for the spinner update interval
sleep "${update_interval}"
done
)&
_pkgctl_spinner_pid=$!
disown
}
term_spinner_stop() {
local status_dir=$1
local frame_buffer status_file
# kill the spinner process
if ! kill "${_pkgctl_spinner_pid}" > /dev/null 2>&1; then
return 1
fi
unset _pkgctl_spinner_pid
# acquire last drawn status
status_file="${status_dir}/drawn"
if [[ ! -f ${status_file} ]]; then
return 0
fi
# clear terminal based on last status line
frame_buffer=$(term_erase_lines "$(awk 'END {print NR}' < "${status_file}")")
echo -n "${frame_buffer}" >&2
# show the cursor after stopping the spinner
term_cursor_show
}
|