summaryrefslogtreecommitdiffstats
path: root/tests/color.sh
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2024-05-22 16:12:37 -0400
committerTavian Barnes <tavianator@tavianator.com>2024-05-22 16:12:37 -0400
commit59b1c525c4c7c254dce702fca0fa472616a976d2 (patch)
tree6df8b88d03b541d2eea43184ec98fd4c625b70fa /tests/color.sh
parent98191107e1dd2d186604bdb58990e020fc04c24e (diff)
downloadbfs-59b1c525c4c7c254dce702fca0fa472616a976d2.tar.xz
tests: Print progress outside the scroll region
Diffstat (limited to 'tests/color.sh')
-rw-r--r--tests/color.sh101
1 files changed, 101 insertions, 0 deletions
diff --git a/tests/color.sh b/tests/color.sh
index 805d2b8..ca85d28 100644
--- a/tests/color.sh
+++ b/tests/color.sh
@@ -35,3 +35,104 @@ color() {
"$@" | "$SED" $'s/\e\\[[^m]*m//g'
fi
}
+
+## Status bar
+
+# Show the terminal status bar
+show_bar() {
+ if [ -z "$TTY" ]; then
+ return 1
+ fi
+
+ # Name the pipe deterministically based on the ttyname, so that concurrent
+ # tests.sh runs on the same terminal (e.g. make -jN check) cooperate
+ local pipe
+ pipe=$(printf '%s' "$TTY" | tr '/' '-')
+ pipe="${TMPDIR:-/tmp}/bfs$pipe.bar"
+
+ if mkfifo "$pipe" 2>/dev/null; then
+ # We won the race, create the background process to manage the bar
+ bar_proc "$pipe" &
+ exec {BAR}>"$pipe"
+ elif [ -p "$pipe" ]; then
+ # We lost the race, connect to the existing process.
+ # There is a small TOCTTOU race here but I don't see how to avoid it.
+ exec {BAR}>"$pipe"
+ else
+ return 1
+ fi
+}
+
+# Print to the terminal status bar
+print_bar() {
+ printf 'PRINT:%d:%s\0' $$ "$1" >&$BAR
+}
+
+# Hide the terminal status bar
+hide_bar() {
+ exec {BAR}>&-
+ unset BAR
+}
+
+# The background process that muxes multiple status bars for one TTY
+bar_proc() {
+ # Read from the pipe, write to the TTY
+ exec <"$1" >"$TTY"
+
+ # Delete the pipe when done
+ defer rm "$1"
+ # Reset the scroll region when done
+ defer printf '\e7\e[r\e8\e[J'
+
+ # Workaround for bash 4: checkwinsize is off by default. We can turn it
+ # on, but we also have to explicitly trigger a foreground job to finish
+ # so that it will update the window size before we use $LINES
+ shopt -s checkwinsize
+ (:)
+
+ BAR_HEIGHT=0
+ resize_bar
+ # Adjust the bar when the TTY size changes
+ trap resize_bar WINCH
+
+ # Map from PID to status bar
+ local -A pid2bar
+
+ # Read commands of the form "OP:PID:STRING\0"
+ while IFS=':' read -r -d '' op pid str; do
+ # Map the pid to a bar, creating a new one if necessary
+ if [ -z "${pid2bar[$pid]:-}" ]; then
+ pid2bar["$pid"]=$((BAR_HEIGHT++))
+ resize_bar
+ fi
+ bar="${pid2bar[$pid]}"
+
+ case "$op" in
+ PRINT)
+ printf '\e7\e[%d;0f\e[K%s\e8' $((TTY_HEIGHT - bar)) "$str"
+ ;;
+ esac
+ done
+}
+
+# Resize the status bar
+resize_bar() {
+ # Bash gets $LINES from stderr, so if it's redirected use tput instead
+ TTY_HEIGHT="${LINES:-$(tput lines 2>"$TTY")}"
+
+ if ((BAR_HEIGHT == 0)); then
+ return
+ fi
+
+ # Hide the bars temporarily
+ local seq='\e7\e[r\e8\e[J'
+ # Print \eD (IND) N times to ensure N blank lines at the bottom
+ for ((i = 0; i < BAR_HEIGHT; ++i)); do
+ seq="${seq}\\eD"
+ done
+ # Go back up N lines
+ seq="${seq}\\e[${BAR_HEIGHT}A"
+ # Create the new scroll region
+ seq="${seq}\\e7\\e[;$((TTY_HEIGHT - BAR_HEIGHT))r\\e8\\e[J"
+ printf "$seq"
+}