Merge master into command_parser

This commit is contained in:
Nicholas Marriott
2026-07-15 10:34:14 +01:00
64 changed files with 6643 additions and 1637 deletions

View File

@@ -46,7 +46,7 @@ wait_for()
value=$($TMUX show -gqv "$option" 2>/dev/null || true)
[ "$value" = "$expected" ] && return 0
i=$((i + 1))
sleep 0.2
sleep 0.5
done
fail "expected $option to be '$expected' but got '$value'"
}
@@ -62,7 +62,7 @@ assert_unchanged()
[ "$value" = "$expected" ] || \
fail "expected $option to remain '$expected' but got '$value'"
i=$((i + 1))
sleep 0.2
sleep 0.5
done
}
@@ -77,7 +77,7 @@ wait_for_fmt()
value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true)
[ "$value" = "$expected" ] && return 0
i=$((i + 1))
sleep 0.2
sleep 0.5
done
fail "expected $fmt for $target to be '$expected' but got '$value'"
}
@@ -94,7 +94,7 @@ assert_fmt_unchanged()
[ "$value" = "$expected" ] || \
fail "expected $fmt for $target to remain '$expected' but got '$value'"
i=$((i + 1))
sleep 0.2
sleep 0.5
done
}
@@ -254,6 +254,19 @@ assert_unchanged @log ''
$TMUX set -wut mon:w0 monitor-activity ||
fail "unset monitor-activity w0 failed"
# Creating a non-current window counts as activity when monitor-activity is
# enabled in the window defaults.
$TMUX set -wg monitor-activity on || fail "set global monitor-activity failed"
$TMUX set -g @log '' || fail "reset @log failed"
$TMUX neww -d -t mon: -n newact cat || fail "new-window newact failed"
wait_for @log '|alert-activity:mon:newact'
wait_for_fmt mon:newact '#{window_activity_flag}' 1
flags_have mon:newact '#'
$TMUX set -wug monitor-activity ||
fail "unset global monitor-activity failed"
$TMUX selectw -t mon:newact || fail "select-window newact failed"
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# monitor-silence: a quiet window fires alert-silence after the interval
# and sets the silence flag, shown as ~ in window_flags. The hook fires
# only once while the flag remains set even though the timer keeps
@@ -273,6 +286,22 @@ wait_for_fmt mon:silw '#{window_silence_flag}' 0
flags_lack mon:silw '~'
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# Activity resets the silence timer: output before the original deadline
# prevents an alert until a full quiet interval has passed afterwards.
$TMUX neww -d -t mon: -n silreset cat || fail "new-window silreset failed"
$TMUX set -g @log '' || fail "reset @log failed"
$TMUX set -wt mon:silreset monitor-silence 10 ||
fail "set monitor-silence silreset failed"
sleep 3
activity mon:silreset
assert_unchanged @log ''
wait_for @log '|alert-silence:mon:silreset'
wait_for_fmt mon:silreset '#{window_silence_flag}' 1
$TMUX set -wt mon:silreset monitor-silence 0 ||
fail "reset monitor-silence silreset failed"
$TMUX selectw -t mon:silreset || fail "select-window silreset failed"
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
# A window linked into two sessions: the hook fires once per winlink and
# the flag is set in both sessions. Selecting the window in one session
# clears the flags on every winlink.

169
regress/copy-mode-redraw.sh Normal file
View File

@@ -0,0 +1,169 @@
#!/bin/sh
# Exercise copy mode redraw through a real client. An inner tmux is attached
# inside an outer tmux pane; the outer pane is captured to inspect what the
# inner client actually drew.
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"
fail() {
echo "$*" >&2
exit 1
}
capture() {
$TMUX capturep -pS0 -E- >$TMP || exit 1
}
check_line() {
line=$1
want=$2
got=$(sed -n "$line"p $TMP)
[ "$got" = "$want" ] || fail "line $line: expected '$want', got '$got'"
}
check_grep() {
pattern=$1
grep -Fq "$pattern" $TMP || fail "missing pattern: $pattern"
}
check_no_grep() {
pattern=$1
grep -Fq "$pattern" $TMP && fail "unexpected pattern: $pattern"
}
redraw() {
$TMUX2 send -X cursor-right || exit 1
sleep 1
capture
}
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
TMP=$(mktemp)
BEFORE=$(mktemp)
AFTER=$(mktemp)
trap "rm -f $TMP $BEFORE $AFTER; $TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null" 0 1 15
$TMUX2 -f/dev/null new -d -x48 -y8 -s test \
"awk 'BEGIN { for (i = 0; i < 90; i++) { if (i % 2 == 0) printf \"L%03d-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END\\n\", i; else printf \"S%03d\\n\", i } }'; exec sleep 100" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g mode-keys vi || exit 1
$TMUX2 set -g copy-mode-line-numbers off || exit 1
$TMUX2 set -g copy-mode-position-format "#[align=right][23/100-LONGTAIL]" || exit 1
$TMUX -f/dev/null new -d -x48 -y8 || exit 1
$TMUX set -g status off || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
CLIENT=$($TMUX2 list-clients -F '#{client_name}' | head -1)
[ -n "$CLIENT" ] || fail "no inner client"
$TMUX2 copy-mode || exit 1
$TMUX2 send -X history-top || exit 1
sleep 1
# Shrinking and moving the position indicator should not leave stale text from
# the previous indicator, for different widths and alignments.
for size in 20 48; do
$TMUX resizew -x$size -y8 || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
sleep 1
for align in left centre right; do
$TMUX2 set -g copy-mode-position-format "#[align=$align][23/100-LONGTAIL]" || exit 1
redraw
check_grep "[23/100-LONGTAIL]"
$TMUX2 set -g copy-mode-position-format "#[align=$align][1/100]" || exit 1
redraw
check_grep "[1/100]"
check_no_grep "LONGTAIL"
check_no_grep "[1/100]]"
done
$TMUX2 set -g copy-mode-position-format "#[align=right][1/100]" || exit 1
redraw
$TMUX2 set -g copy-mode-position-format "#[align=right][23/100]" || exit 1
redraw
check_grep "[23/100]"
done
# Scrolling from long pane content to short pane content should clear the
# remainder of the old line without relying on a full pre-clear.
$TMUX resizew -x48 -y8 || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
$TMUX2 send -X cancel || exit 1
$TMUX2 copy-mode -H || exit 1
$TMUX2 send -X history-top || exit 1
sleep 1
capture
check_line 1 "L000-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END"
$TMUX2 send -X scroll-down || exit 1
sleep 1
capture
check_line 1 "S001"
$TMUX2 send -X scroll-down || exit 1
sleep 1
capture
check_line 1 "L002-ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-END"
$TMUX2 send -X scroll-down || exit 1
sleep 1
capture
check_line 1 "S003"
# Scrolling from long content to a tabbed short line should still clear the old
# line tail.
$TMUX2 send -X cancel || exit 1
$TMUX resizew -x40 -y8 || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
$TMUX2 new-window \
"printf 'LONGTAIL-ABCDEFGHIJKLMNOPQRSTUV\nA\tB\nLONGTAIL-123456789012345678\nS\n'; i=0; while [ \$i -lt 20 ]; do printf 'FILLER-%02d\n' \$i; i=\$((i + 1)); done; exec sleep 100" || \
exit 1
sleep 1
$TMUX2 copy-mode -H || exit 1
$TMUX2 send -X history-top || exit 1
sleep 1
capture
check_line 1 "LONGTAIL-ABCDEFGHIJKLMNOPQRSTUV"
$TMUX2 send -X scroll-down || exit 1
sleep 1
capture
check_line 1 "A B"
sed -n 1p $TMP | grep -Fq "LONGTAIL" && \
fail "short tabbed line left stale tail"
# Reflow can leave padding from tabs at the start of wrapped lines. Entering
# copy mode should redraw those padding cells as spaces, not skip them and move
# the following text left.
$TMUX2 send -X cancel || exit 1
$TMUX resizew -x80 -y24 || exit 1
$TMUX2 refresh -t"$CLIENT" -c || exit 1
$TMUX2 new-window "cat ../tmux.c; exec sleep 100" || exit 1
sleep 1
$TMUX2 splitw -hd || exit 1
sleep 1
$TMUX capturep -pS0 -E- >$BEFORE || exit 1
$TMUX2 copy-mode -H -t:.0 || exit 1
sleep 1
$TMUX capturep -pS0 -E- >$AFTER || exit 1
if ! cmp -s "$BEFORE" "$AFTER"; then
diff -u "$BEFORE" "$AFTER" >&2
fail "copy-mode redraw moved reflowed tab padding"
fi
exit 0

View File

@@ -0,0 +1,113 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
cleanup()
{
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
}
fail()
{
echo "$1"
cleanup
exit 1
}
expect_buffer()
{
expected=$1
actual=$($TMUX show-buffer)
[ "$actual" = "$expected" ] ||
fail "unexpected buffer: expected [$expected], got [$actual]"
}
wheel()
{
button=$1
col=$2
row=$3
seq=$(printf '\033[<%s;%s;%sM' "$button" "$col" "$row")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 1
}
trap cleanup 0
trap 'exit 1' 1 2 3 15
$TMUX new -d -x40 -y10 \
'i=0; while [ $i -lt 80 ]; do printf "line %02d xxxxxxxxxx\n" $i; i=$((i + 1)); done; cat' ||
exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g mouse on || exit 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X history-top || exit 1
$TMUX send-keys -N10 -X cursor-down || exit 1
$TMUX send-keys -X start-of-line || exit 1
$TMUX send-keys -X begin-selection || exit 1
$TMUX send-keys -N2 -X cursor-down || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
initial=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx')
expect_buffer "$initial"
$TMUX send-keys -X stop-selection || exit 1
$TMUX send-keys -N3 -X scroll-down || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -N2 -X scroll-up || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -X scroll-middle || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -X scroll-bottom || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -X scroll-top || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -X recentre-top-bottom || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$initial"
$TMUX send-keys -X other-end || exit 1
$TMUX send-keys -X cursor-down || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
extended_end=$(printf 'line 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx')
expect_buffer "$extended_end"
$TMUX send-keys -X stop-selection || exit 1
$TMUX send-keys -X other-end || exit 1
$TMUX send-keys -X other-end || exit 1
$TMUX send-keys -X cursor-up || exit 1
$TMUX send-keys -X copy-selection-no-clear || exit 1
extended_start=$(printf 'line 09 xxxxxxxxxx\nline 10 xxxxxxxxxx\nline 11 xxxxxxxxxx\nline 12 xxxxxxxxxx')
expect_buffer "$extended_start"
$TMUX2 new-session -d -x40 -y10 "$TMUX attach" || exit 1
sleep 1
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
[ -n "$OUTER" ] || fail "no outer pane"
wheel 65 5 5
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$extended_start"
wheel 64 5 5
$TMUX send-keys -X copy-selection-no-clear || exit 1
expect_buffer "$extended_start"
exit 0

746
regress/customize-mode.sh Normal file
View File

@@ -0,0 +1,746 @@
#!/bin/sh
# Exercise customize-mode-specific mutations. mode-mutation.sh covers stale
# mode-tree data while customize-mode is open; this covers the actions unique to
# customize mode: adding, setting, editing, resetting, renaming array keys and
# unsetting options, hooks, environment variables and key bindings.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMP=$(mktemp -d) || exit 1
TMUX_TMPDIR="$TMP"
export TMUX_TMPDIR
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
OUT="$TEST_TMUX -LtestB$$ -f/dev/null"
FORMAT='CM #{?is_option,O,#{?is_environment,E,K}} #{option_name}#{environment_name}#{key}=#{option_value}#{environment_value}'
cleanup_servers()
{
$TMUX kill-server 2>/dev/null
$OUT kill-server 2>/dev/null
sleep 0.5
}
cleanup()
{
cleanup_servers
rm -rf "$TMP"
}
trap cleanup EXIT
fail()
{
echo "$1" >&2
cleanup
exit 1
}
capture()
{
$OUT capture-pane -p -t out:0 2>/dev/null
}
settle()
{
sleep 0.3
}
wait_clients()
{
i=0
while [ "$i" -lt 50 ]; do
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
[ "$c" -eq "$1" ] && return 0
sleep 0.2
i=$((i + 1))
done
fail "expected $1 clients, have $c"
}
wait_mode()
{
want=$1
i=0
while [ "$i" -lt 50 ]; do
got=$($TMUX display-message -p -t cm:0 '#{pane_in_mode}' \
2>/dev/null)
[ "$got" = "$want" ] && return 0
sleep 0.2
i=$((i + 1))
done
fail "pane mode state is $got, expected $want"
}
assert_alive()
{
$TMUX display-message -p 'alive' >/dev/null 2>&1 ||
fail "$1: server exited"
}
start_client()
{
cleanup_servers
$TMUX new-session -d -s cm -n main -x 80 -y 24 'cat' ||
fail "new-session failed"
$OUT new-session -d -s out -x 80 -y 24 "$TMUX attach -t cm" ||
fail "outer client failed"
wait_clients 1
}
open_customize()
{
if [ "$#" -eq 0 ]; then
$TMUX customize-mode -t cm:0 -F "$FORMAT" ||
fail "customize-mode failed"
else
$TMUX customize-mode -t cm:0 -F "$FORMAT" -f "$1" ||
fail "customize-mode failed"
fi
wait_mode 1
settle
}
send()
{
$TMUX send-keys -t cm:0 "$@" || fail "send-keys failed: $*"
}
send_literal()
{
$TMUX send-keys -t cm:0 -l "$1" || fail "send literal failed: $1"
}
repeat_key()
{
key=$1
count=$2
i=0
while [ "$i" -lt "$count" ]; do
send "$key"
i=$((i + 1))
done
}
replace_prompt()
{
send C-u
send_literal "$1"
send Enter
settle
}
assert_equals()
{
got=$1
want=$2
what=$3
[ "$got" = "$want" ] ||
fail "$what: got '$got', expected '$want'"
}
assert_empty()
{
got=$1
what=$2
[ -z "$got" ] || fail "$what: got '$got', expected empty"
}
assert_contains()
{
text=$1
needle=$2
what=$3
echo "$text" | grep -F -q "$needle" ||
fail "$what: missing '$needle' in '$text'"
}
assert_not_contains()
{
text=$1
needle=$2
what=$3
echo "$text" | grep -F -q "$needle" &&
fail "$what: found '$needle' in '$text'"
return 0
}
test_user_option()
{
start_client
open_customize
send Enter
settle
send_literal "cm_add added"
send Enter
settle
assert_equals "$($TMUX show-options -sqv @cm_add)" "added" \
"added server user option"
send q
wait_mode 0
open_customize '#{==:#{option_name},@cm_add}'
send Right j Enter
settle
replace_prompt "changed"
assert_equals "$($TMUX show-options -sqv @cm_add)" "changed" \
"set user option"
send u y
settle
assert_empty "$($TMUX show-options -sqv @cm_add)" "unset user option"
send q
wait_mode 0
}
test_reset_window_pane_option()
{
start_client
$TMUX set-option -g window-style fg=red ||
fail "set global window-style failed"
$TMUX set-window-option -t cm:0 window-style fg=green ||
fail "set window window-style failed"
$TMUX set-option -p -t cm:0.0 window-style fg=blue ||
fail "set pane window-style failed"
open_customize '#{==:#{option_name},window-style}'
send j j Right j
settle
send d y
settle
assert_equals "$($TMUX show-option -gqv window-style)" "default" \
"reset global window-style"
assert_empty "$($TMUX show-options -wqv -t cm:0 window-style)" \
"reset window window-style"
assert_empty "$($TMUX show-option -p -t cm:0.0 -qv window-style)" \
"reset pane window-style"
send q
wait_mode 0
}
test_environment()
{
start_client
open_customize
repeat_key j 6
send Enter
settle
send_literal "CM_ENV=one"
send Enter
settle
assert_equals "$($TMUX show-environment -t cm CM_ENV)" \
"CM_ENV=one" "add session environment"
send q
wait_mode 0
open_customize '#{==:#{environment_name},CM_ENV}'
repeat_key j 6
send Right j Enter
settle
replace_prompt "two"
assert_equals "$($TMUX show-environment -t cm CM_ENV)" \
"CM_ENV=two" "set session environment"
send S
settle
replace_prompt "global"
assert_equals "$($TMUX show-environment -g CM_ENV)" \
"CM_ENV=global" "set global environment"
send u y
settle
$TMUX show-environment -t cm CM_ENV >/dev/null 2>&1 &&
fail "unset session environment: variable still exists"
send q
wait_mode 0
}
test_user_hook()
{
start_client
open_customize
repeat_key j 3
send Enter
settle
send_literal "cm_hook display-message hook"
send Enter
settle
assert_equals "$($TMUX show-option -qv @cm_hook)" \
"display-message hook" "add user hook"
send q
wait_mode 0
open_customize '#{==:#{option_name},@cm_hook}'
repeat_key j 3
send Right j
settle
assert_contains "$(capture)" "@cm_hook" "user hook shown as hook"
send u y
settle
assert_empty "$($TMUX show-option -qv @cm_hook)" "unset user hook"
send q
wait_mode 0
}
test_hook_array()
{
start_client
$TMUX set-hook -g 'after-new-session[0]' 'display-message old' ||
fail "set hook array failed"
open_customize '#{==:#{option_name},after-new-session}'
repeat_key j 3
send Right j Right j
settle
send a
settle
replace_prompt "1"
hooks=$($TMUX show-hooks -g after-new-session)
assert_contains "$hooks" "after-new-session[1] display-message old" \
"renamed hook array key"
assert_not_contains "$hooks" "after-new-session[0]" \
"old hook array key removed"
send Enter
settle
replace_prompt "display-message new"
hooks=$($TMUX show-hooks -g after-new-session)
assert_contains "$hooks" "after-new-session[1] display-message new" \
"set hook array value"
send u y
settle
hooks=$($TMUX show-hooks -g after-new-session)
assert_not_contains "$hooks" "after-new-session[1]" \
"unset hook array item"
send q
wait_mode 0
}
test_changed_only()
{
start_client
$TMUX set-option -s @cm_changed yes ||
fail "set changed server option failed"
$TMUX bind-key -T prefix User1 display-message changed-only ||
fail "set changed key failed"
open_customize
send C
settle
send M-+
settle
screen=$(capture)
assert_contains "$screen" "@cm_changed" "changed-only user option"
assert_contains "$screen" "User1" "changed-only key"
assert_not_contains "$screen" "Global Environment" \
"changed-only hides environment"
send q
wait_mode 0
}
test_reset_key()
{
start_client
orig=$($TMUX list-keys -T prefix -F '#{key_command}' c) ||
fail "default c binding not found"
$TMUX bind-key -T prefix c display-message changed ||
fail "change c binding failed"
open_customize '#{==:#{key},c}'
repeat_key j 10
send Right j d y
settle
after=$($TMUX list-keys -T prefix -F '#{key_command}' c) ||
fail "reset c binding not found"
assert_equals "$after" "$orig" "reset key binding"
send q
wait_mode 0
}
test_tagged_unset()
{
start_client
$TMUX set-option -s @cm_tag_a one || fail "set tag option a failed"
$TMUX set-option -s @cm_tag_b two || fail "set tag option b failed"
open_customize '#{m/r:^@cm_tag_,#{option_name}}'
send Right j t j t U y
settle
assert_empty "$($TMUX show-options -sqv @cm_tag_a)" \
"tagged unset option a"
assert_empty "$($TMUX show-options -sqv @cm_tag_b)" \
"tagged unset option b"
send q
wait_mode 0
}
test_key_binding()
{
start_client
open_customize
repeat_key j 10
send Enter
settle
send_literal "User0 display-message before"
send Enter
settle
keys=$($TMUX list-keys -T prefix User0 2>/dev/null) ||
fail "added key binding not found"
assert_contains "$keys" "display-message before" "add key binding"
send q
wait_mode 0
open_customize '#{==:#{key},User0}'
repeat_key j 10
send Right j Right j
settle
send s
settle
replace_prompt "display-message after"
keys=$($TMUX list-keys -T prefix User0 2>/dev/null) ||
fail "edited key binding not found"
assert_contains "$keys" "display-message after" "set key command"
send j s
settle
replace_prompt "custom note"
note=$($TMUX list-keys -T prefix -F '#{key_note}' User0)
assert_equals "$note" "custom note" "set key note"
send j s
settle
keys=$($TMUX list-keys -T prefix User0 2>/dev/null) ||
fail "repeat key binding not found"
assert_contains "$keys" "bind-key -r" "toggle key repeat"
send u y
settle
$TMUX list-keys -T prefix User0 >/dev/null 2>&1 &&
fail "unset key binding: binding still exists"
send q
wait_mode 0
}
test_editor()
{
start_client
editor="$TMP/editor.sh"
editor_value="$TMP/editor-value"
{
echo '#!/bin/sh'
printf 'cat "%s" > "$1"\n' "$editor_value"
} > "$editor" || fail "write editor helper failed"
chmod +x "$editor" || fail "chmod editor helper failed"
$TMUX set-option -g editor "$editor" || fail "set editor failed"
$TMUX set-option -g @cm_edit old || fail "set edit option failed"
printf 'edited' > "$editor_value" || fail "write editor value failed"
open_customize '#{==:#{option_name},@cm_edit}'
send j Right j e
i=0
while [ "$i" -lt 50 ]; do
value=$($TMUX show-option -gqv @cm_edit)
[ "$value" = "edited" ] && break
sleep 0.2
i=$((i + 1))
done
assert_equals "$value" "edited" "edit option in editor"
send q
wait_mode 0
$TMUX bind-key -T prefix User2 display-message old ||
fail "set editor key failed"
printf 'display-message edited' > "$editor_value" ||
fail "write editor key command failed"
open_customize '#{==:#{key},User2}'
repeat_key j 10
send Right j Right j e
i=0
while [ "$i" -lt 50 ]; do
value=$($TMUX list-keys -T prefix -F '#{key_command}' User2)
[ "$value" = "display-message edited" ] && break
sleep 0.2
i=$((i + 1))
done
assert_equals "$value" "display-message edited" \
"edit key command in editor"
printf 'edited note' > "$editor_value" ||
fail "write editor key note failed"
send j e
i=0
while [ "$i" -lt 50 ]; do
value=$($TMUX list-keys -T prefix -F '#{key_note}' User2)
[ "$value" = "edited note" ] && break
sleep 0.2
i=$((i + 1))
done
assert_equals "$value" "edited note" "edit key note in editor"
send q
wait_mode 0
$TMUX set-environment -t cm CM_EDIT_ENV old ||
fail "set editor environment failed"
printf 'env-edited' > "$editor_value" ||
fail "write editor environment failed"
open_customize '#{==:#{environment_name},CM_EDIT_ENV}'
repeat_key j 6
send Right j e
i=0
while [ "$i" -lt 50 ]; do
value=$($TMUX show-environment -t cm CM_EDIT_ENV)
[ "$value" = "CM_EDIT_ENV=env-edited" ] && break
sleep 0.2
i=$((i + 1))
done
assert_equals "$value" "CM_EDIT_ENV=env-edited" \
"edit environment in editor"
send q
wait_mode 0
}
test_stale_current_items()
{
start_client
$TMUX set-option -s @cm_stale_option old ||
fail "set stale option failed"
open_customize '#{==:#{option_name},@cm_stale_option}'
send Right j
settle
$TMUX set-option -su @cm_stale_option ||
fail "external unset stale option failed"
send Enter
send u y
send d y
settle
assert_alive "stale option current item"
send q
wait_mode 0
$TMUX set-environment -t cm CM_STALE_ENV old ||
fail "set stale environment failed"
open_customize '#{==:#{environment_name},CM_STALE_ENV}'
repeat_key j 6
send Right j
settle
$TMUX set-environment -t cm -u CM_STALE_ENV ||
fail "external unset stale environment failed"
send Enter
send S
send u y
settle
assert_alive "stale environment current item"
send q
wait_mode 0
$TMUX bind-key -T prefix User3 display-message stale ||
fail "set stale key failed"
open_customize '#{==:#{key},User3}'
repeat_key j 10
send Right j
settle
$TMUX unbind-key -T prefix User3 ||
fail "external unbind stale key failed"
send s
send d y
send u y
settle
assert_alive "stale key current item"
send q
wait_mode 0
}
test_prompt_invalidation()
{
start_client
$TMUX set-option -s @cm_prompt_stale old ||
fail "set prompt stale option failed"
open_customize '#{==:#{option_name},@cm_prompt_stale}'
send Right j Enter
settle
$TMUX set-option -su @cm_prompt_stale ||
fail "external unset prompt option failed"
replace_prompt "new"
assert_empty "$($TMUX show-options -sqv @cm_prompt_stale)" \
"prompt stale option should not be recreated"
assert_alive "stale option prompt"
send q
wait_mode 0
$TMUX bind-key -T prefix User4 display-message old ||
fail "set prompt stale key failed"
open_customize '#{==:#{key},User4}'
repeat_key j 10
send Right j Right j s
settle
$TMUX unbind-key -T prefix User4 ||
fail "external unbind prompt key failed"
replace_prompt "display-message new"
$TMUX list-keys -T prefix User4 >/dev/null 2>&1 &&
fail "prompt stale key should stay unbound"
assert_alive "stale key prompt"
send q
wait_mode 0
$TMUX set-hook -g 'after-new-session[0]' 'display-message old' ||
fail "set prompt stale hook array failed"
$TMUX set-hook -g 'after-new-session[1]' 'display-message taken' ||
fail "set prompt stale hook array target failed"
open_customize '#{==:#{option_name},after-new-session}'
repeat_key j 3
send Right j Right j a
settle
replace_prompt "1"
hooks=$($TMUX show-hooks -g after-new-session)
assert_contains "$hooks" "after-new-session[0] display-message old" \
"array rename to existing key keeps old key"
assert_contains "$hooks" "after-new-session[1] display-message taken" \
"array rename to existing key keeps target key"
assert_alive "existing array key prompt"
send q
wait_mode 0
}
test_editor_invalidation()
{
start_client
editor="$TMP/slow-editor.sh"
{
echo '#!/bin/sh'
echo 'sleep 1'
echo 'printf edited > "$1"'
} > "$editor" || fail "write slow editor helper failed"
chmod +x "$editor" || fail "chmod slow editor helper failed"
$TMUX set-option -g editor "$editor" || fail "set slow editor failed"
$TMUX set-option -g @cm_editor_stale old ||
fail "set editor stale option failed"
open_customize '#{==:#{option_name},@cm_editor_stale}'
send j Right j e
settle
$TMUX set-option -gu @cm_editor_stale ||
fail "external unset editor option failed"
sleep 2
assert_empty "$($TMUX show-option -gqv @cm_editor_stale)" \
"editor stale option should not be recreated"
assert_alive "stale option editor"
send q
wait_mode 0
$TMUX bind-key -T prefix User5 display-message old ||
fail "set editor stale key failed"
open_customize '#{==:#{key},User5}'
repeat_key j 10
send Right j Right j e
settle
$TMUX unbind-key -T prefix User5 ||
fail "external unbind editor key failed"
sleep 2
$TMUX list-keys -T prefix User5 >/dev/null 2>&1 &&
fail "editor stale key should stay unbound"
assert_alive "stale key editor"
send q
wait_mode 0
$TMUX set-environment -t cm CM_EDITOR_STALE old ||
fail "set editor stale environment failed"
open_customize '#{==:#{environment_name},CM_EDITOR_STALE}'
repeat_key j 6
send Right j e
settle
$TMUX set-environment -t cm -u CM_EDITOR_STALE ||
fail "external unset editor environment failed"
sleep 2
assert_equals "$($TMUX show-environment -t cm CM_EDITOR_STALE)" \
"CM_EDITOR_STALE=edited" "editor stale environment is reset"
assert_alive "stale environment editor"
send q
wait_mode 0
}
test_invalid_prompts()
{
start_client
open_customize
send Enter
settle
send_literal "bad value"
send Enter
settle
assert_empty "$($TMUX show-options -sqv bad)" "invalid user option name"
assert_alive "invalid user option prompt"
send q
wait_mode 0
open_customize
repeat_key j 10
send Enter
settle
send_literal "NoSuchKey display-message invalid"
send Enter
settle
assert_alive "invalid key prompt"
send q
wait_mode 0
$TMUX bind-key -T prefix User6 display-message valid ||
fail "set invalid command key failed"
open_customize '#{==:#{key},User6}'
repeat_key j 10
send Right j Right j s
settle
replace_prompt "not-a-command -Z"
keys=$($TMUX list-keys -T prefix -F '#{key_command}' User6)
assert_equals "$keys" "display-message valid" \
"invalid key command keeps old command"
assert_alive "invalid key command prompt"
send q
wait_mode 0
}
cleanup_servers
test_user_option
test_reset_window_pane_option
test_environment
test_user_hook
test_hook_array
test_changed_only
test_reset_key
test_tagged_unset
test_key_binding
test_editor
test_stale_current_items
test_prompt_invalidation
test_editor_invalidation
test_invalid_prompts
cleanup
exit 0

254
regress/display-panes.sh Normal file
View File

@@ -0,0 +1,254 @@
#!/bin/sh
# Tests for display-panes as panes-mode.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMP=$(mktemp -d) || exit 1
TMUX_TMPDIR="$TMP"
export TMUX_TMPDIR
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
rm -rf "$TMP"
}
trap cleanup EXIT
fail()
{
echo "$1" >&2
exit 1
}
wait_format()
{
target=$1
format=$2
want=$3
i=0
while [ "$i" -lt 50 ]; do
got=$($TMUX display-message -p -t "$target" "$format" 2>/dev/null)
[ "$got" = "$want" ] && return 0
sleep 0.2
i=$((i + 1))
done
fail "$target: expected '$want' for $format, got '$got'"
}
wait_option()
{
option=$1
want=$2
i=0
while [ "$i" -lt 50 ]; do
got=$($TMUX show -gv "$option" 2>/dev/null)
[ "$got" = "$want" ] && return 0
sleep 0.2
i=$((i + 1))
done
fail "$option: expected '$want', got '$got'"
}
wait_clients()
{
i=0
while [ "$i" -lt 50 ]; do
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
[ "$c" -eq "$1" ] && return 0
sleep 0.2
i=$((i + 1))
done
fail "expected $1 clients, have $c"
}
$TMUX new-session -d -s m -x 80 -y 24 'cat' || exit 1
$TMUX split-window -t m:0 'cat' || exit 1
p0=$($TMUX display-message -p -t m:0.0 '#{pane_id}')
p1=$($TMUX display-message -p -t m:0.1 '#{pane_id}')
$TMUX set -g display-panes-format 'P#{pane_index}' ||
fail "set display-panes-format failed"
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t m" || exit 1
wait_clients 1
capture()
{
$TMUX2 capture-pane -p -t out:0 2>/dev/null
}
wait_capture()
{
i=0
while [ "$i" -lt 50 ]; do
CAPTURED=$(capture)
printf '%s\n' "$CAPTURED" | grep -F -q "$1" && return 0
sleep 0.2
i=$((i + 1))
done
fail "timed out waiting for '$1' in capture"
}
# Entry, default zoom, and exit.
$TMUX display-panes -d 0 -t "$p0" || fail "display-panes failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_format "$p0" '#{window_zoomed_flag}' '1'
wait_capture 'P0'
wait_capture 'P1'
$TMUX send-keys -t "$p0" q || fail "exit panes mode failed"
wait_format "$p0" '#{pane_in_mode}' '0'
wait_format "$p0" '#{window_zoomed_flag}' '0'
# -Z starts unzoomed.
$TMUX display-panes -Zd 0 -t "$p0" || fail "display-panes -Z failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_format "$p0" '#{window_zoomed_flag}' '0'
$TMUX send-keys -t "$p0" q || fail "exit unzoomed panes mode failed"
wait_format "$p0" '#{pane_in_mode}' '0'
# Selection keys run the command template and close the mode.
$TMUX set -g @picked none || fail "set @picked failed"
$TMUX display-panes -d 0 -t "$p0" 'set -g @picked %%' ||
fail "display-panes selection failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
$TMUX send-keys -t "$p0" 1 || fail "select pane failed"
wait_format "$p0" '#{pane_in_mode}' '0'
wait_option @picked "$p1"
# Commands after display-panes run immediately while the mode remains.
$TMUX set -g @after none || fail "set @after failed"
$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast ||
fail "display-panes immediate command failed"
wait_option @after fast
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_format "$p0" '#{pane_in_mode}' '0'
# Existing zoom is restored on exit.
$TMUX select-pane -t "$p0" || fail "select p0 failed"
$TMUX resize-pane -Z -t "$p0" || fail "zoom failed"
wait_format "$p0" '#{window_zoomed_flag}' '1'
$TMUX display-panes -d 0 -t "$p0" || fail "display-panes zoomed failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
$TMUX send-keys -t "$p0" q || fail "exit zoomed panes mode failed"
wait_format "$p0" '#{pane_in_mode}' '0'
wait_format "$p0" '#{window_zoomed_flag}' '1'
$TMUX resize-pane -Z -t "$p0" || fail "unzoom cleanup failed"
# Panes mode can be stacked above another mode and returns to it on exit.
$TMUX copy-mode -t "$p0" || fail "copy-mode failed"
wait_format "$p0" '#{pane_mode}' 'copy-mode'
$TMUX display-panes -d 0 -t "$p0" 'set -g @picked %%' ||
fail "display-panes over copy-mode failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
$TMUX send-keys -t "$p0" 1 || fail "select from stacked mode failed"
wait_format "$p0" '#{pane_mode}' 'copy-mode'
wait_option @picked "$p1"
$TMUX send-keys -t "$p0" -X cancel || fail "copy-mode cancel failed"
wait_format "$p0" '#{pane_in_mode}' '0'
# Panes mode is not kept underneath another mode.
$TMUX display-panes -d 0 -t "$p0" || fail "display-panes no-stack failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
$TMUX copy-mode -t "$p0" || fail "copy-mode over panes-mode failed"
wait_format "$p0" '#{pane_mode}' 'copy-mode'
$TMUX send-keys -t "$p0" -X cancel || fail "copy-mode cancel failed"
wait_format "$p0" '#{pane_in_mode}' '0'
wait_format "$p0" '#{window_zoomed_flag}' '0'
# -s shows panes from a different source window but enters the mode in the
# target pane.
$TMUX new-window -d -t m: -n src 'cat' || fail "new source window failed"
$TMUX split-window -t m:src 'cat' || fail "split source window failed"
sp0=$($TMUX display-message -p -t m:src.0 '#{pane_id}')
sp1=$($TMUX display-message -p -t m:src.1 '#{pane_id}')
$TMUX respawn-pane -k -t "$sp0" 'printf "SOURCE-PANE\n"; exec cat' ||
fail "write source marker failed"
$TMUX set -g display-panes-format '' || fail "clear source display format failed"
$TMUX set -w -t m:src display-panes-format 'SOURCE-ONLY' ||
fail "set source window display format failed"
$TMUX set -g @picked none || fail "reset @picked failed"
$TMUX display-panes -d 0 -s m:src -t "$p0" 'set -g @picked %%' ||
fail "display-panes source window failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_capture 'SOURCE-PANE'
CAPTURED=$(capture)
printf '%s\n' "$CAPTURED" | grep -F -q 'SOURCE-ONLY' &&
fail "used source window display-panes-format"
$TMUX send-keys -t "$p0" 1 || fail "select source pane failed"
wait_format "$p0" '#{pane_in_mode}' '0'
wait_option @picked "$sp1"
$TMUX kill-window -t m:src || fail "kill source window failed"
$TMUX set -g display-panes-format 'P#{pane_index}' ||
fail "reset display-panes-format after source failed"
# pane-border-status rows are drawn as plain preview borders and do not shift
# pane content into the status row.
$TMUX set -w -t m:0 pane-border-status top ||
fail "set pane-border-status failed"
$TMUX set -g display-panes-format '' || fail "clear display-panes-format failed"
$TMUX respawn-pane -k -t "$p0" 'printf "STATUS-TOP\n"; exec cat' ||
fail "write status marker failed"
wait_capture 'STATUS-TOP'
$TMUX display-panes -d 0 -t "$p0" || fail "display-panes status failed"
wait_format "$p0" '#{pane_mode}' 'panes-mode'
wait_capture 'STATUS-TOP'
CAPTURED=$(capture)
first=$(printf '%s\n' "$CAPTURED" | sed -n '1p')
second=$(printf '%s\n' "$CAPTURED" | sed -n '2p')
printf '%s\n' "$first" | grep -F -q 'STATUS-TOP' &&
fail "pane content drawn in status border row"
printf '%s\n' "$second" | grep -F -q 'STATUS-TOP' ||
fail "pane content not drawn below status border row"
$TMUX send-keys -t "$p0" q || fail "exit status panes mode failed"
wait_format "$p0" '#{pane_in_mode}' '0'
$TMUX set -w -t m:0 pane-border-status off ||
fail "reset pane-border-status failed"
$TMUX set -g display-panes-format 'P#{pane_index}' ||
fail "reset display-panes-format failed"
# Floating panes are always framed in panes-mode, independent of their real
# pane-border-lines setting.
fp=$($TMUX new-pane -dPF '#{pane_id}' -B none -x 30 -y 8 -X 8 -Y 3 \
-t m:0 'cat') || fail "new borderless floating pane failed"
$TMUX set -g display-panes-format '' || fail "clear display-panes-format failed"
$TMUX display-panes -Zd 0 -t "$fp" || fail "display-panes floating failed"
wait_format "$fp" '#{pane_mode}' 'panes-mode'
wait_capture '┌'
wait_capture '┘'
$TMUX send-keys -t "$fp" q || fail "exit floating panes mode failed"
wait_format "$fp" '#{pane_in_mode}' '0'
$TMUX display-panes -d 0 -t "$fp" || fail "display-panes floating zoomed failed"
wait_format "$fp" '#{pane_mode}' 'panes-mode'
wait_format "$fp" '#{window_zoomed_flag}' '1'
wait_capture '┌'
wait_capture '┘'
$TMUX send-keys -t "$fp" q || fail "exit zoomed floating panes mode failed"
wait_format "$fp" '#{pane_in_mode}' '0'
wait_format "$fp" '#{window_zoomed_flag}' '0'
$TMUX resize-pane -t "$fp" -x 48 -y 14 ||
fail "resize floating pane larger failed"
$TMUX set -g display-panes-format '#[align=right]FP#{pane_width}x#{pane_height}' ||
fail "set floating display-panes-format failed"
$TMUX display-panes -Zd 0 -t "$fp" || fail "display-panes floating format failed"
wait_format "$fp" '#{pane_mode}' 'panes-mode'
wait_capture 'FP48x14'
$TMUX resize-pane -t "$fp" -x 30 -y 10 ||
fail "resize floating pane smaller failed"
wait_capture 'FP30x10'
$TMUX send-keys -t "$fp" q || fail "exit floating format panes mode failed"
wait_format "$fp" '#{pane_in_mode}' '0'
$TMUX kill-pane -t "$fp" || fail "kill floating pane failed"
$TMUX set -g display-panes-format 'P#{pane_index}' ||
fail "reset display-panes-format after floating failed"
exit 0

View File

@@ -11,11 +11,13 @@ OUT=$(mktemp -d)
TMUX_TMPDIR="$OUT"
export TMUX_TMPDIR
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
fail()
{
echo "$*" >&2
$TMUX kill-server 2>/dev/null || true
$TMUX2 kill-server 2>/dev/null || true
rm -rf "$OUT"
exit 1
}
@@ -23,6 +25,7 @@ fail()
cleanup()
{
$TMUX kill-server 2>/dev/null || true
$TMUX2 kill-server 2>/dev/null || true
rm -rf "$OUT"
}
trap cleanup EXIT
@@ -92,6 +95,23 @@ wait_for @w 'window-unlinked:main:mywin'
$TMUX set-hook -gu window-linked || fail "unset window-linked failed"
$TMUX set-hook -gu window-unlinked || fail "unset window-unlinked failed"
# window-created and window-closed are actual window lifetime hooks.
$TMUX set -g @wc 0 || fail "set @wc failed"
$TMUX set-hook -g window-created \
'set -gF @wc "#{@wc}|#{hook}:#{hook_window}:#{hook_window_name}"' ||
fail "set-hook window-created failed"
$TMUX set-hook -g window-closed \
'set -gF @wc "#{@wc}|#{hook}:#{hook_window}:#{hook_window_name}"' ||
fail "set-hook window-closed failed"
$TMUX neww -d -t main: -n lifetime || fail "new-window lifetime failed"
window=$($TMUX display -pt main:lifetime '#{window_id}') ||
fail "display lifetime window failed"
wait_for @wc "0|window-created:$window:lifetime"
$TMUX kill-window -t main:lifetime || fail "kill-window lifetime failed"
wait_for @wc "0|window-created:$window:lifetime|window-closed:$window:lifetime"
$TMUX set-hook -gu window-created || fail "unset window-created failed"
$TMUX set-hook -gu window-closed || fail "unset window-closed failed"
# window-renamed sees the window id.
window=$($TMUX display -pt main:0 '#{window_id}') ||
fail "display window_id failed"
@@ -114,26 +134,75 @@ $TMUX set-hook -gu window-layout-changed ||
fail "unset window-layout-changed failed"
# window-pane-changed from select-pane.
old_pane=$($TMUX display -pt main:0.0 '#{pane_id}') ||
fail "display old pane failed"
new_pane=$($TMUX display -pt main:0.1 '#{pane_id}') ||
fail "display new pane failed"
$TMUX set -g @p 0 || fail "set @p failed"
$TMUX set-hook -g window-pane-changed \
'set -gF @p "#{hook}:#{hook_window}"' ||
'set -gF @p "#{hook}:#{hook_window}:#{hook_pane}:#{hook_old_pane}:#{hook_new_pane}"' ||
fail "set-hook window-pane-changed failed"
$TMUX selectp -t main:0.1 || fail "select-pane failed"
wait_for @p "window-pane-changed:$window"
wait_for @p "window-pane-changed:$window:$new_pane:$old_pane:$new_pane"
# window-pane-changed from removing the active pane.
$TMUX set -g @p 0 || fail "reset @p failed"
$TMUX killp -t "$new_pane" || fail "kill active pane failed"
wait_for @p "window-pane-changed:$window:$old_pane:$new_pane:$old_pane"
$TMUX set-hook -gu window-pane-changed ||
fail "unset window-pane-changed failed"
# session-window-changed from select-window.
$TMUX neww -d -t main: -n w2 || fail "new-window w2 failed"
old_window=$($TMUX display -pt main:0 '#{window_id}') ||
fail "display old window failed"
new_window=$($TMUX display -pt main:w2 '#{window_id}') ||
fail "display new window failed"
old_index=$($TMUX display -pt main:0 '#{window_index}') ||
fail "display old window index failed"
new_index=$($TMUX display -pt main:w2 '#{window_index}') ||
fail "display new window index failed"
$TMUX set -g @c 0 || fail "set @c failed"
$TMUX set-hook -g session-window-changed \
'set -gF @c "#{hook}:#{hook_session_name}"' ||
'set -gF @c "#{hook}:#{hook_session_name}:#{hook_window}:#{hook_old_window}:#{hook_new_window}:#{hook_old_window_index}->#{hook_new_window_index}"' ||
fail "set-hook session-window-changed failed"
$TMUX selectw -t main:w2 || fail "select-window failed"
wait_for @c 'session-window-changed:main'
wait_for @c "session-window-changed:main:$new_window:$old_window:$new_window:$old_index->$new_index"
$TMUX set-hook -gu session-window-changed ||
fail "unset session-window-changed failed"
# session-added-to-group and session-removed-from-group carry group details.
$TMUX set -g @sg 0 || fail "set @sg failed"
$TMUX set-hook -g session-added-to-group \
'set -gF @sg "#{@sg}|#{hook}:#{hook_session_name}:#{hook_group}:#{hook_group_size}"' ||
fail "set-hook session-added-to-group failed"
$TMUX set-hook -g session-removed-from-group \
'set -gF @sg "#{@sg}|#{hook}:#{hook_session_name}:#{hook_group}:#{hook_group_size}"' ||
fail "set-hook session-removed-from-group failed"
$TMUX new -d -s group-a || fail "new-session group-a failed"
$TMUX new -d -s group-b -t group-a || fail "new-session group-b failed"
wait_for @sg "0|session-added-to-group:group-a:group-a:1|session-added-to-group:group-b:group-a:2"
$TMUX kill-session -t group-b || fail "kill-session group-b failed"
wait_for @sg "0|session-added-to-group:group-a:group-a:1|session-added-to-group:group-b:group-a:2|session-removed-from-group:group-b:group-a:2"
$TMUX set-hook -gu session-added-to-group ||
fail "unset session-added-to-group failed"
$TMUX set-hook -gu session-removed-from-group ||
fail "unset session-removed-from-group failed"
$TMUX kill-session -t group-a || fail "kill-session group-a failed"
# window-resized carries old and new dimensions.
old_size=$($TMUX display -pt main:0 '#{window_width},#{window_height}') ||
fail "display old window size failed"
$TMUX set -g @wr 0 || fail "set @wr failed"
$TMUX set-hook -g window-resized \
'set -gF @wr "#{hook}:#{hook_window}:#{hook_old_width},#{hook_old_height}->#{hook_width},#{hook_height}"' ||
fail "set-hook window-resized failed"
$TMUX resizew -t main:0 -x 70 -y 20 || fail "resize-window failed"
new_size=$($TMUX display -pt main:0 '#{window_width},#{window_height}') ||
fail "display new window size failed"
wait_for @wr "window-resized:$old_window:$old_size->$new_size"
$TMUX set-hook -gu window-resized || fail "unset window-resized failed"
# pane-mode-changed from entering and leaving copy mode.
$TMUX set -g @m 0 || fail "set @m failed"
pane=$($TMUX display -pt main:0.0 '#{pane_id}') ||
@@ -168,18 +237,44 @@ $TMUX set-hook -gu pane-died || fail "unset pane-died failed"
$TMUX killp -t "$pane" || fail "kill-pane failed"
$TMUX set -g remain-on-exit off || fail "reset remain-on-exit failed"
# pane-title-changed when a pane sets its title.
# pane-title-changed from split-window -T.
$TMUX set -g @t 0 || fail "set @t failed"
$TMUX set-hook -g pane-title-changed \
'set -gF @t "#{hook}:#{hook_pane}:#{pane_title}"' ||
'set -gF @t "#{hook}:#{hook_pane}:#{hook_new_title}:#{pane_title}"' ||
fail "set-hook pane-title-changed failed"
pane=$($TMUX splitw -d -t main:0 -T splittitle -P -F '#{pane_id}' \
'sleep 30') ||
fail "split-window title option failed"
wait_for @t "pane-title-changed:$pane:splittitle:splittitle"
$TMUX killp -t "$pane" || fail "kill-pane title option failed"
# pane-title-changed from select-pane -T.
$TMUX set -g @t 0 || fail "reset @t select title failed"
pane=$($TMUX display -pt main:0.0 '#{pane_id}') ||
fail "display select title pane failed"
$TMUX selectp -t "$pane" -T selecttitle || fail "select-pane title failed"
wait_for @t "pane-title-changed:$pane:selecttitle:selecttitle"
# pane-title-changed when a pane sets its title.
$TMUX set -g @t 0 || fail "reset @t OSC title failed"
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
'printf "\033]2;mytitle\007"; sleep 30') ||
fail "split-window title failed"
wait_for @t "pane-title-changed:$pane:mytitle"
wait_for @t "pane-title-changed:$pane:mytitle:mytitle"
$TMUX killp -t "$pane" || fail "kill-pane title failed"
# pane-title-changed from popping the title stack.
$TMUX set -g @t 0 || fail "reset @t title pop failed"
$TMUX set-hook -g pane-title-changed \
'set -gF @t "#{@t}|#{hook_new_title}"' ||
fail "set-hook pane-title-changed pop failed"
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
'printf "\033]2;stackbase\007\033[22;0t\033]2;stacktemp\007\033[23;0t"; sleep 30') ||
fail "split-window title pop failed"
wait_for @t "0|stackbase|stacktemp|stackbase"
$TMUX set-hook -gu pane-title-changed ||
fail "unset pane-title-changed failed"
$TMUX killp -t "$pane" || fail "kill-pane title failed"
$TMUX killp -t "$pane" || fail "kill-pane title pop failed"
# pane-created payload from a command pane, an empty pane, and a respawn.
$TMUX set -g @pc 0 || fail "set @pc failed"
@@ -204,6 +299,82 @@ wait_for @pc "pane-created:$pane:\"sleep 30\":0:1"
$TMUX killp -t "$pane" || fail "kill-pane pane-created respawn failed"
$TMUX set-hook -gu pane-created || fail "unset pane-created failed"
# pane-moved from break-pane and join-pane.
$TMUX neww -d -t main: -n move-a 'sleep 30' ||
fail "new-window move-a failed"
pane=$($TMUX splitw -d -t main:move-a -P -F '#{pane_id}' 'sleep 30') ||
fail "split-window pane-moved failed"
target=$($TMUX display -pt main:move-a.0 '#{pane_id}') ||
fail "display pane-moved target failed"
old_window=$($TMUX display -pt "$pane" '#{window_id}') ||
fail "display pane-moved old window failed"
old_index=$($TMUX display -pt "$pane" '#{window_index}') ||
fail "display pane-moved old index failed"
$TMUX set -g @pm 0 || fail "set @pm failed"
$TMUX set-hook -g pane-moved \
'set -gF @pm "#{hook}:#{hook_pane}:#{hook_old_window}:#{hook_old_window_index}->#{hook_new_window}:#{hook_new_window_index}"' ||
fail "set-hook pane-moved failed"
$TMUX breakp -d -s "$pane" -n move-b -t main: ||
fail "break-pane pane-moved failed"
new_window=$($TMUX display -pt "$pane" '#{window_id}') ||
fail "display pane-moved break new window failed"
new_index=$($TMUX display -pt "$pane" '#{window_index}') ||
fail "display pane-moved break new index failed"
wait_for @pm "pane-moved:$pane:$old_window:$old_index->$new_window:$new_index"
$TMUX set -g @pm 0 || fail "reset @pm join failed"
old_window=$new_window
old_index=$new_index
$TMUX joinp -d -s "$pane" -t "$target" ||
fail "join-pane pane-moved failed"
new_window=$($TMUX display -pt "$pane" '#{window_id}') ||
fail "display pane-moved join new window failed"
new_index=$($TMUX display -pt "$pane" '#{window_index}') ||
fail "display pane-moved join new index failed"
wait_for @pm "pane-moved:$pane:$old_window:$old_index->$new_window:$new_index"
$TMUX kill-window -t main:move-a || fail "kill-window move-a failed"
# pane-moved from cross-window swap-pane fires once for each pane.
$TMUX neww -d -t main: -n swap-a 'sleep 30' ||
fail "new-window swap-a failed"
$TMUX neww -d -t main: -n swap-b 'sleep 30' ||
fail "new-window swap-b failed"
pane1=$($TMUX display -pt main:swap-a.0 '#{pane_id}') ||
fail "display swap pane1 failed"
pane2=$($TMUX display -pt main:swap-b.0 '#{pane_id}') ||
fail "display swap pane2 failed"
window1=$($TMUX display -pt "$pane1" '#{window_id}') ||
fail "display swap window1 failed"
window2=$($TMUX display -pt "$pane2" '#{window_id}') ||
fail "display swap window2 failed"
index1=$($TMUX display -pt "$pane1" '#{window_index}') ||
fail "display swap index1 failed"
index2=$($TMUX display -pt "$pane2" '#{window_index}') ||
fail "display swap index2 failed"
$TMUX set -g @pm 0 || fail "reset @pm swap failed"
$TMUX set-hook -g pane-moved \
'set -gF @pm "#{@pm}|#{hook_pane}:#{hook_old_window}:#{hook_old_window_index}->#{hook_new_window}:#{hook_new_window_index}"' ||
fail "set-hook pane-moved swap failed"
$TMUX swapp -d -s "$pane1" -t "$pane2" ||
fail "swap-pane pane-moved failed"
wait_for @pm "0|$pane1:$window1:$index1->$window2:$index2|$pane2:$window2:$index2->$window1:$index1"
$TMUX set-hook -gu pane-moved || fail "unset pane-moved failed"
$TMUX kill-window -t main:swap-a || fail "kill-window swap-a failed"
$TMUX kill-window -t main:swap-b || fail "kill-window swap-b failed"
# pane-bell fires for the actual pane even when bell alerts are disabled.
pane=$($TMUX neww -d -t main: -n raw-bell -P -F '#{pane_id}' 'cat') ||
fail "new-window pane-bell failed"
$TMUX setw -t main:raw-bell monitor-bell off ||
fail "set monitor-bell off failed"
$TMUX set -g @pb 0 || fail "set @pb failed"
$TMUX set-hook -g pane-bell 'set -gF @pb "#{hook}:#{hook_pane}"' ||
fail "set-hook pane-bell failed"
$TMUX send-keys -t "$pane" -H 07 0a || fail "send-keys pane-bell failed"
wait_for @pb "pane-bell:$pane"
$TMUX set-hook -gu pane-bell || fail "unset pane-bell failed"
$TMUX kill-window -t main:raw-bell || fail "kill-window pane-bell failed"
# pane-resized carries old and new dimensions.
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'sleep 30') ||
fail "split-window pane-resized failed"
@@ -276,6 +447,13 @@ $TMUX killp -t "$pane" || fail "kill-pane zoom failed"
# client-attached and client-detached using a control client.
$TMUX set -g @a 0 || fail "set @a failed"
$TMUX set -g @cl 0 || fail "set @cl failed"
$TMUX set-hook -g client-created \
'set -gF @cl "#{?#{m:client-*,#{hook_client}},#{@cl}|#{hook}:#{hook_client},#{@cl}}"' ||
fail "set-hook client-created failed"
$TMUX set-hook -g client-closed \
'set -gF @cl "#{?#{m:client-*,#{hook_client}},#{@cl}|#{hook}:#{hook_client},#{@cl}}"' ||
fail "set-hook client-closed failed"
$TMUX set-hook -g client-attached 'set -gF @a "#{hook}"' ||
fail "set-hook client-attached failed"
$TMUX set-hook -g client-detached 'set -gF @a "#{hook}"' ||
@@ -284,9 +462,72 @@ mkfifo "$OUT/fifo" || fail "mkfifo failed"
$TMUX -C attach -t main <"$OUT/fifo" >"$OUT/control.out" 2>&1 &
exec 3>"$OUT/fifo"
wait_for @a 'client-attached'
# client-session-changed carries old and new sessions.
$TMUX new -d -s client-target || fail "new-session client target failed"
client=$($TMUX list-clients -F '#{client_name} #{client_control_mode}' |
awk '$2 == 1 { print $1; exit }') ||
fail "list control client failed"
[ -n "$client" ] || fail "no control client found"
wait_for @cl "0|client-created:$client"
old_session=$($TMUX display -pt main '#{session_id}') ||
fail "display old client session failed"
new_session=$($TMUX display -pt client-target '#{session_id}') ||
fail "display new client session failed"
$TMUX set -g @cs 0 || fail "set @cs failed"
$TMUX set-hook -g client-session-changed \
'set -gF @cs "#{hook}:#{hook_client}:#{hook_session}:#{hook_old_session}:#{hook_new_session}"' ||
fail "set-hook client-session-changed failed"
$TMUX switch-client -c "$client" -t client-target ||
fail "switch-client session failed"
wait_for @cs "client-session-changed:$client:$new_session:$old_session:$new_session"
$TMUX set-hook -gu client-session-changed ||
fail "unset client-session-changed failed"
exec 3>&-
wait_for @a 'client-detached'
wait_for @cl "0|client-created:$client|client-closed:$client"
$TMUX set-hook -gu client-created || fail "unset client-created failed"
$TMUX set-hook -gu client-closed || fail "unset client-closed failed"
$TMUX set-hook -gu client-attached || fail "unset client-attached failed"
$TMUX set-hook -gu client-detached || fail "unset client-detached failed"
# client-resized carries old and new dimensions.
$TMUX2 new -d -s resize -x 80 -y 24 'sleep 30' ||
fail "new-session client-resized failed"
$TMUX2 set -g @cr 0 || fail "set @cr failed"
$TMUX2 set-hook -g client-resized \
'set -gF @cr "#{hook}:#{hook_client}:#{hook_old_width},#{hook_old_height}->#{hook_width},#{hook_height}"' ||
fail "set-hook client-resized failed"
$TMUX neww -d -n resize-client "$TMUX2 attach -t resize" ||
fail "new outer window for client-resized failed"
i=0
client=
while [ $i -lt 30 ]; do
client=$($TMUX2 list-clients -F '#{client_name}' 2>/dev/null |
awk 'NR == 1 { print; exit }')
[ -n "$client" ] && break
i=$((i + 1))
sleep 0.2
done
[ -n "$client" ] || fail "inner client did not attach"
old_size=$($TMUX2 list-clients -F '#{client_width},#{client_height}') ||
fail "display old client size failed"
$TMUX resizew -t resize-client -x 70 -y 20 ||
fail "resize outer client window failed"
i=0
while [ $i -lt 30 ]; do
value=$($TMUX2 show -gqv @cr 2>/dev/null || true)
new_size=$($TMUX2 list-clients -F '#{client_width},#{client_height}' \
2>/dev/null || true)
expected="client-resized:$client:$old_size->$new_size"
[ "$new_size" != "$old_size" ] && [ "$value" = "$expected" ] &&
break
i=$((i + 1))
sleep 0.2
done
[ "$value" = "$expected" ] ||
fail "expected @cr to be '$expected' but got '$value'"
$TMUX2 set-hook -gu client-resized || fail "unset client-resized failed"
exit 0

View File

@@ -8,9 +8,11 @@
# - copy-mode -k (window-copy.c);
# - choose-tree -k (window-tree.c);
# - choose-buffer -k (window-buffer.c).
# - display-panes -k (window-panes.c).
#
# choose-tree and choose-buffer share cmd_choose_tree_exec(), which also backs
# choose-client and customize-mode, so those are not repeated.
# choose-client, customize-mode, and display-panes, so choose-client and
# customize-mode are not repeated.
#
# Each mode is entered in the active pane of a two-pane window: exiting with
# -k must remove that pane and leave the other. copy-mode is left with the
@@ -147,7 +149,7 @@ wait_mode "$active" 0
pane_gone "$active" && fail 'copy-mode: pane killed without -k'
$TMUX kill-window -t m:w 2>/dev/null
# --- choose-tree -k and choose-buffer -k kill the pane ----------------------
# --- choose-tree -k, choose-buffer -k, and display-panes -k kill the pane ---
#
# These need the client to draw the mode before a key acts, so attach one now.
# A paste buffer is needed for choose-buffer to have something to show, and a
@@ -168,4 +170,11 @@ wait_for 'BUFMARK'
$TMUX send-keys -t m:w q || fail "choose-buffer exit failed"
check_killed 'choose-buffer -k'
open_window
$TMUX set -g display-panes-format 'DPMARK' || fail "set display-panes-format"
$TMUX display-panes -k -d 0 -t m:w || fail "display-panes -k failed"
wait_for 'DPMARK'
$TMUX send-keys -t m:w q || fail "display-panes exit failed"
check_killed 'display-panes -k'
exit 0

81
regress/new-pane-mouse.sh Normal file
View File

@@ -0,0 +1,81 @@
#!/bin/sh
# Test new-pane -M creates a floating pane from a mouse drag.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
cleanup()
{
$TMUX kill-server >/dev/null 2>&1
$TMUX2 kill-server >/dev/null 2>&1
}
fail()
{
echo "$*" >&2
cleanup
exit 1
}
must_equal()
{
got=$1
want=$2
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
}
# drag STARTCOL STARTROW ENDCOL ENDROW
#
# Write an SGR Ctrl-mouse press, drag update and release at 1-based positions
# to the outer pane holding the inner client.
drag()
{
scol="$1"
srow="$2"
ecol="$3"
erow="$4"
seq=$(printf '\033[<16;%s;%sM' "$scol" "$srow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<48;%s;%sM' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 0.2
seq=$(printf '\033[<16;%s;%sm' "$ecol" "$erow")
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
sleep 1
}
cleanup
$TMUX new-session -d -s inner -x 80 -y 24 || exit 1
$TMUX set -g mouse on
$TMUX set -g default-command 'sleep 100'
$TMUX2 new-session -d -x 80 -y 24 "$TMUX attach -t inner" || exit 1
sleep 1
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
[ -n "$OUTER" ] || fail "No outer pane."
BASE=$($TMUX list-panes -F '#{pane_id}' | head -1)
[ -n "$BASE" ] || fail "No base pane."
# Drag from SGR column 3,row 1 to column 10,row 5. These are window positions
# 2,0 to 9,4. With the default border, the floating pane content is inset by
# one cell and has size 6x3.
drag 3 1 10 5
id=$($TMUX list-panes -F '#{?pane_floating_flag,#{pane_id},}' | tail -n 1)
[ -n "$id" ] || fail "no floating pane created"
must_equal "$($TMUX display-message -p -t "$id" '#{pane_left}')" 3
must_equal "$($TMUX display-message -p -t "$id" '#{pane_top}')" 1
must_equal "$($TMUX display-message -p -t "$id" '#{pane_width}')" 6
must_equal "$($TMUX display-message -p -t "$id" '#{pane_height}')" 3
$TMUX new-pane -d -M -L -t "$BASE" 'sleep 100' || fail "new-pane -M -L failed"
cleanup
exit 0

View File

@@ -0,0 +1,133 @@
#!/bin/sh
# Exercise drawing of window menus in the screen-redraw.c scene. Menus are
# window-owned and drawn as REDRAW_MENU spans over panes. Captures use -e so
# menu-style, menu-selected-style, and menu-border-style are included.
#
# 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=
TMUX2=
SETUP=0
RESULTS=screen-redraw-results
TMP=$(mktemp)
TMP2=$(mktemp)
cleanup() {
rm -f "$TMP" "$TMP2"
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
}
trap cleanup 0 1 15
fail() {
echo "$*" >&2
exit 1
}
trim() {
awk '
{ lines[NR] = $0 }
END {
n = NR
while (n > 0 && lines[n] == "")
n--
for (i = 1; i <= n; i++)
print lines[i]
}
' "$TMP" >"$TMP2" || exit 1
mv "$TMP2" "$TMP" || exit 1
}
compare() {
sleep 1
$TMUX capturep -pe >$TMP || exit 1
trim
if [ -n "$GENERATE" ]; then
cp $TMP "$RESULTS/$1.result" || exit 1
echo "generated $1"
else
cmp -s $TMP "$RESULTS/$1.result" || \
fail "scene $1 differs from $RESULTS/$1.result"
fi
}
C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"MENU%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
setup() {
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
SETUP=$((SETUP + 1))
TMUX="$TEST_TMUX -LtestA$$-$SETUP -f/dev/null"
TMUX2="$TEST_TMUX -LtestB$$-$SETUP -f/dev/null"
$TMUX2 new -d -x"$1" -y"$2" "$C" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX2 set -g window-size manual || exit 1
$TMUX2 resizew -x"$1" -y"$2" || exit 1
$TMUX2 setw menu-style "fg=colour250,bg=colour235" || exit 1
$TMUX2 setw menu-selected-style "fg=colour16,bg=colour220" || exit 1
$TMUX2 setw menu-border-style "fg=colour45,bg=colour235" || exit 1
$TMUX new -d -x"$1" -y"$2" || exit 1
$TMUX set -g status off || exit 1
$TMUX set -g window-size manual || exit 1
$TMUX set -g default-terminal "tmux-256color" || exit 1
$TMUX send -l "$TMUX2 attach" || exit 1
$TMUX send Enter || exit 1
sleep 1
}
menu() {
$TMUX2 display-menu -T "Menu" -C 1 "$@" \
"Alpha item" a "" \
"Beta item" b "" \
"" "" "" \
"Gamma item" g "" || exit 1
sleep 1
}
# Basic menu over a single pane.
setup 40 14
menu -x6 -y8
compare menu-basic
# Menu over a split: drawn on top of the pane border.
setup 40 14
$TMUX2 splitw -h "$C" || exit 1
menu -x8 -y9
compare menu-over-split
# Menu with no border lines.
setup 40 14
menu -b none -x6 -y8
compare menu-noborder
# Menu with double border lines.
setup 40 14
menu -b double -x6 -y8
compare menu-double
# Window menu styles are re-expanded from window options when redrawn.
setup 40 14
menu -x6 -y8
$TMUX2 setw menu-style "fg=colour196,bg=colour22" || exit 1
$TMUX2 setw menu-selected-style "fg=colour231,bg=colour88" || exit 1
$TMUX2 setw menu-border-style "fg=colour51,bg=colour22" || exit 1
$TMUX2 refresh-client -S || exit 1
compare menu-restyled
# Menu wider than the window: it should be accepted and clipped by the scene.
setup 12 6
$TMUX2 display-menu -T "Menu" -C 0 -x0 -y5 \
"This item is much wider than the window" t "" \
"Second item is also too wide" s "" || exit 1
compare menu-clipped
exit 0

View File

@@ -0,0 +1,13 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02┌─Menu───────────┐
MENU03│ Alpha item (a) │
MENU04│ Beta item (b) │
MENU05├────────────────┤
MENU06│ Gamma item (g) │
MENU07└────────────────┘
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
MENU11 abcdefghij
MENU12 abcdefghij

View File

@@ -0,0 +1,5 @@
fghij
┌─Menu─────┐
│ window> │
│ oo wide> │
└──────────┘

View File

@@ -0,0 +1,13 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02╔═Menu═══════════╗
MENU03║ Alpha item (a) ║
MENU04║ Beta item (b) ║
MENU05╠════════════════╣
MENU06║ Gamma item (g) ║
MENU07╚════════════════╝
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
MENU11 abcdefghij
MENU12 abcdefghij

View File

@@ -0,0 +1,13 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02 Menu 
MENU03  Alpha item (a)  
MENU04  Beta item (b)  
MENU05 
MENU06  Gamma item (g)  
MENU07 
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
MENU11 abcdefghij
MENU12 abcdefghij

View File

@@ -0,0 +1,14 @@
MENU00 abcdefghij │MENU00 abcdefghij
MENU01 abcdefghij │MENU01 abcdefghij
MENU02 abcdefghij │MENU02 abcdefghij
MENU03 a┌─Menu───────────┐3 abcdefghij
MENU04 a│ Alpha item (a) │4 abcdefghij
MENU05 a│ Beta item (b) │5 abcdefghij
MENU06 a├────────────────┤6 abcdefghij
MENU07 a│ Gamma item (g) │7 abcdefghij
MENU08 a└────────────────┘8 abcdefghij
MENU09 abcdefghij │MENU09 abcdefghij
MENU10 abcdefghij │MENU10 abcdefghij
MENU11 abcdefghij │MENU11 abcdefghij
MENU12 abcdefghij │MENU12 abcdefghij
│

View File

@@ -0,0 +1,13 @@
MENU00 abcdefghij
MENU01 abcdefghij
MENU02┌─Menu───────────┐
MENU03│ Alpha item (a) │
MENU04│ Beta item (b) │
MENU05├────────────────┤
MENU06│ Gamma item (g) │
MENU07└────────────────┘
MENU08 abcdefghij
MENU09 abcdefghij
MENU10 abcdefghij
MENU11 abcdefghij
MENU12 abcdefghij