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
|
#!/hint/bash
# Copyright © Tavian Barnes <tavianator@tavianator.com>
# SPDX-License-Identifier: 0BSD
## Colored output
# Common escape sequences
BLD=$'\e[01m'
RED=$'\e[01;31m'
GRN=$'\e[01;32m'
YLW=$'\e[01;33m'
BLU=$'\e[01;34m'
MAG=$'\e[01;35m'
CYN=$'\e[01;36m'
RST=$'\e[0m'
# Check if we should color output to the given fd
color_fd() {
[ -z "${NO_COLOR:-}" ] && [ -t "$1" ]
}
# Cache the color status for std{out,err}
color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0
color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0
# Save this in case the tests unset PATH
SED=$(command -v sed)
# Filter out escape sequences if necessary
color() {
if color_fd 1; then
"$@"
else
"$@" | "$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="${TMPDIR:-/tmp}/bfs${TTY//\//-}.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() {
printf 'HIDE:%d:\0' $$ >&$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" >/dev/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"
;;
HIDE)
bar="${pid2bar[$pid]}"
# Delete this status bar
unset 'pid2bar[$pid]'
# Shift all higher status bars down
for i in "${!pid2bar[@]}"; do
ibar="${pid2bar[$i]}"
if ((ibar > bar)); then
pid2bar["$i"]=$((ibar - 1))
fi
done
((BAR_HEIGHT--))
resize_bar
;;
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>/dev/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"
}
|