Side status line regress tests.

This commit is contained in:
Nicholas Marriott
2026-10-16 09:02:03 +01:00
parent 4ae6a7ec55
commit 647e15b038
24 changed files with 1779 additions and 14 deletions

View File

@@ -0,0 +1,143 @@
#!/bin/sh
# With side-status left, a client narrower than the window must clamp pane
# output to its visible part without killing the server or drawing over the
# side status line. Each inner client runs in an outer pane of its size.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
DIR=$(mktemp -d) || exit 1
TRIGGER=$DIR/trigger
CAPTURE=$DIR/capture
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
rm -f "$TRIGGER" "$CAPTURE"
rmdir "$DIR"
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
exit 1
}
# Start the inner server with one pane running $1, then attach a big and a
# small client from outer panes of sizes $2 and $3 (WxH).
start()
{
$TMUX kill-server >/dev/null 2>&1
$TMUX2 kill-server >/dev/null 2>&1
rm -f "$TRIGGER"
$TMUX new-session -d -s inner -x 200 -y 50 "$1" || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g automatic-rename off || exit 1
$TMUX set -g window-size largest || exit 1
$TMUX set -g side-status left || exit 1
}
attach()
{
$TMUX2 new-session -d -s big -x "${1%x*}" -y "${1#*x}" 'sleep 100' ||
exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 new-session -d -s small -x "${2%x*}" -y "${2#*x}" 'sleep 100' ||
exit 1
$TMUX2 respawn-pane -k -t big:0.0 "$TMUX attach -t inner" || exit 1
sleep 0.5
$TMUX2 respawn-pane -k -t small:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
$TMUX has-session 2>/dev/null || fail "server exited on attach"
SMALL=$($TMUX list-clients -F '#{client_width} #{client_name}' |
sort -n | head -n 1 | cut -d' ' -f2)
}
alive()
{
$TMUX has-session 2>/dev/null || fail "server exited: $1"
}
# A clear below the cursor in a pane wider than the small client's view.
start "sh -c 'while [ ! -e $TRIGGER ]; do sleep 0.1; done
printf \"\\033[J\"; exec sleep 100'"
$TMUX set -g side-status-width 31 || exit 1
attach 167x49 40x12
: >"$TRIGGER"
sleep 1
alive "clear in a pane wider than the client"
# The same clear with the small client panned right, so the pane is cut off
# on both sides. The side status line keeps the window on its first row.
start "sh -c 'while [ ! -e $TRIGGER ]; do sleep 0.1; done
printf \"\\033[J\"; exec sleep 100'"
$TMUX rename-window -t inner:0 work || exit 1
$TMUX set -g side-status-width 31 || exit 1
attach 167x49 40x12
$TMUX refresh-client -t "$SMALL" -R 5 || exit 1
sleep 0.5
: >"$TRIGGER"
sleep 1
alive "clear in a pane cut off on both sides"
$TMUX2 capture-pane -p -t small:0.0 >"$CAPTURE" || exit 1
case "$(head -n 1 "$CAPTURE")" in
0:work*) ;;
*) fail "side status first row cleared" ;;
esac
# Output in the right pane of a split, cut off on the right.
start 'sleep 100'
$TMUX split-window -h -d "sh -c 'while [ ! -e $TRIGGER ]; do sleep 0.1; done
printf ab; exec sleep 100'" || exit 1
attach 120x30 80x30
: >"$TRIGGER"
sleep 1
alive "output in a pane cut off on the right"
# Panned right by less than the side status width, the left pane is cut off
# on the left. Its output is drawn after the side status line, not over it.
start "sh -c 'while [ ! -e $TRIGGER ]; do sleep 0.1; done
printf \"%053d\" 0 | tr 0 X; exec sleep 100'"
$TMUX split-window -h -d 'sleep 100' || exit 1
attach 120x30 80x30
$TMUX refresh-client -t "$SMALL" -R 10 || exit 1
sleep 0.5
: >"$TRIGGER"
sleep 1
alive "output in a pane cut off on the left"
$TMUX2 capture-pane -p -t small:0.0 >"$CAPTURE" || exit 1
row=$(head -n 1 "$CAPTURE")
case "$row" in
?????????????│XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX│*) ;;
*) fail "row is '$row', expected the side status then 43 X" ;;
esac
# Panned into the middle of the right pane, which is cut off on both sides.
# The visible columns of the pane are drawn, counted from the pane's left.
start 'sleep 100'
$TMUX split-window -h -d -l 100 "sh -c 'while [ ! -e $TRIGGER ]; do sleep 0.1; done
printf \"%010d\" 0 | sed s/0/0123456789/g; exec sleep 100'" || exit 1
attach 214x30 40x30
$TMUX refresh-client -t "$SMALL" -R 120 || exit 1
sleep 0.5
: >"$TRIGGER"
sleep 1
alive "output in a pane cut off on both sides"
$TMUX2 capture-pane -p -t small:0.0 >"$CAPTURE" || exit 1
row=$(head -n 1 "$CAPTURE")
case "$row" in
?????????????│01234567890123456789012345) ;;
*) fail "row is '$row', expected the side status then pane columns 20-45" ;;
esac
exit 0

View File

@@ -0,0 +1,77 @@
#!/bin/sh
# Clicking a window on the side status line, including the current window,
# dispatches the Status binding with that window.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
# Click row $1 of the side status line and check the binding got $2.
click()
{
$TMUX set -g @click none || exit 1
seq=$(printf '\033[<0;3;%sM\033[<0;3;%sm' "$1" "$1")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail "send mouse failed"
sleep 1
got=$($TMUX show -gv @click)
[ "$got" = "$2" ] || fail "row $1: got '$got', expected '$2'"
}
$TMUX new-session -d -s inner -n zero -x 60 -y 20 'sleep 100' || exit 1
$TMUX new-window -d -n one 'sleep 100' || exit 1
$TMUX new-window -d -n two 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status left || exit 1
$TMUX bind -n MouseDown1Status \
set -gF @click '#{mouse_status_range} #{window_name}' || exit 1
$TMUX bind -n MouseDown1StatusDefault set -g @click default || exit 1
$TMUX2 new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
# The current window is first, then in the middle of the list.
click 1 'window zero'
click 2 'window one'
click 4 default
$TMUX select-window -t inner:1 || exit 1
sleep 0.5
click 1 'window zero'
click 2 'window one'
click 3 'window two'
# A range closed in the same style as the line break ends on its own row.
$TMUX set -g side-status-format '#[range=user|foo]one#[norange,nl]two' ||
exit 1
sleep 0.5
click 1 'foo one'
click 2 default
# A range still open at a line break carries on to the next row.
$TMUX set -g side-status-format '#[range=user|foo]one#[nl]two#[norange]' ||
exit 1
sleep 0.5
click 1 'foo one'
click 2 'foo one'
exit 0

View File

@@ -0,0 +1,90 @@
#!/bin/sh
# A Ctrl-drag starting on the side status line must not create a floating
# pane, and must still dispatch Status bindings. Ctrl-clicking a window on the
# side status line swaps windows, so panes are compared across the session.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
mouse()
{
seq=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail "send mouse failed"
sleep 0.2
}
check_drag()
{
sx=$1
sy=$2
ex=$3
ey=$4
# First move along the side status line, then into the pane.
mouse 16 "$sx" "$sy" M
mouse 48 "$sx" $((sy + 1)) M
mouse 48 "$ex" "$ey" M
mouse 48 $((ex + 2)) "$ey" M
mouse 16 $((ex + 2)) "$ey" m
panes=$($TMUX list-panes -s -F '#{pane_id}') || fail "server exited"
panes=$(echo "$panes" | sort)
[ "$panes" = "$BASE" ] ||
fail "side status drag from $sx,$sy created a pane: $panes"
}
$TMUX new-session -d -s inner -x 60 -y 20 'sleep 100' || exit 1
$TMUX new-window -d 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g default-command 'sleep 100' || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status left || exit 1
BASE=$($TMUX list-panes -s -F '#{pane_id}') || exit 1
BASE=$(echo "$BASE" | sort)
$TMUX2 new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
# Blank rows and the window list on the left.
check_drag 3 10 30 12
check_drag 2 1 30 12
check_drag 2 2 30 12
$TMUX set -g side-status right || exit 1
sleep 0.5
check_drag 55 10 20 12
# The horizontal status line takes the bottom row below the side status.
$TMUX set -g side-status left || exit 1
$TMUX set -g status on || exit 1
sleep 0.5
check_drag 3 10 30 12
# A drag starting on a window range dispatches the Status binding.
$TMUX bind -n C-MouseDrag1Status set -g @status-drag status || exit 1
$TMUX set -g @status-drag '' || exit 1
check_drag 2 2 30 12
got=$($TMUX show -gv @status-drag)
[ "$got" = status ] || fail "got drag '$got', expected 'status'"
exit 0

View File

@@ -0,0 +1,139 @@
#!/bin/sh
# Split formats into lines, preserve headers and footers while scrolling,
# and clip wide or combining characters at the side status boundary.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
DIR=$(mktemp -d) || exit 1
HEIGHT=8
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
rm -f "$DIR/actual" "$DIR/expected"
rmdir "$DIR"
}
trap cleanup 0 1 15
check()
{
format=$1
shift
$TMUX set -g side-status-format "$format" || exit 1
: >"$DIR/expected"
n=0
for line in "$@"; do
printf '%s\n' "$line" >>"$DIR/expected"
n=$((n + 1))
done
while [ "$n" -lt "$HEIGHT" ]; do
printf ' │\n' >>"$DIR/expected"
n=$((n + 1))
done
i=0
while [ "$i" -lt 50 ]; do
$TMUX2 capture-pane -p -t outer:0.0 >"$DIR/actual" || exit 1
cmp -s "$DIR/actual" "$DIR/expected" && return
sleep 0.1
i=$((i + 1))
done
echo "format '$format' at height $HEIGHT differs:" >&2
diff -u "$DIR/expected" "$DIR/actual" >&2
exit 1
}
resize()
{
HEIGHT=$1
$TMUX2 resize-window -t outer:0 -y "$HEIGHT" || exit 1
}
$TMUX new-session -d -s inner -x 40 -y 8 'sleep 100' || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status left || exit 1
$TMUX set -g side-status-width 8 || exit 1
$TMUX2 new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
# Empty, consecutive, leading and trailing breaks, with both spellings.
check ''
check 'one#[nl]#[nl]three#[nl]' 'one │' ' │' 'three │'
check '
three
' ' │' ' │' 'three │'
check 'one
two' 'one │' 'two │'
# Escaped hashes must not turn literal text into a line break.
check '##[nl]#[nl]two' '#[nl] │' 'two │'
check '####[nl]#[nl]two' '##[nl] │' 'two │'
check '###[nl]two' '# │' 'two │'
check 'one##' 'one# │'
# Invalid or unfinished styles do not consume the following valid lines.
check 'one#[invalid-style]two#[nl]three' 'onetwo │' 'three │'
check 'one#[unfinished' 'one │'
check '#[ignore]A#[nl]B
C' 'A#[nl]B│' 'C │'
# Alignment carries over a break, as do character widths.
check '#[align=centre]X#[nl]Y' ' X │' ' Y │'
check '#[align=absolute-centre]X#[nl]Y' ' X │' ' Y │'
check 'abcde界#[nl]abcdef界#[nl]abcdeéf' \
'abcde界│' 'abcdef │' 'abcdeéf│'
format='H1#[nl]H2#[nl]#[list=on]L0#[nl]L1#[nl]'
format=$format'#[list=focus]L2#[nl]#[list=on]L3#[nl]L4#[nl]'
format=$format'#[nolist]F1#[nl]F2'
check "$format" 'H1 │' 'H2 │' 'L0 │' 'L1 │' \
'L2 │' 'L3 │' 'F1 │' 'F2 │'
resize 6
check "$format" 'H1 │' 'H2 │' 'L1 │' 'L2 │' \
'F1 │' 'F2 │'
resize 4
check "$format" 'H1 │' 'H2 │' 'F1 │' 'F2 │'
resize 3
check "$format" 'H1 │' 'H2 │' 'F1 │'
resize 1
check "$format" 'H1 │'
# Markers consume only list lines and cannot replace the focused line.
format='H#[nl]#[list=on]#[list=left-marker]^#[nl]'
format=$format'#[list=right-marker]v#[nl]#[list=on]L0#[nl]L1#[nl]'
format=$format'#[list=focus]L2#[nl]#[list=on]L3#[nl]L4#[nl]'
format=$format'#[nolist]F'
resize 5
check "$format" 'H │' '^ │' 'L2 │' 'v │' 'F │'
resize 3
check "$format" 'H │' 'L2 │' 'F │'
resize 2
check "$format" 'H │' 'F │'
# A large logical list does not require a tall terminal.
format=$(awk 'BEGIN {
printf "H#[nl]#[list=on]"
for (i = 0; i < 1000; i++) {
if (i == 800)
printf "#[list=focus]"
if (i == 801)
printf "#[list=on]"
printf "L%d#[nl]", i
}
printf "#[nolist]F"
}')
resize 3
check "$format" 'H │' 'L800 │' 'F │'
exit 0

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# Images are drawn and erased in the pane's columns with either side status
# position and with the horizontal status off, above or below the window.
PATH=/bin:/usr/bin
TERM=screen
export TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
python3 -B - "$TEST_TMUX" <<'PY'
import sys
from image_test import image_test, sixel
from image_terminal import Terminal
side = 14
w, h = 16, 32
wire = sixel(w, h, [(100, 0, 0)], [0] * (w * h))
def placed(cx, cy):
return {(cx * 8 + x, cy * 16 + y): (255, 0, 0)
for y in range(h) for x in range(w)}
def side_cells(terminal, xoff, yoff, height):
return {(x, y): terminal.cells.get((x, y))
for y in range(yoff, yoff + height)
for x in range(xoff, xoff + side)}
def check(position, status):
with image_test(sys.argv[1]) as test:
test.run("set-option", "-g", "side-status", position)
test.run("set-option", "-g", "side-status-width", str(side))
if status != "off":
test.run("set-option", "-g", "status", "2")
test.run("set-option", "-g", "status-position", status)
xoff = side if position == "left" else 0
yoff = 2 if status == "top" else 0
height = 16 if status == "off" else 14
side_x = 0 if position == "left" else 40 - side
separator = side - 1 if position == "left" else side_x
client = test.attach()
test.emit(b'\033[3;3H' + wire)
# A full redraw.
test.redraw()
terminal = Terminal()
terminal.feed(client.data)
terminal.check(placed(xoff + 2, yoff + 2))
cells = side_cells(terminal, side_x, yoff, height)
assert all(cells[separator, y] not in (None, ' ')
for y in range(yoff, yoff + height)), \
(position, status, 'side status line not drawn')
# Inserting a line above the image redraws the damaged area.
test.emit(b'\033[3;1H\033[L')
terminal.feed(client.data)
terminal.check(placed(xoff + 2, yoff + 3))
assert side_cells(terminal, side_x, yoff, height) == cells, \
(position, status, 'side status erased by damage')
# Leaving copy mode redraws only the pane.
client.clear()
test.run("copy-mode")
test.wait(lambda: test.run("display-message", "-p",
"#{pane_in_mode}") == "1")
test.run("send-keys", "-X", "cancel")
test.wait(lambda: test.run("display-message", "-p",
"#{pane_in_mode}") == "0")
client.read(0.2)
terminal.feed(client.data)
terminal.check(placed(xoff + 2, yoff + 3))
assert side_cells(terminal, side_x, yoff, height) == cells, \
(position, status, 'side status erased by pane redraw')
for position in ("left", "right"):
for status in ("off", "top", "bottom"):
check(position, status)
PY

View File

@@ -0,0 +1,72 @@
#!/bin/sh
# On a terminal with left and right margins, scrolling a pane beside the side
# status line sets the margins to the pane's columns. The inner client is
# told the terminal has margins and its output is recorded raw from the outer
# pane, where ESC [ left ; right s sets the margins (1-based).
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
DIR=$(mktemp -d) || exit 1
TRIGGER=$DIR/trigger
OUT=$DIR/out
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
rm -f "$TRIGGER" "$OUT"
rmdir "$DIR"
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
# Scroll a full width pane with the side status line on side $1 and check the
# margins sent are $2.
check()
{
$TMUX kill-server >/dev/null 2>&1
$TMUX2 kill-server >/dev/null 2>&1
rm -f "$TRIGGER" "$OUT"
$TMUX new-session -d -s inner -x 60 -y 10 "sh -c '
while [ ! -e $TRIGGER ]; do sleep 0.1; done
i=0
while [ \$i -lt 15 ]; do i=\$((i + 1)); echo line\$i; done
exec sleep 100'" || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status "$1" || exit 1
$TMUX set -as terminal-features ',*:margins' || exit 1
$TMUX2 new-session -d -s outer -x 60 -y 10 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
$TMUX2 pipe-pane -t outer:0.0 "cat >$OUT" || exit 1
sleep 0.5
: >"$TRIGGER"
sleep 1
got=$(tr '\033' E <"$OUT" | grep -o 'E\[[0-9]*;[0-9]*s' | sort -u |
tr -d 'E[s' | tr '\n' ' ')
[ "$got" = "$2 " ] || fail "side-status $1: margins '$got', expected '$2'"
}
# Pane in columns 15 to 60 after a left side status line of width 14.
check left '15;60'
# Pane in columns 1 to 46 before a right side status line.
check right '1;46'
exit 0

View File

@@ -0,0 +1,109 @@
#!/bin/sh
# Status coordinates and session/window targets on either side, with the
# horizontal status off, above or below the window.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
click()
{
expected="$3|$(($1 - 1))|$(($2 - 1))"
$TMUX set -g @mouse none || exit 1
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' "$1" "$2" "$1" "$2")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || exit 1
i=0
while [ "$i" -lt 50 ]; do
got=$($TMUX show -gv @mouse)
[ "$got" = "$expected" ] && break
sleep 0.1
i=$((i + 1))
done
[ "$got" = "$expected" ] || {
echo "$side/$status at $1,$2: '$got', expected '$expected'" >&2
exit 1
}
sleep 0.5
}
$TMUX new-session -d -s inner -n zero -x 80 -y 20 'sleep 100' || exit 1
$TMUX new-window -d -n one 'sleep 100' || exit 1
$TMUX new-session -d -s beta -n other -x 100 -y 30 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g automatic-rename off || exit 1
$TMUX set -g side-status-width 14 || exit 1
[ "$($TMUX display -p '#{mouse_absolute_x}|#{mouse_absolute_y}')" = '|' ] ||
exit 1
$TMUX set -g 'status-format[0]' \
'#[range=window|1]window#[norange]' || exit 1
$TMUX set -g 'status-format[1]' 'second' || exit 1
format='#{mouse_status_range}|#{mouse_status_line}|#{mouse_x}|#{mouse_y}'
format=$format'|#{session_name}|#{window_name}'
format=$format'|#{mouse_absolute_x}|#{mouse_absolute_y}'
for key in MouseDown1Status MouseDown1StatusDefault MouseDown1StatusBorder \
MouseDown1Pane; do
$TMUX bind -n "$key" set -gF @mouse "$format" || exit 1
done
$TMUX2 new-session -d -s outer -x 80 -y 20 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
for side in left right; do
$TMUX set -g side-status "$side" || exit 1
if [ "$side" = left ]; then
column=3
separator=14
pane_column=17
separator_x=13
else
column=69
separator=67
pane_column=3
separator_x=0
fi
for status in off top bottom; do
offset=0
if [ "$status" = off ]; then
$TMUX set -g status off || exit 1
else
$TMUX set -g status 2 || exit 1
$TMUX set -g status-position "$status" || exit 1
[ "$status" = top ] && offset=2
fi
sleep 0.5
# The current session's windows are between the session headers.
click "$column" "$((offset + 1))" 'session|0|2|0|inner|zero'
click "$((column + 3))" "$((offset + 3))" \
'window|2|5|2|inner|one'
click "$column" "$((offset + 4))" 'session|3|2|3|beta|other'
click "$separator" "$((offset + 6))" \
"|5|$separator_x|5|inner|zero"
click "$pane_column" "$((offset + 7))" '||2|6|inner|zero'
# Horizontal status coordinates are relative to its first line,
# even if the clicked window's pane extends behind the status.
if [ "$status" != off ]; then
line=1
[ "$status" = bottom ] && line=19
click 3 "$line" 'window|0|2|0|inner|one'
click 3 "$((line + 1))" '|1|2|1|inner|zero'
fi
done
done
exit 0

View File

@@ -0,0 +1,55 @@
#!/bin/sh
# With the side status line off, a full redraw after changing window still
# draws pane border status lines that have not changed.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
check()
{
got=$($TMUX2 capture-pane -p -t outer:0.0 | head -n 1)
[ "$got" = "$2" ] || fail "$1: top row is '$got', expected '$2'"
}
$TMUX new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g pane-border-status top || exit 1
$TMUX set -g pane-border-format ' #{pane_index} ' || exit 1
$TMUX split-window -h 'sleep 100' || exit 1
$TMUX new-window 'sleep 100' || exit 1
$TMUX select-window -t:0 || exit 1
$TMUX2 new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
SPLIT='── 0 ───────────────┬── 1 ──────────────'
check attach "$SPLIT"
$TMUX select-window -t:1 || exit 1
sleep 0.5
$TMUX select-window -t:0 || exit 1
sleep 0.5
check "after select-window" "$SPLIT"
exit 0

View File

@@ -0,0 +1,113 @@
#!/bin/sh
# Creating, moving and resizing panes must use window coordinates with a
# side status line, including when the horizontal status is at the top.
PATH=/bin:/usr/bin
TERM=screen
export PATH TERM
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$side/$status: $*" >&2
exit 1
}
# Send window coordinates as one-based terminal coordinates.
mouse()
{
seq=$(printf '\033[<%s;%s;%s%s' "$1" \
"$(( $2 + xoff + 1 ))" "$(( $3 + yoff + 1 ))" "$4")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail "send mouse failed"
sleep 0.2
}
drag()
{
mouse "$1" "$2" "$3" M
mouse "$(( $1 + 32 ))" "$4" "$5" M
mouse "$1" "$4" "$5" m
}
check()
{
i=0
while [ "$i" -lt 50 ]; do
got=$($TMUX display -p -t "$1" \
'#{pane_left},#{pane_top},#{pane_width},#{pane_height}')
[ "$got" = "$2" ] && return
sleep 0.1
i=$((i + 1))
done
fail "pane $1: got '$got', expected '$2'"
}
$TMUX new-session -d -s inner -x 80 -y 24 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g default-command 'sleep 100' || exit 1
$TMUX set -g side-status-width 14 || exit 1
BASE=$($TMUX display -p '#{pane_id}') || exit 1
$TMUX2 new-session -d -s outer -x 80 -y 24 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
for side in left right; do
$TMUX set -g side-status "$side" || exit 1
xoff=0
[ "$side" = left ] && xoff=14
for status in off top bottom; do
yoff=0
if [ "$status" = off ]; then
$TMUX set -g status off || exit 1
else
$TMUX set -g status 2 || exit 1
$TMUX set -g status-position "$status" || exit 1
[ "$status" = top ] && yoff=2
fi
sleep 0.5
# Ctrl-drag creates a floating pane inside the rectangle.
drag 16 2 1 10 5
id=$($TMUX list-panes -F \
'#{?pane_floating_flag,#{pane_id},}' | tail -1)
[ -n "$id" ] || fail "drag did not create a floating pane"
check "$id" '3,2,7,3'
$TMUX kill-pane -t "$id" || exit 1
# Alt-drag moves a floating pane without changing its size.
id=$($TMUX new-pane -dPF '#{pane_id}' \
-x 14 -y 7 -X 5 -Y 3 'sleep 100') || exit 1
check "$id" '6,4,12,5'
drag 8 8 5 12 7
check "$id" '10,6,12,5'
# Dragging the bottom right border resizes both dimensions.
drag 0 22 11 25 13
check "$id" '10,6,15,7'
$TMUX kill-pane -t "$id" || exit 1
# The same terminal offsets apply to tiled pane borders.
id=$($TMUX split-window -dhPF '#{pane_id}' \
-t "$BASE" 'sleep 100') || exit 1
width=$($TMUX display -p -t "$BASE" '#{pane_width}')
drag 0 "$width" 5 "$((width + 4))" 5
got=$($TMUX display -p -t "$BASE" '#{pane_width}')
[ "$got" = "$((width + 4))" ] ||
fail "tiled width: got '$got', expected '$((width + 4))'"
$TMUX kill-pane -t "$id" || exit 1
done
done
exit 0

View File

@@ -0,0 +1,83 @@
#!/bin/sh
# A pane prompt redrawn after the pane scrolls stays beside the side status
# line. With side-status left the pane is not full width, so scrolling is
# redrawn as damage, which draws the prompt again over the pane.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
DIR=$(mktemp -d) || exit 1
TRIGGER=$DIR/trigger
CAPTURE=$DIR/capture
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
rm -rf "$DIR"
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
exit 1
}
wait_outer_has()
{
i=0
while [ "$i" -lt 50 ]; do
$TMUX2 capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null
grep -q "$1" "$CAPTURE" && return 0
sleep 0.1
i=$((i + 1))
done
fail "outer client did not show $1"
}
# The pane waits for the trigger, then prints enough lines to scroll.
$TMUX new-session -d -s inner -n work -x 60 -y 20 "sh -c '
while [ ! -e $TRIGGER ]; do sleep 0.1; done
i=0
while [ \$i -lt 40 ]; do i=\$((i + 1)); echo line\$i; done
exec sleep 100'" || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g automatic-rename off || exit 1
$TMUX set -g side-status left || exit 1
$TMUX2 new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
i=0
while [ "$i" -lt 50 ]; do
CLIENT=$($TMUX list-clients -F '#{client_name}' 2>/dev/null)
[ -n "$CLIENT" ] && break
sleep 0.1
i=$((i + 1))
done
[ -n "$CLIENT" ] || fail "inner client did not attach"
$TMUX command-prompt -b -P -t "$CLIENT" -p PROMPTX 'display -p %%' ||
exit 1
wait_outer_has PROMPTX
: >"$TRIGGER"
wait_outer_has line40
sleep 0.5
# The bottom row is the side status line followed by the prompt.
$TMUX2 capture-pane -p -t outer:0.0 >"$CAPTURE" || exit 1
want="$(printf '%13s' '')│PROMPTX"
got=$(tail -n 1 "$CAPTURE")
[ "$got" = "$want" ] || fail "bottom row is '$got', expected '$want'"
exit 0

View File

@@ -0,0 +1,78 @@
#!/bin/sh
# The side status line must refresh every status-interval seconds on its own,
# whether or not the horizontal status line is on, and whether it was on when
# the client attached or turned on afterwards. The client status timer drives
# both lines, so this checks it is armed in both cases with status off.
#
# The side status line is rendered by an inner tmux attached inside an outer
# tmux pane. Its format is a #() command which appends a byte to a file each
# time it runs, and a #() command is run again only when the format is
# expanded in a later second, so the file grows once per refresh. Nothing in
# the test asks for a redraw between the two counts, so growth means the
# timer refreshed the side status line by itself.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
COUNT1=$(mktemp)
COUNT2=$(mktemp)
trap "rm -f $COUNT1 $COUNT2; $TMUX kill-server 2>/dev/null; \
$TMUX2 kill-server 2>/dev/null" 0 1 15
fail() {
echo "$*" >&2
exit 1
}
count() {
wc -c <"$1" | tr -d ' '
}
# Attach the inner server in an outer pane, then wait for the first draw.
attach() {
$TMUX new -d -x30 -y8 "$TMUX2 attach" || exit 1
sleep 1
}
# Check the side status line refreshed at least twice in three seconds.
refreshes() {
before=$(count "$1")
sleep 3
after=$(count "$1")
[ $((after - before)) -ge 2 ] ||
fail "$2: side status ran $before then $after times, did not refresh"
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
# Side status on before the client attaches, with status off.
$TMUX2 new -d -x30 -y8 "sh -c 'exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g status-interval 1 || exit 1
$TMUX2 set -g side-status left || exit 1
$TMUX2 set -g side-status-format "#(printf x >>$COUNT1)" || exit 1
attach
refreshes "$COUNT1" "on at attach"
# Side status turned on after the client attached, with status off. A new
# inner server so the client timer starts from nothing.
$TMUX2 kill-server || exit 1
sleep 1
$TMUX2 new -d -x30 -y8 "sh -c 'exec sleep 100'" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g status-interval 1 || exit 1
$TMUX2 set -g side-status-format "#(printf x >>$COUNT2)" || exit 1
attach
$TMUX2 set -g side-status left || exit 1
sleep 1
refreshes "$COUNT2" "on after attach"
exit 0

View File

@@ -0,0 +1,197 @@
#!/bin/sh
# Drag the separator on either side, including across the moving boundary and
# horizontal status. Check limits, release, text bindings and a second client.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$side/$status: $*" >&2
exit 1
}
mouse()
{
seq=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail "send mouse failed"
sleep 0.1
}
check()
{
i=0
while [ "$i" -lt 50 ]; do
width=$($TMUX show -Av -t inner side-status-width)
window=$($TMUX display -p -t inner '#{window_width}')
if [ "$width" = "$1" ] && [ "$window" = "$((80 - $1))" ]; then
break
fi
sleep 0.1
i=$((i + 1))
done
[ "$width,$window" = "$1,$((80 - $1))" ] ||
fail "side/window widths $width/$window, expected $1/$((80 - $1))"
column=$1
[ "$side" = right ] && column=$((81 - $1))
padding=$(printf '%*s' "$((column - 1))" '')
for target in outer other; do
i=0
while [ "$i" -lt 50 ]; do
row=$($TMUX2 capture-pane -p -t "$target:0.0" |
sed -n "$((offset + 2))p")
case "$row" in
"$padding│"*) break ;;
esac
sleep 0.1
i=$((i + 1))
done
[ "$i" -lt 50 ] || fail "$target: wrong separator column: $row"
done
}
$TMUX new-session -d -s inner -n zero -x 80 -y 20 'sleep 180' || exit 1
$TMUX set -g mouse on || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status-format \
'#[range=window|0]window#[norange]' || exit 1
$TMUX bind -n MouseDrag1Status set -g @text-drag yes || exit 1
$TMUX bind -n MouseDrag1StatusDefault set -g @blank-drag yes || exit 1
$TMUX2 new-session -d -s outer -x 80 -y 20 'sleep 180' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 new-session -d -s other -x 80 -y 20 'sleep 180' || exit 1
for target in outer other; do
$TMUX2 respawn-pane -k -t "$target:0.0" \
"$TMUX attach -t inner" || exit 1
done
sleep 1
for side in left right; do
$TMUX set -t inner side-status "$side" || exit 1
for status in off top bottom; do
offset=0
if [ "$status" = off ]; then
$TMUX set -t inner status off || exit 1
else
$TMUX set -t inner status 2 || exit 1
$TMUX set -t inner status-position "$status" || exit 1
[ "$status" = top ] && offset=2
fi
$TMUX set -t inner side-status-width 14 || exit 1
check 14
if [ "$side" = left ]; then
start=14 grow=20 shrink=10 smallest=1 largest=80
else
start=67 grow=61 shrink=71 smallest=80 largest=1
fi
# Grow into the window, then shrink past the starting point.
mouse 0 "$start" "$((offset + 6))" M
mouse 32 "$grow" "$((offset + 6))" M
check 20
mouse 32 "$shrink" 1 M
check 10
mouse 32 "$largest" "$((offset + 6))" M
check 79
mouse 32 "$smallest" "$((offset + 6))" M
check 2
mouse 0 "$smallest" "$((offset + 6))" m
# Motion after release must not resize, and the separator stays
# draggable at the minimum width.
mouse 35 "$grow" "$((offset + 6))" M
check 2
sleep 0.5
start=2
[ "$side" = right ] && start=79
mouse 0 "$start" "$((offset + 6))" M
mouse 32 "$grow" "$((offset + 6))" M
check 20
mouse 0 "$grow" "$((offset + 6))" m
sleep 0.5
# Dragging text or blank content dispatches its usual binding.
text=2
[ "$side" = right ] && text=63
$TMUX set -g @text-drag no || exit 1
mouse 0 "$text" "$((offset + 1))" M
mouse 32 "$((text + 1))" "$((offset + 1))" M
mouse 32 40 "$((offset + 6))" M
mouse 0 40 "$((offset + 6))" m
got=$($TMUX show -gv @text-drag)
[ "$got" = yes ] || fail "text drag binding did not fire"
check 20
sleep 0.5
$TMUX set -g @blank-drag no || exit 1
mouse 0 "$text" "$((offset + 8))" M
mouse 32 40 "$((offset + 8))" M
mouse 0 40 "$((offset + 8))" m
got=$($TMUX show -gv @blank-drag)
[ "$got" = yes ] || fail "blank drag binding did not fire"
check 20
# A modified drag on the separator does not resize it.
mouse 16 "$grow" "$((offset + 6))" M
mouse 48 40 "$((offset + 6))" M
mouse 16 40 "$((offset + 6))" m
check 20
if [ "$status" != off ]; then
line=1
[ "$status" = bottom ] && line=19
mouse 0 "$grow" "$line" M
mouse 32 40 "$((offset + 6))" M
mouse 0 40 "$((offset + 6))" m
check 20
fi
[ "$($TMUX show -gv side-status-width)" = 14 ] ||
fail "drag changed the global width"
# Double-click removes the local width and inherits the default.
sleep 0.5
seq=$(printf '\033[<0;%s;%sM\033[<0;%s;%sm' \
"$grow" "$((offset + 6))" "$grow" "$((offset + 6))")
$TMUX2 send-keys -t outer:0.0 -l "$seq$seq" || exit 1
check 14
[ -z "$($TMUX show -v -q -t inner side-status-width)" ] ||
fail "double-click did not remove the local width"
done
done
# The resize action is an ordinary binding and can be replaced or removed.
$TMUX set -t inner side-status-width 20 || exit 1
check 20
$TMUX set -g @border-drag no || exit 1
$TMUX set -g @border-end no || exit 1
$TMUX bind -n MouseDrag1StatusBorder \
set -gF @border-drag '#{mouse_absolute_x},#{mouse_absolute_y}' || exit 1
$TMUX bind -n MouseDragEnd1StatusBorder set -g @border-end yes || exit 1
mouse 0 61 6 M
mouse 32 40 6 M
mouse 32 42 6 M
mouse 0 42 6 m
check 20
[ "$($TMUX show -gv @border-drag)" = 41,5 ] ||
fail "custom border binding did not get the terminal coordinates"
[ "$($TMUX show -gv @border-end)" = yes ] ||
fail "border drag end binding did not fire"
$TMUX unbind -n MouseDrag1StatusBorder || exit 1
mouse 0 61 6 M
mouse 32 40 6 M
mouse 0 40 6 m
check 20
exit 0

View File

@@ -0,0 +1,24 @@
0:editor │┌──────────┐
1:server* ││FLOAT │
2:logs- ││ │
│└──────────┘
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│

View File

@@ -0,0 +1,24 @@
0:editor │SERVER
1:server* │
2:logs- │
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│

View File

@@ -0,0 +1,24 @@
SERVER
[test]0:editor 1:server* 2:logs-

View File

@@ -0,0 +1,24 @@
SERVER │0:editor
│1:server*
│2:logs-
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
[test]0:editor 1:server* 2:logs-

View File

@@ -0,0 +1,24 @@
SERVER │0:editor
│1:server*
│2:logs-
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
[test]0:editor 1:server* 2:logs-

View File

@@ -0,0 +1,24 @@
SERVER │0:editor
│1:server*
│2:logs-
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│

View File

@@ -0,0 +1,24 @@
work │SERVER
├─→ 0:editor │
├─→ 1:server* │
└─→ 2:logs- │
beta │
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│
│

View File

@@ -0,0 +1,148 @@
#!/bin/sh
# With more windows than rows, the default side status format scrolls the
# window list to keep the current window visible and replaces the first and
# last visible rows with up and down markers.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
TMP=$(mktemp) || exit 1
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
rm -f "$TMP"
}
trap cleanup 0 1 15
$TMUX new-session -d -s inner -n w0 -x 60 -y 10 'sleep 100' || exit 1
i=1
while [ "$i" -lt 15 ]; do
$TMUX new-window -d -n "w$i" 'sleep 100' || exit 1
i=$((i + 1))
done
$TMUX set -g status off || exit 1
$TMUX set -g side-status left || exit 1
$TMUX select-window -t inner:7 || exit 1
$TMUX2 new-session -d -s outer -x 60 -y 10 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
# Compare the outer pane with the expected rows on standard input.
compare()
{
$TMUX2 capture-pane -p -t outer:0.0 >"$TMP" || exit 1
if ! cmp -s - "$TMP"; then
echo "$1 differs:" >&2
cat "$TMP" >&2
exit 1
fi
}
compare "scrolled side status" <<EOF
↑ │
3:w3 │
4:w4 │
5:w5 │
6:w6 │
7:w7* │
8:w8 │
9:w9 │
10:w10 │
↓ │
EOF
# A long current window name in the first row is cut on the right like the
# other rows.
$TMUX rename-window -t inner:0 dotfiles-repo || exit 1
$TMUX select-window -t inner:0 || exit 1
sleep 1
compare "first row" <<EOF
0:dotfiles-re│
1:w1 │
2:w2 │
3:w3 │
4:w4 │
5:w5 │
6:w6 │
7:w7- │
8:w8 │
↓ │
EOF
# A format with its list styles in the same order as on the status line, with
# list=on before the markers, draws the same rows.
$TMUX set -g side-status-format '#[list=on]#[list=left-marker]^#[nl]'\
'#[list=right-marker]v#[nl]'\
'#{W:#[range=window|#{window_index}]#I:#W#[norange]#[nl],'\
'#[range=window|#{window_index} list=focus]#I:#W*#[norange list=on]#[nl]}' ||
exit 1
$TMUX rename-window -t inner:0 w0 || exit 1
$TMUX select-window -t inner:7 || exit 1
sleep 1
compare "status line order" <<EOF
^ │
3:w3 │
4:w4 │
5:w5 │
6:w6 │
7:w7* │
8:w8 │
9:w9 │
10:w10 │
v │
EOF
# With list=on at the start of each window row and the focus closed in the
# current row, a long current window name is still cut on the right.
$TMUX set -g side-status-format \
'#{W:#[list=on]#I:#W#[nl],#[list=on]#[list=focus]#I:#W*#[list=on]#[nl]}' ||
exit 1
$TMUX rename-window -t inner:0 dotfiles-repo || exit 1
$TMUX select-window -t inner:0 || exit 1
sleep 1
compare "list=on rows" <<EOF
0:dotfiles-re│
1:w1 │
2:w2 │
3:w3 │
4:w4 │
5:w5 │
6:w6 │
7:w7 │
8:w8 │
9:w9 │
EOF
# With only one or two rows, a marker is not drawn over the current window.
$TMUX set -gu side-status-format || exit 1
$TMUX rename-window -t inner:0 w0 || exit 1
$TMUX select-window -t inner:7 || exit 1
$TMUX2 resize-window -t outer -y 2 || exit 1
sleep 1
compare "two rows" <<EOF
↑ │
7:w7* │
EOF
$TMUX select-window -t inner:1 || exit 1
sleep 1
compare "two rows at the top" <<EOF
0:w0 │
1:w1* │
EOF
$TMUX select-window -t inner:7 || exit 1
$TMUX2 resize-window -t outer -y 1 || exit 1
sleep 1
compare "one row" <<EOF
7:w7* │
EOF
exit 0

View File

@@ -0,0 +1,81 @@
#!/bin/sh
# The style at the end of a side status row carries over to the next row, as
# if the rows were one line: attributes removed from side-status-style stay
# removed, a pushed or set default stays in place, and the alignment, fill and
# hyperlink carry on. The inner client runs in an outer pane which is captured
# with its attributes.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export PATH TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX2 kill-server >/dev/null 2>&1
$TMUX kill-server >/dev/null 2>&1
}
trap cleanup 0 1 15
fail()
{
echo "$*" >&2
exit 1
}
# Check the second side status row starts with $2.
row2()
{
got=$($TMUX2 capture-pane -ep -t outer:0.0 | sed -n 2p)
case "$got" in
"$2"*) ;;
*) fail "$1: row 2 is '$got'" ;;
esac
}
$TMUX new-session -d -s inner -x 40 -y 10 'sleep 100' || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g side-status left || exit 1
$TMUX2 new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1
ESC=$(printf '\033')
$TMUX set -g side-status-style bold || exit 1
$TMUX set -g side-status-format '#[nobold]one#[nl]two' || exit 1
sleep 1
row2 "nobold" "two"
$TMUX set -g side-status-style default || exit 1
$TMUX set -g side-status-format \
'#[fg=red]#[push-default]one#[nl]#[fg=blue]#[default]two' || exit 1
sleep 1
row2 "push-default" "$ESC[31mtwo"
$TMUX set -g side-status-format \
'#[fg=red]#[set-default]#[fg=blue]#[push-default]one#[nl]'\
'#[pop-default]#[default]two' || exit 1
sleep 1
row2 "set-default" "$ESC[31mtwo"
$TMUX set -g side-status-format '#[align=right]one#[nl]two' || exit 1
sleep 1
row2 "align" " two"
$TMUX set -g side-status-format '#[fill=red]one#[nl]two' || exit 1
sleep 1
row2 "fill" "two$ESC[41m"
$TMUX set -g side-status-format '#[link=https://example.com/]one#[nl]two' ||
exit 1
sleep 1
row2 "link" "$ESC]8;"
exit 0

89
regress/side-status.sh Normal file
View File

@@ -0,0 +1,89 @@
#!/bin/sh
# Side status line rendering: geometry on the left and right, a floating pane
# against the window edge, coexistence with the horizontal status line, width
# changes and turning it off. Scenes are rendered in an inner tmux attached
# inside an outer tmux pane; the outer pane is captured and compared with
# goldens in side-status-results/.
#
# Run with GENERATE=1 to (re)create the golden files.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C.UTF-8
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
RESULTS=side-status-results
TMP=$(mktemp)
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" \
0 1 15
fail() {
echo "$*" >&2
exit 1
}
compare() {
sleep 1
$TMUX capturep -p >$TMP || exit 1
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
else
cmp -s $TMP "$RESULTS/$1.result" ||
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
mkdir -p "$RESULTS"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
$TMUX new-session -d -x 80 -y 24 || exit 1
# Inner server with fixed pane content so captures are stable.
$TMUX2 new-session -d -s work -n editor "sh -c 'printf EDITOR; exec sleep 100'"
$TMUX2 new-window -n server "sh -c 'printf SERVER; exec sleep 100'"
$TMUX2 new-window -n logs "sh -c 'printf LOGS; exec sleep 100'"
$TMUX2 select-window -t:1
$TMUX2 set -g status off
$TMUX2 set -g status-left "[test]"
$TMUX2 set -g status-right ""
$TMUX2 set -g side-status left
$TMUX send-keys "exec $TEST_TMUX -LtestB$$ -f/dev/null attach" Enter
sleep 1
compare side-left
# A floating pane at the window's top left corner sits beside the side status
# line, not over it.
id=$($TMUX2 new-pane -dPF '#{pane_id}' -x 12 -y 4 -X 0 -Y 0 \
"sh -c 'printf FLOAT; exec sleep 100'") || exit 1
compare side-left-floating
$TMUX2 kill-pane -t "$id" || exit 1
$TMUX2 set -g side-status right
compare side-right
$TMUX2 set -g status on
compare side-right-status
$TMUX2 set -g side-status-width 12
compare side-right-narrow
$TMUX2 set -g side-status off
compare side-off
# A second session collapses the bar into session rows with the current
# session's windows indented below its row.
$TMUX2 set -g side-status left
$TMUX2 set -g side-status-width 24
$TMUX2 set -g status off
$TMUX2 new-session -d -s beta -n shell "sh -c 'printf SHELL; exec sleep 100'"
$TMUX2 refresh-client -S
compare side-sessions
exit 0

View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# A client attached with ignore-size that is shorter than the status line # A client attached with ignore-size that is too small for the status line or
# must have it turned off rather than kill the server. # the side status line must have them turned off rather than kill the server.
# Each inner client runs in an outer pane of its size. # Each inner client runs in an outer pane of its size.
PATH=/bin:/usr/bin PATH=/bin:/usr/bin
@@ -51,4 +51,10 @@ $TMUX set -g status 5 || exit 1
attach 80x24 80x4 attach 80x24 80x4
$TMUX has-session 2>/dev/null || fail "server exited: status line too tall" $TMUX has-session 2>/dev/null || fail "server exited: status line too tall"
# A side status line wider than the client.
$TMUX set -g status on || exit 1
$TMUX set -g side-status left || exit 1
attach 80x24 13x24
$TMUX has-session 2>/dev/null || fail "server exited: side status too wide"
exit 0 exit 0

View File

@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# Status menus are displayed in the visible window and their commands # Status menus are displayed in the visible window, target the clicked
# target the clicked window, including when it is not current. # window, and are placed beside the clicked row on either side status line.
PATH=/bin:/usr/bin PATH=/bin:/usr/bin
TERM=screen TERM=screen
@@ -29,6 +29,7 @@ fail()
} }
# Right-button press at column $1, row $2 (1-based), targeting window $3. # Right-button press at column $1, row $2 (1-based), targeting window $3.
# If supplied, $4 is the expected row and column of the first menu item's text.
check_menu() check_menu()
{ {
seq=$(printf '\033[<2;%s;%sM' "$1" "$2") seq=$(printf '\033[<2;%s;%sM' "$1" "$2")
@@ -42,6 +43,14 @@ check_menu()
done done
grep -q 'Swap Left' "$CAPTURE" || fail "window $3 menu not visible" grep -q 'Swap Left' "$CAPTURE" || fail "window $3 menu not visible"
grep -q "─$3:" "$CAPTURE" || fail "window $3 title missing" grep -q "─$3:" "$CAPTURE" || fail "window $3 title missing"
if [ -n "$4" ]; then
got=$(awk '{
gsub(/│/, "|")
if (index($0, "Swap Left"))
print NR, index($0, "Swap Left")
}' "$CAPTURE")
[ "$got" = "$4" ] || fail "menu at '$got', expected '$4'"
fi
# Mark acts on the clicked window without selecting it. Other windows # Mark acts on the clicked window without selecting it. Other windows
# exercise dragging into the menu and releasing the right button. # exercise dragging into the menu and releasing the right button.
@@ -81,15 +90,49 @@ $TMUX new-window -d -n one 'sleep 100' || exit 1
$TMUX new-window -d -n two 'sleep 100' || exit 1 $TMUX new-window -d -n two 'sleep 100' || exit 1
$TMUX set -g mouse on || exit 1 $TMUX set -g mouse on || exit 1
$TMUX set -g automatic-rename off || exit 1 $TMUX set -g automatic-rename off || exit 1
$TMUX set -g status 2 || exit 1 $TMUX set -g side-status-width 14 || exit 1
$TMUX set -g 'status-format[0]' \
'#[range=window|0]0:zero#[norange] #[range=window|1]1:one#[norange]' || \
exit 1
$TMUX2 new-session -d -s outer -x 80 -y 24 'sleep 100' || exit 1 $TMUX2 new-session -d -s outer -x 80 -y 24 'sleep 100' || exit 1
$TMUX2 set -g status off || exit 1 $TMUX2 set -g status off || exit 1
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1 $TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
sleep 1 sleep 1
for side in left right; do
$TMUX set -g side-status "$side" || exit 1
if [ "$side" = left ]; then
column=3
text_column=17
else
column=69
text_column=51
fi
for status in off bottom top; do
offset=0
if [ "$status" = off ]; then
$TMUX set -g status off || exit 1
else
$TMUX set -g status 2 || exit 1
$TMUX set -g status-position "$status" || exit 1
[ "$status" = top ] && offset=2
fi
sleep 0.5
for window in 0 1; do
row=$((window + offset + 1))
check_menu "$column" "$row" "$window" \
"$((row + 1)) $text_column"
done
done
done
# A noncurrent window's dimensions must not constrain the visible menu.
$TMUX resize-window -t inner:1 -x 10 -y 5 || exit 1
check_menu 69 4 1 '5 51'
# The same target/display distinction applies to the horizontal status line.
$TMUX set -g side-status off || exit 1
$TMUX set -g status 2 || exit 1
$TMUX set -g 'status-format[0]' \
'#[range=window|0]0:zero#[norange] #[range=window|1]1:one#[norange]' || \
exit 1
for position in top bottom; do for position in top bottom; do
$TMUX set -g status-position "$position" || exit 1 $TMUX set -g status-position "$position" || exit 1
if [ "$position" = top ]; then if [ "$position" = top ]; then
@@ -98,12 +141,8 @@ for position in top bottom; do
row=23 row=23
fi fi
sleep 0.5 sleep 0.5
check_menu 2 "$row" 0 check_menu 2 "$row" 0 ''
check_menu 9 "$row" 1 check_menu 9 "$row" 1 ''
done done
# A noncurrent window's dimensions must not constrain the visible menu.
$TMUX resize-window -t inner:1 -x 10 -y 5 || exit 1
check_menu 9 23 1
exit 0 exit 0