Files
tmux/regress/window-ops.sh
2026-07-06 11:50:58 +01:00

407 lines
13 KiB
Bash

#!/bin/sh
# Tests of window management command semantics (not parsing), as implemented
# in cmd-new-window.c, cmd-move-window.c (move-window and link-window),
# cmd-unlink-window.c, cmd-swap-window.c, cmd-rotate-window.c,
# cmd-kill-window.c and cmd-select-window.c.
#
# This exercises:
# - new-window placement: next free index, explicit index, index in use with
# and without -k, -a (after) and -b (before) insertion with shuffling, and
# -S selecting an existing window by name instead of creating;
# - move-window to a free index, to an occupied index with and without -k,
# -a insertion and -r renumbering (including base-index);
# - renumber-windows closing gaps;
# - link-window sharing a window between two sessions (window_linked and
# window_linked_sessions), unlink-window removing one link and refusing to
# unlink the last link without -k;
# - swap-window within and between sessions, -d keeping the active window,
# and the grouped-sessions error;
# - rotate-window -U/-D rotating pane positions;
# - kill-window switching to the last (previously current) window, kill-window
# -a killing all other windows and the "-f only valid with -a" guard.
#
# pane-ops.sh covers pane-level commands and buffers.sh covers paste buffers.
PATH=/bin:/usr/bin
TERM=screen
LANG=C.UTF-8
LC_ALL=C.UTF-8
export TERM LANG LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
# check_ok $cmd...
#
# Run a command and require that it succeeds.
check_ok()
{
if ! $TMUX "$@"; then
echo "Command failed (expected success): $*"
exit 1
fi
}
# check_fail $expected_error $cmd...
#
# Run a command and require that it fails with the given error message.
check_fail()
{
exp="$1"
shift
out=$($TMUX "$@" 2>&1)
if [ $? -eq 0 ]; then
echo "Command succeeded (expected failure): $*"
exit 1
fi
if [ "$out" != "$exp" ]; then
echo "Wrong error for: $*"
echo "Expected: '$exp'"
echo "But got: '$out'"
exit 1
fi
}
# check_windows $session $expected
#
# Compare the window list of a session (as "index:name index:name ...", in
# index order) with $expected.
check_windows()
{
out=$(echo $($TMUX list-windows -t "$1" -F \
'#{window_index}:#{window_name}'))
if [ "$out" != "$2" ]; then
echo "Window list of '$1' wrong."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
# check_fmt $target $format $expected
#
# Expand a format in a target's context and compare with $expected.
check_fmt()
{
out=$($TMUX display-message -p -t "$1" "$2" 2>&1)
if [ "$out" != "$3" ]; then
echo "Format '$2' for '$1' wrong."
echo "Expected: '$3'"
echo "But got: '$out'"
exit 1
fi
}
assert_alive()
{
if [ "$($TMUX display-message -p alive 2>&1)" != "alive" ]; then
echo "Server died: $1"
exit 1
fi
}
# ---------------------------------------------------------------------------
# new-window placement.
check_ok new-session -d -s W -x 80 -y 24 -n w0
# Next free index.
check_ok new-window -d -t W: -n w1
check_ok new-window -d -t W: -n w2
check_windows W '0:w0 1:w1 2:w2'
# Explicit index, then the next new window fills the first free index, not
# one past the highest.
check_ok new-window -d -t W:9 -n w9
check_ok new-window -d -t W: -n w3
check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9'
# Occupied index fails without -k and replaces with -k.
check_fail 'create window failed: index 9 in use' \
new-window -d -t W:9 -n dup
check_ok new-window -d -k -t W:9 -n w9k
check_windows W '0:w0 1:w1 2:w2 3:w3 9:w9k'
# -a inserts after the target, shuffling the following windows up.
check_ok new-window -d -a -t W:1 -n wA
check_windows W '0:w0 1:w1 2:wA 3:w2 4:w3 9:w9k'
# -b inserts before the target, shuffling the target and followers up.
check_ok new-window -d -b -t W:0 -n wB
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
# -S selects an existing window with the same name instead of creating (with
# -d it would not switch, so no -d here).
check_ok select-window -t W:0
check_ok new-window -S -t W: -n w3
check_windows W '0:wB 1:w0 2:w1 3:wA 4:w2 5:w3 9:w9k'
check_fmt 'W:' '#{window_index}:#{window_name}' '5:w3'
# Clean up to a known arrangement.
check_ok kill-window -t W:wB
check_ok kill-window -t W:wA
check_ok kill-window -t W:w9k
check_ok move-window -r -t W:
check_windows W '0:w0 1:w1 2:w2 3:w3'
# ---------------------------------------------------------------------------
# move-window.
# To a free index.
check_ok move-window -d -s W:2 -t W:7
check_windows W '0:w0 1:w1 3:w3 7:w2'
# To an occupied index, without and with -k.
check_fail 'index in use: 7' move-window -d -s W:3 -t W:7
check_ok move-window -d -k -s W:3 -t W:7
check_windows W '0:w0 1:w1 7:w3'
# -a inserts after the target and shuffles.
check_ok move-window -d -a -s W:7 -t W:0
check_windows W '0:w0 1:w3 2:w1'
# -r renumbers in order, respecting base-index.
check_ok move-window -d -s W:2 -t W:8
check_ok set-option -t W base-index 5
check_ok move-window -r -t W:
check_windows W '5:w0 6:w3 7:w1'
check_ok set-option -t W base-index 0
check_ok move-window -r -t W:
check_windows W '0:w0 1:w3 2:w1'
# With the renumber-windows option on, killing a window renumbers the rest
# automatically.
check_ok set-option -t W renumber-windows on
check_ok new-window -d -t W:9 -n wtmp
check_windows W '0:w0 1:w3 2:w1 9:wtmp'
check_ok kill-window -t W:1
check_windows W '0:w0 1:w1 2:wtmp'
check_ok kill-window -t W:2
check_ok set-option -t W renumber-windows off
check_ok new-window -d -t W:2 -n w3
check_windows W '0:w0 1:w1 2:w3'
# Without -s, the current window of the client/session moves.
check_ok select-window -t W:2
check_ok move-window -d -t W:6
check_windows W '0:w0 1:w1 6:w3'
check_ok move-window -r -t W:
check_windows W '0:w0 1:w1 2:w3'
# ---------------------------------------------------------------------------
# link-window and unlink-window.
check_ok new-session -d -s L -x 80 -y 24 -n l0
# Link a window from W into L and check it is shared.
check_ok link-window -d -s W:w1 -t L:5
check_windows L '0:l0 5:w1'
check_fmt 'W:w1' '#{window_linked}' '1'
check_fmt 'L:5' '#{window_linked_sessions}' '2'
# The linked window is the same window: renaming in one session shows in the
# other.
check_ok rename-window -t L:5 shared
check_windows W '0:w0 1:shared 2:w3'
check_ok rename-window -t W:1 w1
# Linking again to an occupied index fails without -k.
check_fail 'index in use: 0' link-window -d -s W:w3 -t L:0
# Unlink removes one link; the window survives in the other session.
check_ok unlink-window -t L:5
check_windows L '0:l0'
check_windows W '0:w0 1:w1 2:w3'
check_fmt 'W:w1' '#{window_linked}' '0'
# Unlinking a window linked to only one session needs -k.
check_fail 'window only linked to one session' unlink-window -t W:w3
check_ok unlink-window -k -t W:w3
check_windows W '0:w0 1:w1'
# ---------------------------------------------------------------------------
# swap-window.
check_ok new-window -d -t W:2 -n w2
check_ok new-window -d -t W:3 -n w3
# Swap within a session: indices are exchanged.
check_ok swap-window -d -s W:0 -t W:3
check_windows W '0:w3 1:w1 2:w2 3:w0'
check_ok swap-window -d -s W:0 -t W:3
check_windows W '0:w0 1:w1 2:w2 3:w3'
# Without -d the current index does not change, so the window that arrives
# there becomes current; with -d the swapped windows are selected, so the
# source window stays current at its new index.
check_ok select-window -t W:0
check_ok swap-window -s W:0 -t W:3
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w3'
check_ok swap-window -s W:3 -t W:0
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0'
check_ok swap-window -d -s W:0 -t W:3
check_fmt 'W:' '#{window_index}:#{window_name}' '3:w0'
check_ok swap-window -d -s W:3 -t W:0
check_fmt 'W:' '#{window_index}:#{window_name}' '0:w0'
# Swap between two different sessions.
check_ok swap-window -d -s W:w2 -t L:l0
check_windows W '0:w0 1:w1 2:l0 3:w3'
check_windows L '0:w2'
check_ok swap-window -d -s W:2 -t L:0
check_windows W '0:w0 1:w1 2:w2 3:w3'
check_windows L '0:l0'
# Swapping between two sessions in the same group is an error.
check_ok new-session -d -s WG -t W
check_fail "can't move window, sessions are grouped" \
swap-window -d -s W:0 -t WG:1
check_ok kill-session -t WG
check_windows W '0:w0 1:w1 2:w2 3:w3'
# ---------------------------------------------------------------------------
# rotate-window.
check_ok new-session -d -s R -x 80 -y 24
check_ok split-window -d -t R:0
check_ok split-window -d -t R:0
p0=$($TMUX display-message -p -t R:0.0 '#{pane_id}')
p1=$($TMUX display-message -p -t R:0.1 '#{pane_id}')
p2=$($TMUX display-message -p -t R:0.2 '#{pane_id}')
# check_panes $target $expected
#
# Compare the pane list of a window (as "index:id ...") with $expected.
check_panes()
{
out=$(echo $($TMUX list-panes -t "$1" -F \
'#{pane_index}:#{pane_id}'))
if [ "$out" != "$2" ]; then
echo "Pane list of '$1' wrong."
echo "Expected: '$2'"
echo "But got: '$out'"
exit 1
fi
}
check_panes R:0 "0:$p0 1:$p1 2:$p2"
# -U rotates panes up (each pane moves to the previous position); -D rotates
# down. -U then -D restores the original order.
check_ok rotate-window -U -t R:0
check_panes R:0 "0:$p1 1:$p2 2:$p0"
check_ok rotate-window -D -t R:0
check_panes R:0 "0:$p0 1:$p1 2:$p2"
check_ok rotate-window -D -t R:0
check_panes R:0 "0:$p2 1:$p0 2:$p1"
check_ok rotate-window -U -t R:0
# The active position is preserved across rotation: the pane that arrives at
# the active position becomes the active pane.
check_ok select-pane -t R:0.0
check_ok rotate-window -U -t R:0
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p1"
check_ok rotate-window -D -t R:0
check_fmt 'R:0' '#{pane_index}:#{pane_id}' "0:$p0"
# ---------------------------------------------------------------------------
# kill-window.
# Killing the current window switches to the last (previously current)
# window.
check_ok select-window -t W:1
check_ok select-window -t W:3
check_fmt 'W:' '#{window_index}' '3'
check_ok kill-window -t W:3
check_fmt 'W:' '#{window_index}' '1'
check_windows W '0:w0 1:w1 2:w2'
# -f is only valid with -a.
check_fail '-f only valid with -a' kill-window -f 'x' -t W:0
# -a kills every window except the target.
check_ok kill-window -a -t W:w1
check_windows W '1:w1'
# ---------------------------------------------------------------------------
# select-window, next-window, previous-window.
# -P prints where the new window went; an invalid (non-UTF-8) name is an
# error.
out=$($TMUX new-window -d -t W:0 -n wa -P -F '#{window_index}:#{window_name}')
if [ "$out" != "0:wa" ]; then
echo "new-window -P output wrong: '$out'"
exit 1
fi
check_fail "invalid window name: $(printf 'a\377b')" \
new-window -d -t W: -n "$(printf 'a\377b')"
check_ok new-window -d -t W:2 -n wc
check_windows W '0:wa 1:w1 2:wc'
# -n and -p select the next and previous window, wrapping at the ends.
check_ok select-window -t W:0
check_ok select-window -n -t W:
check_fmt 'W:' '#{window_index}' '1'
check_ok select-window -n -t W:
check_fmt 'W:' '#{window_index}' '2'
check_ok select-window -n -t W:
check_fmt 'W:' '#{window_index}' '0'
check_ok select-window -p -t W:
check_fmt 'W:' '#{window_index}' '2'
# next-window and previous-window are the same code.
check_ok next-window -t W:
check_fmt 'W:' '#{window_index}' '0'
check_ok previous-window -t W:
check_fmt 'W:' '#{window_index}' '2'
# -l selects the previously current window and select-window -T on the
# already-current window does the same.
check_ok select-window -t W:1
check_ok select-window -l -t W:
check_fmt 'W:' '#{window_index}' '2'
check_ok select-window -T -t W:2
check_fmt 'W:' '#{window_index}' '1'
check_ok select-window -T -t W:2
check_fmt 'W:' '#{window_index}' '2'
# With -a, next-window looks for a window with an alert and fails if there
# is none; a fresh session has no last window.
check_fail 'no next window' next-window -a -t W:
check_fail 'no previous window' previous-window -a -t W:
check_ok new-session -d -s F -x 80 -y 24
check_fail 'no last window' select-window -l -t F:
check_fail 'no last window' select-window -T -t F:0
check_ok kill-session -t F
# ---------------------------------------------------------------------------
# more kill-window -a: no-op, filters and multiply-linked windows.
# -a with a single window in the session does nothing.
check_ok kill-window -a -t L:0
check_windows L '0:l0'
# -a -f only kills other windows matching the filter.
check_ok kill-window -a -f '#{==:#{window_name},wc}' -t W:0
check_windows W '0:wa 1:w1'
# If the current window is linked into the session more than once, -a kills
# it too - taking the whole session with it here.
check_ok new-session -d -s D -x 80 -y 24 -n d0
check_ok link-window -d -s D:0 -t D:5
check_ok new-window -d -t D:1 -n dx
check_ok select-window -t D:0
check_ok kill-window -a -t D:0
if $TMUX has-session -t D 2>/dev/null; then
echo "Session D survived kill-window -a on multiply-linked window."
exit 1
fi
check_fmt 'R:0' '#{window_panes}' '3'
assert_alive
$TMUX kill-server 2>/dev/null
exit 0