diff --git a/regress/choose-tree.sh b/regress/choose-tree.sh index 132890730..4656963e6 100644 --- a/regress/choose-tree.sh +++ b/regress/choose-tree.sh @@ -290,4 +290,92 @@ wait_count ': G4' 7 fail "window 1 of zzz not killed" exit_mode q +# --- help and information preview -------------------------------------------- +# The complete tree help is taller than the 24-line test terminal, where the +# help renderer deliberately leaves the tree unchanged. +$TMUX2 resize-window -t out:0 -x 80 -y 50 || exit 1 +i=0 +while [ "$i" -lt 50 ]; do + # The inner session has a one-line status, leaving a 49-line window. + [ "$($TMUX display-message -p -t aaa:0 '#{window_height}')" -ge 49 ] && + break + sleep 0.2 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "attached client did not resize for tree help" +$TMUX choose-tree -t aaa:0 -F 'G5' -O index || exit 1 +wait_count ': G5' 7 +$TMUX2 send-keys -t out:0 F1 || fail "send-keys F1 failed" +wait_for 'Swap current and previous window' +# The first key closes help; the second exits the tree. +$TMUX send-keys -t aaa:0 q q || fail "send-keys help exit failed" +wait_mode aaa:0 0 + +$TMUX choose-tree -t aaa:0 -F 'G6' -O index || exit 1 +wait_count ': G6' 7 +$TMUX send-keys -t aaa:0 i || fail "send-keys info failed" +wait_for 'Session' +$TMUX send-keys -t aaa:0 i || fail "send-keys preview failed" +exit_mode q + +# --- window swapping and command prompt for current/tagged items ------------- +# Restrict a window-only tree to two disposable windows in aaa. J (the +# Shift-Down equivalent) swaps their window objects while leaving their indexes +# in place. +$TMUX new-window -d -t aaa: -n swap-a 'cat' || exit 1 +$TMUX new-window -d -t aaa: -n swap-b 'cat' || exit 1 +aidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \ + awk -F: '$2 == "swap-a" {print $1}') +bidx=$($TMUX list-windows -t aaa -F '#{window_index}:#{window_name}' | \ + awk -F: '$2 == "swap-b" {print $1}') +$TMUX choose-tree -w -t aaa:0 -O index -F 'WT #{window_name}' \ + -f '#{m:swap-*,#{window_name}}' || exit 1 +wait_count 'WT swap-' 2 +$TMUX send-keys -t aaa:0 g j J || fail "window tree swap failed" +i=0 +while [ "$i" -lt 50 ]; do + aname=$($TMUX display-message -p -t "aaa:$aidx" '#{window_name}') + bname=$($TMUX display-message -p -t "aaa:$bidx" '#{window_name}') + [ "$aname:$bname" = 'swap-b:swap-a' ] && break + sleep 0.2 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "window tree did not swap the two windows" + +# ':' runs an entered command for the current item and rebuilds the tree when +# its queued command completes. +$TMUX set-option -g @tree-command '' || exit 1 +$TMUX send-keys -t aaa:0 : || fail "window tree command prompt failed" +wait_for '(current)' +$TMUX send-keys -t aaa:0 -l "set-option -g @tree-command '%%'" || exit 1 +$TMUX send-keys -t aaa:0 Enter || exit 1 +i=0 +while [ "$i" -lt 50 ]; do + value=$($TMUX show-option -gqv @tree-command) + [ -n "$value" ] && break + sleep 0.2 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "window tree entered command did not run" +case "$value" in +=aaa:*.) ;; +*) fail "window tree command target was '$value'" ;; +esac + +# Tag both disposable windows and kill them through the tagged confirmation +# callback. The mode-hosting window is outside the filter and survives. +$TMUX send-keys -t aaa:0 g j t j t X || fail "tagged kill keys failed" +wait_for 'Kill 2 tagged?' +$TMUX send-keys -t aaa:0 y || fail "tagged kill confirmation failed" +i=0 +while [ "$i" -lt 50 ]; do + left=$($TMUX list-windows -t aaa -F '#{window_name}' | \ + grep -c '^swap-' || true) + [ "$left" -eq 0 ] && break + sleep 0.2 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "tagged windows were not killed" +exit_mode q + exit 0 diff --git a/regress/clock-mode.sh b/regress/clock-mode.sh new file mode 100644 index 000000000..846e8dc7e --- /dev/null +++ b/regress/clock-mode.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +# Exercise clock-mode initialization, all four styles, its one-second timer, +# resize into the compact renderer, arbitrary-key exit, and cleanup. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +INNER="$TEST_TMUX -LtestI$$ -f/dev/null" +OUTER="$TEST_TMUX -LtestO$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $OUTER kill-server 2>/dev/null + $INNER kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +capture() +{ + $OUTER capture-pane -p -t outer:0.0 2>/dev/null +} + +wait_mode() +{ + want=$1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($INNER display-message -p -t clock:0 '#{pane_mode}' 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "pane mode is '$got', expected '$want'" +} + +wait_hashes() +{ + i=0 + while [ "$i" -lt 50 ]; do + captured=$(capture) + printf '%s\n' "$captured" | grep -q '#' && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "large clock did not render" +} + +$INNER new-session -d -s clock -x80 -y24 'exec sleep 100' || exit 1 +$INNER set-option -g status off || exit 1 +$INNER set-option -g window-size manual || exit 1 +$INNER set-option -w clock-mode-colour red || exit 1 +$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t clock" || exit 1 +$OUTER set-option -g status off || exit 1 +$OUTER set-option -g window-size manual || exit 1 +sleep 1 + +for style in 12 24 12-with-seconds 24-with-seconds; do + $INNER set-option -w -t clock:0 clock-mode-style "$style" || exit 1 + $INNER clock-mode -t clock:0 || exit 1 + wait_mode clock-mode + wait_hashes + # Let at least one timer callback observe a new second. + [ "$style" != 12-with-seconds ] || sleep 1.2 + $INNER send-keys -t clock:0 x || exit 1 + wait_mode '' +done + +# Resizing an active mode below the large-glyph threshold selects the compact +# renderer. A visible time contains a colon and no block-clock hash. +$INNER clock-mode -t clock:0 || exit 1 +wait_mode clock-mode +$INNER resize-window -t clock:0 -x20 -y5 || exit 1 +i=0 +while [ "$i" -lt 50 ]; do + captured=$(capture) + printf '%s\n' "$captured" | grep -Eq '[0-9][0-9]?:[0-9][0-9]' && break + sleep 0.1 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "compact clock did not render after resize" +printf '%s\n' "$captured" | grep -q '#' && + fail "compact clock still used large glyphs" +$INNER send-keys -t clock:0 Enter || exit 1 +wait_mode '' + +$INNER has-session -t clock || fail "server died during clock-mode tests" +exit 0 diff --git a/regress/confirm-before-lock.sh b/regress/confirm-before-lock.sh new file mode 100644 index 000000000..db570ab10 --- /dev/null +++ b/regress/confirm-before-lock.sh @@ -0,0 +1,150 @@ +#!/bin/sh + +# Exercise confirm-before's waiting and background callbacks, custom keys and +# prompts, default-yes handling and errors. Also cover all three lock command +# entry points with a harmless lock-command which leaves a marker. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +INNER="$TEST_TMUX -LtestI$$ -f/dev/null" +OUTER="$TEST_TMUX -LtestO$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $OUTER kill-server 2>/dev/null + $INNER kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +capture() +{ + $OUTER capture-pane -p -t outer:0.0 2>/dev/null +} + +wait_capture() +{ + marker=$1 + i=0 + while [ "$i" -lt 50 ]; do + captured=$(capture) + printf '%s\n' "$captured" | grep -Fq "$marker" && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "timed out waiting for '$marker'" +} + +wait_option() +{ + want=$1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($INNER show-option -gqv @confirmed 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "@confirmed is '$got', expected '$want'" +} + +wait_file() +{ + marker=$1 + i=0 + while [ "$i" -lt 50 ]; do + [ -f "$marker" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "lock command did not create $marker" +} + +$INNER new-session -d -s test -x80 -y24 'exec sleep 100' || exit 1 +$INNER set-option -g status on || exit 1 +$INNER set-option -g status-position bottom || exit 1 +$INNER set-option -g window-size manual || exit 1 +$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t test" || exit 1 +$OUTER set-option -g status off || exit 1 +$OUTER set-option -g window-size manual || exit 1 +sleep 1 + +client=$($INNER list-clients -F '#{client_name}') +[ -n "$client" ] || fail "inner client did not attach" + +$INNER bind-key -n M-a confirm-before \ + 'set-option -g @confirmed accepted' || exit 1 +$INNER bind-key -n M-b confirm-before -b -p 'Background?' \ + 'set-option -g @confirmed background' || exit 1 +$INNER bind-key -n M-c confirm-before -c x -p 'Custom?' \ + 'set-option -g @confirmed custom' || exit 1 +$INNER bind-key -n M-y confirm-before -y -p 'Default?' \ + 'set-option -g @confirmed default' || exit 1 + +# A non-confirming response closes the waiting prompt without running its +# command; reopening and accepting inserts the command after the waiting item. +$INNER set-option -g @confirmed sentinel || exit 1 +$OUTER send-keys M-a || exit 1 +wait_capture "Confirm 'set-option'? (y/n)" +$OUTER send-keys n || exit 1 +sleep 0.2 +wait_option sentinel +$OUTER send-keys M-a || exit 1 +wait_capture "Confirm 'set-option'? (y/n)" +$OUTER send-keys y || exit 1 +wait_option accepted + +# -b has no waiting queue item and appends its command to the client queue. +$OUTER send-keys M-b || exit 1 +wait_capture 'Background?' +$OUTER send-keys y || exit 1 +wait_option background + +# A custom printable key is accepted, and -y makes Enter affirmative. +$OUTER send-keys M-c || exit 1 +wait_capture 'Custom?' +$OUTER send-keys x || exit 1 +wait_option custom +$OUTER send-keys M-y || exit 1 +wait_capture 'Default?' +$OUTER send-keys Enter || exit 1 +wait_option default + +# Reject invalid multi-character and control confirmation keys. +if $INNER confirm-before -b -t "$client" -c xx 'display-message x' \ + >/dev/null 2>&1; then + fail "multi-character confirm key was accepted" +fi +if $INNER confirm-before -b -t "$client" -c "$(printf '\001')" \ + 'display-message x' >/dev/null 2>&1; then + fail "control confirm key was accepted" +fi + +# lock-client, lock-session and lock-server all send lock-command to the real +# attached client. The command returns immediately and unlocks the client. +for kind in client session server; do + marker="$DIR/locked-$kind" + $INNER set-option -t test lock-command "printf locked >$marker" || exit 1 + case "$kind" in + client) $INNER lock-client -t "$client" || exit 1 ;; + session) $INNER lock-session -t test || exit 1 ;; + server) $INNER lock-server || exit 1 ;; + esac + wait_file "$marker" +done + +$INNER has-session -t test || fail "server died during prompt or lock tests" +exit 0 diff --git a/regress/copy-mode-commands.sh b/regress/copy-mode-commands.sh new file mode 100644 index 000000000..aa94e9ef2 --- /dev/null +++ b/regress/copy-mode-commands.sh @@ -0,0 +1,253 @@ +#!/bin/sh + +# Exercise the broad non-mouse copy-mode command surface: navigation, search, +# jumps, state toggles, line/end-of-line copies, append, and pipe variants. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +x() +{ + $TMUX send-keys -t copy:0 -X "$@" || fail "copy command failed: $*" +} + +wait_mode() +{ + want=$1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($TMUX display-message -p -t copy:0 '#{pane_in_mode}' 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "pane_in_mode is '$got', expected '$want'" +} + +fresh() +{ + $TMUX copy-mode -t copy:0 || exit 1 + wait_mode 1 + x history-top + x start-of-line +} + +select_text() +{ + fresh + x begin-selection + x cursor-right + x cursor-right +} + +$TMUX new-session -d -s copy -x40 -y8 \ + "sh -c 'i=0; while [ \$i -lt 40 ]; do printf \" line-%02d needle (a[b]c) end\\n\" \$i; i=\$((i + 1)); done; printf \"\\nparagraph two needle\\n\\nparagraph three\\n\"; printf \"\\033]133;A\\007prompt-one\\n\\033]133;C\\007output-one\\n\\033]133;A\\007prompt-two\\n\\033]133;C\\007output-two\\n\"; exec sleep 100'" || + exit 1 +$TMUX set-option -g status off || exit 1 +$TMUX set-option -g history-limit 200 || exit 1 + +i=0 +while [ "$i" -lt 50 ]; do + history=$($TMUX display-message -p -t copy:0 '#{history_size}') + [ "$history" -ge 30 ] && break + sleep 0.1 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "history did not fill" + +fresh + +# Copy-mode-specific format callbacks expose the word, line, hyperlink and +# active search match at the cursor. +formats=$($TMUX display-message -p -t copy:0 \ + '#{copy_cursor_word}|#{copy_cursor_line}|#{copy_cursor_hyperlink}|#{search_match}') || + exit 1 +[ -n "$formats" ] || fail "copy-mode cursor formats were empty" + +# Cursor, viewport and paragraph commands. Each is asserted by successful +# dispatch and the final mode-liveness check; detailed word/selection geometry +# is covered by the existing copy-mode tests. +for command in back-to-indentation bottom-line top-line middle-line \ + cursor-centre-vertical cursor-centre-horizontal end-of-line history-bottom \ + history-top halfpage-down halfpage-up page-down page-up next-paragraph \ + previous-paragraph next-matching-bracket previous-matching-bracket; do + x "$command" +done +x goto-line 15 +x set-mark +x cursor-down +x jump-to-mark + +# OSC 133 prompt and output markers support shell-integration navigation. +x history-top +x next-prompt +case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in +*prompt-one*) ;; +*) fail "next-prompt did not find prompt-one" ;; +esac +x next-prompt +x previous-prompt +case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in +*prompt-one*) ;; +*) fail "previous-prompt did not return to prompt-one" ;; +esac +x next-prompt -o +case "$($TMUX display-message -p -t copy:0 '#{copy_cursor_line}')" in +*output-one*) ;; +*) fail "next-prompt -o did not find output-one" ;; +esac + +# Character jumps remember their direction and character for repeat/reverse. +x jump-forward e +x jump-again +x jump-reverse +x jump-backward e +x jump-to-forward e +x jump-to-backward e + +# Direct, text and incremental searches cover both directions and the saved +# search used by again/reverse. +x search-forward needle +x search-again +x search-reverse +x search-backward needle +x search-forward-text needle +x search-backward-text needle +x search-forward-incremental '=needle' +x search-forward-incremental '+needle' +x search-backward-incremental '=needle' +x search-backward-incremental '-needle' + +# Stateful display and behaviour toggles. +for command in rectangle-on rectangle-off rectangle-toggle \ + line-numbers-on line-numbers-off line-numbers-toggle \ + refresh-on refresh-now refresh-off refresh-toggle \ + scroll-exit-on scroll-exit-off scroll-exit-toggle toggle-position; do + x "$command" +done +wait_mode 1 +x cancel +wait_mode 0 + +# Line and end-of-line copy commands work without an explicit selection. +fresh +x select-line +x copy-selection-and-cancel +case "$($TMUX show-buffer)" in +*line-00*) ;; +*) fail "select-line did not select the current line" ;; +esac + +fresh +x copy-line +[ -n "$($TMUX show-buffer)" ] || fail "copy-line produced an empty buffer" +x cancel + +fresh +x copy-end-of-line +[ -n "$($TMUX show-buffer)" ] || fail "copy-end-of-line produced an empty buffer" +x cancel + +# Append variants retain or close the mode as advertised. +$TMUX set-buffer -b append-buffer prefix || exit 1 +select_text +x append-selection +appended=$($TMUX show-buffer -b append-buffer) +case "$appended" in +prefix*) ;; +*) fail "append-selection produced '$appended'" ;; +esac +x cancel + +$TMUX set-buffer -b append-cancel-buffer prefix || exit 1 +select_text +x append-selection-and-cancel +wait_mode 0 +appended=$($TMUX show-buffer -b append-cancel-buffer) +case "$appended" in +prefix*) ;; +*) fail "append-selection-and-cancel produced '$appended'" ;; +esac + +# The explicit cancel variants close copy mode. +fresh +x copy-line-and-cancel +wait_mode 0 +fresh +x copy-end-of-line-and-cancel +wait_mode 0 +select_text +x copy-selection-and-cancel +wait_mode 0 + +# Pipe wrappers have distinct command handlers. /dev/null keeps the test +# deterministic while exercising asynchronous pipe job creation and cleanup. +fresh +x copy-pipe-line 'cat >/dev/null' +x cancel +fresh +x copy-pipe-line-and-cancel 'cat >/dev/null' +wait_mode 0 +fresh +x copy-pipe-end-of-line 'cat >/dev/null' +x cancel +fresh +x copy-pipe-end-of-line-and-cancel 'cat >/dev/null' +wait_mode 0 + +select_text +x copy-pipe-no-clear 'cat >/dev/null' +x pipe-no-clear 'cat >/dev/null' +x copy-pipe 'cat >/dev/null' +x pipe 'cat >/dev/null' +x cancel +select_text +x copy-pipe-and-cancel 'cat >/dev/null' +wait_mode 0 +select_text +x pipe-and-cancel 'cat >/dev/null' +wait_mode 0 + +# Commands which cancel on downward movement have separate handlers. +fresh +x history-bottom +x bottom-line +x cursor-down-and-cancel +wait_mode 0 +fresh +x history-bottom +x halfpage-down-and-cancel +wait_mode 0 +fresh +x history-bottom +x page-down-and-cancel +wait_mode 0 +fresh +x history-bottom +x bottom-line +x scroll-down-and-cancel +wait_mode 0 + +$TMUX has-session -t copy || fail "server died during copy-mode commands" +exit 0 diff --git a/regress/find-window.sh b/regress/find-window.sh new file mode 100644 index 000000000..820a4777d --- /dev/null +++ b/regress/find-window.sh @@ -0,0 +1,118 @@ +#!/bin/sh + +# Exercise find-window's content, name and title filters in every combination, +# glob/regex and case-insensitive suffixes, plus zoom restoration. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +INNER="$TEST_TMUX -LtestI$$ -f/dev/null" +OUTER="$TEST_TMUX -LtestO$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $OUTER kill-server 2>/dev/null + $INNER kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +capture() +{ + $OUTER capture-pane -p -t outer:0.0 2>/dev/null +} + +wait_mode() +{ + want=$1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($INNER display-message -p -t find:base.0 '#{pane_mode}' 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "pane mode is '$got', expected '$want'" +} + +run_find() +{ + label=$1 + expected=$2 + shift 2 + $INNER find-window -t find:base.0 "$@" || fail "$label failed" + wait_mode tree-mode + i=0 + while [ "$i" -lt 50 ]; do + captured=$(capture) + printf '%s\n' "$captured" | grep -Fq "$expected" && break + sleep 0.1 + i=$((i + 1)) + done + [ "$i" -lt 50 ] || fail "$label did not show '$expected'" + $INNER send-keys -t find:base.0 q || exit 1 + wait_mode '' +} + +$INNER new-session -d -s find -n base -x80 -y24 \ + "sh -c 'printf \"BaseBody\\n\"; exec sleep 100'" || exit 1 +$INNER split-window -d -h -t find:base \ + "sh -c 'printf \"OtherBody\\n\"; exec sleep 100'" || exit 1 +$INNER new-window -d -t find: -n NameNeedle 'exec sleep 100' || exit 1 +$INNER new-window -d -t find: -n body \ + "sh -c 'printf \"BodyNeedle\\n\"; exec sleep 100'" || exit 1 +$INNER new-window -d -t find: -n titled 'exec sleep 100' || exit 1 +$INNER select-pane -t find:titled.0 -T TitleNeedle || exit 1 +$INNER set-option -g status off || exit 1 +$INNER set-option -g window-size manual || exit 1 +$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t find:base" || exit 1 +$OUTER set-option -g status off || exit 1 +$OUTER set-option -g window-size manual || exit 1 +sleep 1 + +# Wait until the body text is in the pane history before searching it. +i=0 +while [ "$i" -lt 50 ]; do + $INNER capture-pane -p -t find:body.0 | grep -Fq BodyNeedle && break + sleep 0.1 + i=$((i + 1)) +done +[ "$i" -lt 50 ] || fail "pane content was not ready" + +# No flags means C+N+T. Then cover all three pairs and all three singletons. +run_find default NameNeedle NameNeedle +run_find content-name NameNeedle -C -N NameNeedle +run_find content-title titled -C -T TitleNeedle +run_find name-title titled -N -T TitleNeedle +run_find content-only body -C BodyNeedle +run_find name-only NameNeedle -N NameNeedle +run_find title-only titled -T TitleNeedle + +# Glob case folding and all regular-expression suffix variants. +run_find insensitive NameNeedle -N -i nameneedle +run_find regex NameNeedle -N -r '^NameNeedle$' +run_find regex-insensitive NameNeedle -N -r -i '^nameneedle$' + +# -Z temporarily zooms the target window and restores its prior state on exit. +$INNER find-window -Z -t find:base.0 -N NameNeedle || exit 1 +wait_mode tree-mode +[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 1 ] || + fail "find-window -Z did not zoom" +$INNER send-keys -t find:base.0 q || exit 1 +wait_mode '' +[ "$($INNER display-message -p -t find:base '#{window_zoomed_flag}')" = 0 ] || + fail "find-window -Z did not restore zoom" + +exit 0 diff --git a/regress/layout-set.sh b/regress/layout-set.sh new file mode 100644 index 000000000..ea02195a9 --- /dev/null +++ b/regress/layout-set.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +# Exercise every predefined layout, next/previous cycling, lookup and restore +# paths, one-pane early returns, minimum-size handling and floating panes. + +PATH=/bin:/usr/bin +TERM=screen +export PATH TERM + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" +SEEN="$DIR/layouts" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $TMUX kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +layout() +{ + $TMUX display-message -p -t layouts:many '#{window_layout}' +} + +check_layout() +{ + name=$1 + $TMUX select-layout -t layouts:many "$name" || + fail "select-layout $name failed" + value=$(layout) + [ -n "$value" ] || fail "$name produced an empty layout" + if grep -Fxq "$value" "$SEEN"; then + fail "$name produced the same layout as an earlier preset" + fi + printf '%s\n' "$value" >>"$SEEN" + [ "$($TMUX list-panes -t layouts:many -F x | wc -l)" -eq 5 ] || + fail "$name lost a pane" + [ "$($TMUX list-panes -t layouts:many -F '#{pane_floating_flag}' | \ + grep -c '^1$')" -eq 1 ] || fail "$name did not preserve the floating pane" +} + +$TMUX new-session -d -s layouts -n many -x100 -y40 'exec sleep 100' || exit 1 +$TMUX set-option -g window-size manual || exit 1 +$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1 +$TMUX split-window -d -v -t layouts:many 'exec sleep 100' || exit 1 +$TMUX split-window -d -h -t layouts:many 'exec sleep 100' || exit 1 +floating=$($TMUX new-pane -dP -F '#{pane_id}' -t layouts:many \ + -x20 -y8 -X10 -Y5 'exec sleep 100') || exit 1 +: >"$SEEN" + +# Options exercise percentage and explicit secondary-size calculations. +$TMUX set-option -w -t layouts:many main-pane-height 30% || exit 1 +$TMUX set-option -w -t layouts:many other-pane-height 12 || exit 1 +$TMUX set-option -w -t layouts:many main-pane-width 35% || exit 1 +$TMUX set-option -w -t layouts:many other-pane-width 20 || exit 1 + +for name in even-horizontal even-vertical main-horizontal \ + main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do + check_layout "$name" +done + +# Exact and unique-prefix lookup work; ambiguous and invalid names fail. +$TMUX select-layout -t layouts:many even-h || fail "unique layout prefix failed" +$TMUX select-layout -t layouts:many main >/dev/null 2>&1 && + fail "ambiguous layout prefix succeeded" +$TMUX select-layout -t layouts:many no-such-layout >/dev/null 2>&1 && + fail "invalid layout name succeeded" + +# Cycle through all layouts in both directions, including wraparound. The +# aliases and select-layout flags share layout_set_next/previous. +first=$(layout) +i=0 +while [ "$i" -lt 7 ]; do + $TMUX next-layout -t layouts:many || exit 1 + i=$((i + 1)) +done +[ "$(layout)" = "$first" ] || fail "next-layout did not wrap" +i=0 +while [ "$i" -lt 7 ]; do + $TMUX previous-layout -t layouts:many || exit 1 + i=$((i + 1)) +done +[ "$(layout)" = "$first" ] || fail "previous-layout did not wrap" +$TMUX select-layout -t layouts:many -n || exit 1 +$TMUX select-layout -t layouts:many -p || exit 1 + +# No argument reapplies the last preset; -o restores the previous layout; -E +# spreads the current cell without changing the pane set. +$TMUX kill-pane -t "$floating" || exit 1 +$TMUX select-layout -t layouts:many tiled || exit 1 +tiled=$(layout) +$TMUX select-layout -t layouts:many even-horizontal || exit 1 +$TMUX select-layout -t layouts:many -o || exit 1 +[ "$(layout)" = "$tiled" ] || fail "select-layout -o did not restore layout" +$TMUX select-layout -t layouts:many || exit 1 +$TMUX select-layout -t layouts:many -E || exit 1 + +# Each arranger has a deliberate one-pane early return. +$TMUX new-window -d -t layouts: -n one 'exec sleep 100' || exit 1 +one=$($TMUX display-message -p -t layouts:one '#{window_layout}') +for name in even-horizontal even-vertical main-horizontal \ + main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do + $TMUX select-layout -t layouts:one "$name" || exit 1 + [ "$($TMUX display-message -p -t layouts:one '#{window_layout}')" = "$one" ] || + fail "$name changed a one-pane layout" +done + +# Force the minimum-size paths without requiring a terminal client. +$TMUX resize-window -t layouts:many -x10 -y6 || exit 1 +for name in even-horizontal even-vertical main-horizontal \ + main-horizontal-mirrored main-vertical main-vertical-mirrored tiled; do + $TMUX select-layout -t layouts:many "$name" || exit 1 +done + +$TMUX has-session -t layouts || fail "server died during layout tests" +exit 0 diff --git a/regress/list-commands.sh b/regress/list-commands.sh new file mode 100644 index 000000000..9e1463bea --- /dev/null +++ b/regress/list-commands.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +# Exercise list-commands for the full table, a single command, aliases, custom +# and empty formats, and lookup errors. + +PATH=/bin:/usr/bin +TERM=screen +export PATH TERM + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +TMUX="$TEST_TMUX -Ltest$$ -f/dev/null" + +cleanup() +{ + $TMUX kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +# Keep a server alive across commands. Otherwise a STARTSERVER-only command +# may exit between invocations and leave a short socket-cleanup race. +$TMUX new-session -d -s list-commands 'exec sleep 100' || exit 1 + +all=$($TMUX list-commands) || exit 1 +printf '%s\n' "$all" | grep -q '^attach-session (attach)' || exit 1 +printf '%s\n' "$all" | grep -q '^list-commands (lscm)' || exit 1 + +one=$($TMUX list-commands -F \ + '#{command_list_name}|#{command_list_alias}|#{command_list_usage}' \ + list-commands) || exit 1 +case "$one" in +'list-commands|lscm|'*) ;; +*) exit 1 ;; +esac + +# Aliases resolve to their command, while an empty expansion prints no line. +[ "$($TMUX list-commands -F '#{command_list_name}' lscm)" = list-commands ] || + exit 1 +[ -z "$($TMUX list-commands -F '' list-commands)" ] || exit 1 + +$TMUX list-commands tmux-no-command-$$ >/dev/null 2>&1 && exit 1 +$TMUX list-commands list >/dev/null 2>&1 && exit 1 +exit 0 diff --git a/regress/prompt-words-history.sh b/regress/prompt-words-history.sh new file mode 100644 index 000000000..6aaa5689c --- /dev/null +++ b/regress/prompt-words-history.sh @@ -0,0 +1,165 @@ +#!/bin/sh + +# Cover prompt word movement in emacs and vi modes, ambiguous inline command +# completion, and the show/clear-prompt-history command surface. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export PATH TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +DIR=$(mktemp -d) || exit 1 +TMUX_TMPDIR=$DIR +export TMUX_TMPDIR +INNER="$TEST_TMUX -LtestI$$ -f/dev/null" +OUTER="$TEST_TMUX -LtestO$$ -f/dev/null" + +fail() +{ + echo "$*" >&2 + exit 1 +} + +cleanup() +{ + $OUTER kill-server 2>/dev/null + $INNER kill-server 2>/dev/null + rm -rf "$DIR" +} +trap cleanup 0 1 15 + +capture() +{ + $OUTER capture-pane -p -t outer:0.0 2>/dev/null +} + +wait_result() +{ + want=$1 + i=0 + while [ "$i" -lt 50 ]; do + got=$($INNER show-option -gqv @result 2>/dev/null) + [ "$got" = "$want" ] && return 0 + sleep 0.1 + i=$((i + 1)) + done + fail "prompt result is '$got', expected '$want'" +} + +bind_prompt() +{ + initial=$1 + $INNER bind-key -n M-r command-prompt -I "$initial" -p '(word)' \ + "set-option -g @result '%%'" || exit 1 +} + +run_prompt() +{ + want=$1 + shift + $INNER set-option -g @result sentinel || exit 1 + $OUTER send-keys M-r || exit 1 + sleep 0.2 + $OUTER send-keys "$@" || exit 1 + $OUTER send-keys Enter || exit 1 + wait_result "$want" +} + +run_vi_prompt() +{ + want=$1 + shift + $INNER set-option -g @result sentinel || exit 1 + $OUTER send-keys M-r || exit 1 + sleep 0.2 + # Send Escape separately so the terminal's escape-time handling does not + # combine it with the first vi command as a Meta key. + $OUTER send-keys Escape || exit 1 + # The inner client must see Escape as a complete key, not the prefix of a + # Meta sequence (the default escape-time is 500 milliseconds). + sleep 0.7 + $OUTER send-keys "$@" || exit 1 + $OUTER send-keys Enter || exit 1 + wait_result "$want" +} + +$INNER new-session -d -s prompt -x80 -y24 'exec sleep 100' || exit 1 +$INNER set-option -g status on || exit 1 +$INNER set-option -g status-position bottom || exit 1 +$INNER set-option -g window-size manual || exit 1 +$OUTER new-session -d -s outer -x80 -y24 "$INNER attach -t prompt" || exit 1 +$OUTER set-option -g status off || exit 1 +$OUTER set-option -g window-size manual || exit 1 +sleep 1 + +# Emacs Meta-f stops after the first word; Meta-b returns to the start of the +# previous word. Inserting a marker makes the cursor position observable. +$INNER set-option -g status-keys emacs || exit 1 +bind_prompt 'one two' +run_prompt 'oneX two' Home M-f X +bind_prompt 'one two' +run_prompt 'one Xtwo' M-b X + +# Vi translation and the distinct separator-aware and WORD motions. +$INNER set-option -g status-keys vi || exit 1 +bind_prompt 'one-two three' +run_vi_prompt 'one-two Xthree' b i X +bind_prompt 'one-two three' +run_vi_prompt 'one-two Xthree' B i X +bind_prompt 'one-two three' +run_vi_prompt 'oneX-two three' 0 w i X +bind_prompt 'one-two three' +run_vi_prompt 'one-two Xthree' 0 W i X +bind_prompt 'one-two three' +run_vi_prompt 'oneX-two three' 0 e a X +bind_prompt 'one-two three' +run_vi_prompt 'one-twoX three' 0 E a X + +# "show-" has several command matches and no longer common prefix. Tab keeps +# the input and draws the sorted candidates inline. +$INNER set-option -g status-keys emacs || exit 1 +bind_prompt '' +$OUTER send-keys M-r || exit 1 +sleep 0.2 +$OUTER send-keys -l 'show-' || exit 1 +$OUTER send-keys Tab || exit 1 +sleep 0.2 +captured=$(capture) +printf '%s\n' "$captured" | grep -Fq 'show-buffer' || + fail "ambiguous completion list was not drawn" +printf '%s\n' "$captured" | grep -Fq 'show-environment' || + fail "ambiguous completion list was incomplete" +$OUTER send-keys Escape || exit 1 + +# Add entries to both history rings through real prompts. +bind_prompt 'history-command' +run_prompt 'history-command' +$INNER bind-key -n M-s command-prompt -T search -I history-search \ + -p '(search-history)' "set-option -g @result '%%'" || exit 1 +$OUTER send-keys M-s || exit 1 +$OUTER send-keys Enter || exit 1 +wait_result history-search + +command_history=$($INNER show-prompt-history -T command) || exit 1 +printf '%s\n' "$command_history" | grep -Fq 'history-command' || + fail "command history entry missing" +search_history=$($INNER show-prompt-history -T search) || exit 1 +printf '%s\n' "$search_history" | grep -Fq 'history-search' || + fail "search history entry missing" +all_history=$($INNER show-prompt-history) || exit 1 +printf '%s\n' "$all_history" | grep -Fq 'History for command:' || exit 1 +printf '%s\n' "$all_history" | grep -Fq 'History for search:' || exit 1 + +$INNER show-prompt-history -T invalid >/dev/null 2>&1 && + fail "invalid show history type succeeded" +$INNER clear-prompt-history -T invalid >/dev/null 2>&1 && + fail "invalid clear history type succeeded" +$INNER clear-prompt-history -T command || exit 1 +$INNER show-prompt-history -T command | grep -Fq history-command && + fail "type-specific history clear failed" +$INNER clear-prompt-history || exit 1 +$INNER show-prompt-history | grep -Fq history-search && + fail "all-history clear failed" + +exit 0