diff --git a/regress/prompt-keys.sh b/regress/prompt-keys.sh new file mode 100644 index 000000000..7fa7a673b --- /dev/null +++ b/regress/prompt-keys.sh @@ -0,0 +1,300 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT + +fail() { + echo "[FAIL] $1" + exit 1 +} + +# Capture the outer pane: what the inner client rendered. No -e, so we match +# plain visible text, not styles or escape sequences. +capture() { + $OUT capture-pane -p +} + +# The "(search) ..." prompt row of a mode prompt. +search_row() { + capture | grep '(search)' | head -1 +} + +# The inner status line is the last row of the outer capture (status is at the +# bottom). Used to assert a prompt is in the pane and not on the status line. +status_line() { + capture | tail -1 +} + +# Inner mode must still be a tree mode (the prompt did not close or crash it). +in_tree_mode() { + [ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] +} + +# Small settle for the key -> inner server -> inner client -> outer pane round +# trip to redraw before we capture. Matches the short waits other regress tests +# use; we do not depend on exact timing beyond the redraw completing. +settle() { + sleep 0.5 +} + +# Assert the mode search prompt currently shows exactly "(search) ". +search_is() { + want=$1; msg=$2 + search_row | grep -qF "(search) $want" || \ + fail "$msg (wanted '(search) $want', got '$(search_row)')" +} + +# --- Inner session under test. --------------------------------------------- +# +# Two windows so the tree has content; status on so we can distinguish the pane +# from the status line; fixed size and manual sizing so the layout is stable. +# A root-table key opens a status-line command prompt whose accept action writes +# the final buffer into @r, so we can recover it exactly. +$IN new -d -x80 -y24 "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-position bottom || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 +$IN new-window -d "sh -c 'exec sleep 1000'" || exit 1 +$IN bind -n M-r command-prompt -p ">" "set -g @r '%%'" || exit 1 + +# --- Outer session: attach the inner one inside its pane. ------------------- +$OUT new -d -x80 -y24 || exit 1 +$OUT set -g status off || exit 1 +$OUT set -g window-size manual || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +# =========================================================================== +# Mode prompt (choose-tree search): the thorough engine vehicle. +# =========================================================================== + +$IN choose-tree || exit 1 +settle +in_tree_mode || fail "choose-tree did not enter tree-mode" + +# --- 1. Search prompt is drawn in the pane, not on the status line. --- +$IN send-keys C-s || exit 1 +settle +search_row | grep -q '(search)' || fail "search prompt not drawn in the pane" +status_line | grep -q '(search)' && \ + fail "search prompt drawn on the status line, not in the pane" + +# --- 2. emacs editing: insert and delete at middle, start and end. --- +# Cursor position is checked behaviourally: move, insert a marker, read the row. +# This needs no cursor coordinates and fails if a movement/edit key is wrong. + +# Middle insert: "abcd", Left Left (cursor between b and c), insert X -> abXcd. +$IN send-keys -l "abcd" || exit 1 +settle +search_is "abcd" "literal input not shown in search prompt" +$IN send-keys Left Left || exit 1 +$IN send-keys -l "X" || exit 1 +settle +search_is "abXcd" "middle insert wrong (Left/insert)" + +# Middle delete: BSpace removes X (before cursor), DC removes c (at cursor). +$IN send-keys BSpace || exit 1 +$IN send-keys DC || exit 1 +settle +search_is "abd" "middle delete wrong (BSpace/Delete)" + +# Clear, then start/end insert with C-a and C-e. +$IN send-keys C-u || exit 1 +$IN send-keys -l "mno" || exit 1 +$IN send-keys C-a || exit 1 +$IN send-keys -l "S" || exit 1 +$IN send-keys C-e || exit 1 +$IN send-keys -l "E" || exit 1 +settle +search_is "SmnoE" "C-a/C-e start/end insert wrong" + +# Word kill: "hello world", C-w removes the last word leaving "hello " (with the +# separating space). capture-pane trims trailing spaces, so make the space +# visible by inserting a marker after it: the buffer becomes "hello Z". +$IN send-keys C-u || exit 1 +$IN send-keys -l "hello world" || exit 1 +$IN send-keys C-w || exit 1 +$IN send-keys -l "Z" || exit 1 +settle +search_is "hello Z" "C-w did not kill a word" + +# C-a then C-k kills the whole line. +$IN send-keys C-a || exit 1 +$IN send-keys C-k || exit 1 +settle +search_row | grep -q '(search) [^ ]' && fail "C-a C-k did not clear the line" + +# --- 3. Editing kept the prompt open the whole time. --- +in_tree_mode || fail "editing keys closed the mode" +search_row | grep -q '(search)' || fail "editing keys closed the prompt" + +# --- 4. Unicode wide character: insert, render, delete as one unit. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "a中b" || exit 1 +settle +search_is "a中b" "wide character not shown" +# Left moves over "b" (one column); BSpace deletes the wide "中" as a single +# width-2 unit, leaving "ab". +$IN send-keys Left || exit 1 +$IN send-keys BSpace || exit 1 +settle +search_is "ab" "wide character not deleted as one unit" + +# --- 5. Control character: quote-next inserts it literally, shown as ^G. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "a" || exit 1 +$IN send-keys C-v || exit 1 # quote next key +$IN send-keys C-g || exit 1 # literal BEL -> displayed as ^G +$IN send-keys -l "b" || exit 1 +settle +search_is "a^Gb" "control character not shown as ^G" +# Deleted as a single unit too. +$IN send-keys Left || exit 1 +$IN send-keys BSpace || exit 1 +settle +search_is "ab" "control character not deleted as one unit" + +# --- 6. Kill and yank: C-w fills the yank buffer, C-y pastes it at the cursor. --- +# (prompt_key only fills the yank buffer from C-w; C-y then yanks that text, or +# the top paste buffer if nothing has been killed. Establish our own kill here +# so the result is deterministic.) +$IN send-keys C-u || exit 1 +$IN send-keys -l "one two" || exit 1 +$IN send-keys C-w || exit 1 # kill "two", buffer "one " +$IN send-keys C-y || exit 1 # yank it back -> "one two" +settle +search_is "one two" "C-y did not yank the killed text" +$IN send-keys C-y || exit 1 # yank again at cursor -> "one twotwo" +settle +search_is "one twotwo" "second C-y did not yank again" + +# --- 7. History: accept a string, reopen, Up recalls it. --- +$IN send-keys C-u || exit 1 +$IN send-keys -l "alpha" || exit 1 +$IN send-keys Enter || exit 1 +settle +$IN send-keys C-s || exit 1 +settle +$IN send-keys Up || exit 1 +settle +search_is "alpha" "history (Up) did not recall the previous entry" + +# --- 8. Escape closes the prompt but leaves the mode open. --- +$IN send-keys Escape || exit 1 +settle +search_row | grep -q '(search)' && fail "Escape left a dangling search prompt" +in_tree_mode || fail "Escape closed the mode as well as the prompt" + +# Leave the mode. +$IN send-keys q || exit 1 +settle + +# =========================================================================== +# Status-line prompt (command-prompt): the same engine on the status line. +# Keys go through the inner client's terminal (outer send-keys); the accepted +# buffer is recovered exactly via %% -> @r. +# =========================================================================== + +# --- 10. Prompt is drawn on the status line (the last row). --- +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +status_line | grep -q '>' || fail "status-line prompt not drawn on the status line" + +# --- 11. emacs cursor-marker edit, accept recovers the exact buffer. --- +$OUT send-keys -l "abc" || exit 1 +$OUT send-keys Home || exit 1 +$OUT send-keys -l "X" || exit 1 +settle +status_line | grep -qF "> Xabc" || \ + fail "status-line edit wrong (got '$(status_line)')" +$OUT send-keys Enter || exit 1 +settle +[ "$($IN show -gv @r)" = "Xabc" ] || \ + fail "status-line accept recovered '$($IN show -gv @r)', wanted 'Xabc'" + +# --- 12. Unicode on the status line: insert, move, delete wide char. --- +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "a㋡b" || exit 1 +settle +status_line | grep -qF "a㋡b" || \ + fail "status-line wide character not shown (got '$(status_line)')" +# Home, insert Z (start); End, BSpace (delete b); BSpace (delete wide char). +$OUT send-keys Home || exit 1 +$OUT send-keys -l "Z" || exit 1 +$OUT send-keys End || exit 1 +$OUT send-keys BSpace || exit 1 +$OUT send-keys BSpace || exit 1 +settle +$OUT send-keys Enter || exit 1 +settle +[ "$($IN show -gv @r)" = "Za" ] || \ + fail "status-line wide edit recovered '$($IN show -gv @r)', wanted 'Za'" + +# --- 13. Overflow: more text than fits stays within the line and is kept. --- +big="0123456789012345678901234567890123456789012345678901234567890123456789ABCDEFGHIJ" +$IN set -g @r "" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "$big" || exit 1 +settle +# The drawn status line must not exceed the client width (80): no wrap, no crash. +width=$(status_line | awk '{print length($0)}') +[ "$width" -le 80 ] || fail "overflowing prompt drew $width columns, wider than 80" +$OUT send-keys Enter || exit 1 +settle +# The whole buffer was kept despite only part being visible. +[ "$($IN show -gv @r)" = "$big" ] || fail "overflowing prompt lost buffer content" + +# --- 14. Escape closes the status-line prompt cleanly. --- +$IN set -g @r "SENTINEL" || exit 1 +$OUT send-keys M-r || exit 1 +settle +$OUT send-keys -l "discard" || exit 1 +$OUT send-keys Escape || exit 1 +settle +status_line | grep -q '> discard' && fail "Escape left a dangling status-line prompt" +[ "$($IN show -gv @r)" = "SENTINEL" ] || fail "Escape ran the prompt's accept action" + +# =========================================================================== +# Two clients attached to the same window: a mode prompt must render on both. +# =========================================================================== + +$OUT new-window || exit 1 +$OUT set -g status off || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +$IN choose-tree || exit 1 +settle +$IN send-keys C-s || exit 1 +settle +$IN send-keys -l "dual" || exit 1 +settle +for w in $($OUT list-windows -F '#{window_index}'); do + $OUT capture-pane -t ":$w" -p | grep -qF "(search) dual" || \ + fail "mode prompt not shown on client in outer window $w" +done +$IN send-keys Escape || exit 1 +settle + +# --- Inner tmux is still alive and responsive. --- +$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died" + +exit 0 diff --git a/regress/prompt-mechanics.sh b/regress/prompt-mechanics.sh new file mode 100644 index 000000000..6f3e5294e --- /dev/null +++ b/regress/prompt-mechanics.sh @@ -0,0 +1,336 @@ +#!/bin/sh + +# Exercise the prompt mechanics shared by all three host paths of the prompt +# engine in prompt.c: +# +# status.c status_prompt_set - the status-line command prompt. +# window.c window_pane_set_prompt - a prompt drawn over a pane (-P). +# mode-tree.c mode_tree_set_prompt - search/filter prompts in tree modes. +# +# prompt-keys.sh covers the editing keys; this test covers that each path OPENS +# and DRAWS in the right place and that the prompt flags select the right engine +# behaviour: -1 (single), -N (numeric), -i (incremental), -k (key), -e +# (backspace exit), -I (prefill), multi prompts, type-scoped history and command +# completion. + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +LANG=C.UTF-8 +export TERM LC_ALL LANG + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" # outer (host for the client) +IN="$TEST_TMUX -Ltest2 -f/dev/null" # inner (under test) + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" EXIT + +fail() { + echo "[FAIL] $1" + exit 1 +} + +# Capture the outer pane: what the inner client rendered. Pane-area prompts +# (over a pane or in a tree mode) and the status-line prompt all end up here. +capture() { + $OUT capture-pane -p +} + +# The inner status line is the last row of the outer capture. +status_line() { + capture | tail -1 +} + +# The recovered buffer. Every prompt below accepts into the @r option so the +# exact final string can be checked; reset it to a sentinel first so we can tell +# "accept ran" from "prompt cancelled". +got() { + $IN show -gv @r +} +reset() { + $IN set -g @r "SENTINEL" || exit 1 +} + +# Settle for the key -> inner server -> inner client -> outer pane redraw round +# trip, as in prompt-keys.sh. +settle() { + sleep 0.5 +} + +# --- Inner session under test. --------------------------------------------- +# +# The window is created at -y23 so that, with a one-row bottom status, the pane +# area (23) exactly fills the 24-row client: a pane prompt drawn on the pane's +# bottom row lands on visible row 23, just above the status line on row 24. A +# second, distinctively named window gives the tree something to filter. +$IN new -d -x80 -y23 -n YAK "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-position bottom || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 +$IN new-window -d -n ZEBRA "sh -c 'exec sleep 1000'" || exit 1 + +# One root-table key per path/flag. The accept template records the final +# buffer in @r (or both buffers, for the multi-prompt case). +$IN bind -n M-s command-prompt -p '(stat)' "set -g @r '%%'" || exit 1 +$IN bind -n M-p command-prompt -P -p '(pane)' "set -g @r '%%'" || exit 1 +$IN bind -n M-o command-prompt -P -1 -p '(one)' "set -g @r '%%'" || exit 1 +$IN bind -n M-n command-prompt -P -N -I 5 -p '(num)' "set -g @r '%%'" || exit 1 +$IN bind -n M-i command-prompt -P -i -p '(inc)' "set -g @r '%%'" || exit 1 +$IN bind -n M-k command-prompt -P -k -p '(key)' "set -g @r '%%'" || exit 1 +$IN bind -n M-e command-prompt -P -e -p '(bs)' "set -g @r '%%'" || exit 1 +$IN bind -n M-j command-prompt -P -I hello -p '(pre)' "set -g @r '%%'" || exit 1 +$IN bind -n M-m command-prompt -p 'first,second' "set -g @r '%1/%2'" || exit 1 +$IN bind -n M-c command-prompt -p '(cmd)' "set -g @r '%%'" || exit 1 +$IN bind -n M-h command-prompt -T search -p '(srch)' "set -g @r '%%'" || exit 1 + +# --- Outer session: attach the inner one inside its pane. ------------------- +$OUT new -d -x80 -y24 || exit 1 +$OUT set -g status off || exit 1 +$OUT set -g window-size manual || exit 1 +$OUT send-keys -l "$IN attach" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +# =========================================================================== +# 1. Each path opens and draws in the right place. +# =========================================================================== + +# --- 1a. status.c: drawn on the status line (the last row). --- +reset +$OUT send-keys M-s || exit 1 +settle +status_line | grep -qF '(stat)' || \ + fail "status-line prompt not on the status line (got '$(status_line)')" +$OUT send-keys -l "go" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "go" ] || fail "status-line accept recovered '$(got)', wanted 'go'" + +# --- 1b. window.c: drawn over the pane, not on the status line. --- +reset +$OUT send-keys M-p || exit 1 +settle +capture | grep -qF '(pane)' || fail "pane prompt not drawn in the pane" +status_line | grep -qF '(pane)' && \ + fail "pane prompt drawn on the status line, not over the pane" +$OUT send-keys -l "deep" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "deep" ] || fail "pane prompt accept recovered '$(got)', wanted 'deep'" + +# --- 1c. mode-tree.c: search prompt drawn in the pane. --- +$IN choose-tree || exit 1 +settle +[ "$($IN display-message -p '#{pane_mode}')" = "tree-mode" ] || \ + fail "choose-tree did not enter tree-mode" +$IN send-keys C-s || exit 1 +settle +capture | grep -qF '(search)' || fail "mode-tree search prompt not drawn in the pane" +status_line | grep -qF '(search)' && \ + fail "mode-tree search prompt drawn on the status line" +$IN send-keys Escape || exit 1 +settle + +# --- 1d. mode-tree.c: filter prompt opens, applies, prefills, and clears. --- +$IN send-keys f || exit 1 +settle +capture | grep -qF '(filter)' || fail "mode-tree filter prompt not drawn" +$IN send-keys -l "ZEBRA" || exit 1 +$IN send-keys Enter || exit 1 +settle +capture | grep -q 'filter: active' || fail "accepting the filter did not apply it" +# Reopening the filter prompt prefills it with the current filter. +$IN send-keys f || exit 1 +settle +capture | grep -qF '(filter) ZEBRA' || \ + fail "filter prompt not prefilled with the current filter" +$IN send-keys Escape || exit 1 +settle +# 'c' clears the filter. +$IN send-keys c || exit 1 +settle +capture | grep -q 'filter: active' && fail "'c' did not clear the filter" +$IN send-keys q || exit 1 +settle + +# =========================================================================== +# 2. Flags select the right engine behaviour. +# =========================================================================== + +# --- 2a. -1 (PROMPT_SINGLE): one keystroke closes and accepts that char. --- +reset +$OUT send-keys M-o || exit 1 +settle +capture | grep -qF '(one)' || fail "single prompt did not open" +$OUT send-keys -l "q" || exit 1 +settle +[ "$(got)" = "q" ] || fail "single prompt recovered '$(got)', wanted 'q'" +capture | grep -qF '(one)' && fail "single prompt stayed open after one key" + +# --- 2b. -N (PROMPT_NUMERIC): prefilled, digits append, non-digit closes. --- +reset +$OUT send-keys M-n || exit 1 +settle +capture | grep -qF '(num) 5' || fail "numeric prompt not prefilled with 5" +$OUT send-keys -l "7" || exit 1 # 57 +$OUT send-keys Enter || exit 1 # Enter is a non-digit: close +settle +[ "$(got)" = "57" ] || fail "numeric accept recovered '$(got)', wanted '57'" +# A non-digit key closes the prompt with the existing buffer, dropping the key. +reset +$OUT send-keys M-n || exit 1 +settle +$OUT send-keys -l "x" || exit 1 +settle +[ "$(got)" = "5" ] || fail "numeric non-digit close recovered '$(got)', wanted '5'" +capture | grep -qF '(num)' && fail "numeric prompt stayed open after a non-digit" + +# --- 2c. -i (PROMPT_INCREMENTAL): callback fires on every edit, stays open. --- +# The incremental code path prefixes the buffer with '=' (or +/-), so the '=' +# proves the value came through the incremental callback, not a plain accept. +reset +$OUT send-keys M-i || exit 1 +settle +[ "$(got)" = "=" ] || fail "incremental prompt did not fire on open (got '$(got)')" +$OUT send-keys -l "a" || exit 1 +settle +[ "$(got)" = "=a" ] || fail "incremental did not fire after 'a' (got '$(got)')" +$OUT send-keys -l "b" || exit 1 +settle +[ "$(got)" = "=ab" ] || fail "incremental did not fire after 'b' (got '$(got)')" +capture | grep -qF '(inc)' || fail "incremental prompt closed during editing" +$OUT send-keys Escape || exit 1 +settle +capture | grep -qF '(inc)' && fail "Escape did not close the incremental prompt" + +# --- 2d. -k (PROMPT_KEY): the next key closes and delivers its name. --- +reset +$OUT send-keys M-k || exit 1 +settle +capture | grep -qF '(key)' || fail "key prompt did not open" +$OUT send-keys -l "z" || exit 1 +settle +[ "$(got)" = "z" ] || fail "key prompt recovered '$(got)', wanted 'z'" +capture | grep -qF '(key)' && fail "key prompt stayed open after a key" + +# --- 2e. -e (PROMPT_BSPACE_EXIT): backspace on empty cancels (no accept). --- +reset +$OUT send-keys M-e || exit 1 +settle +$OUT send-keys BSpace || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "backspace-exit ran the accept action (got '$(got)')" +capture | grep -qF '(bs)' && fail "backspace on empty did not close the prompt" + +# --- 2f. -I (prefill): prompt opens with the given buffer. --- +reset +$OUT send-keys M-j || exit 1 +settle +capture | grep -qF '(pre) hello' || fail "prefill not shown (got '$(capture | grep -F '(pre)')')" +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "hello" ] || fail "prefill accept recovered '$(got)', wanted 'hello'" + +# --- 2g. Multi prompt: accept advances to the next, both are delivered. --- +reset +$OUT send-keys M-m || exit 1 +settle +capture | grep -qF 'first' || fail "first of multi prompt not shown" +$OUT send-keys -l "X" || exit 1 +$OUT send-keys Enter || exit 1 +settle +capture | grep -qF 'second' || fail "multi prompt did not advance to the second" +$OUT send-keys -l "Y" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "X/Y" ] || fail "multi prompt recovered '$(got)', wanted 'X/Y'" + +# =========================================================================== +# 3. Type-scoped history and command completion. +# =========================================================================== + +# --- 3a. Command history is recalled with Up; search history is separate. --- +reset +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys -l "alpha" || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$(got)" = "alpha" ] || fail "command prompt accept recovered '$(got)'" +# Reopen the command prompt: Up recalls the command-type entry. +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys Up || exit 1 +settle +capture | grep -qF '(cmd) alpha' || fail "Up did not recall command history" +$OUT send-keys Escape || exit 1 +settle +# A search-type prompt must NOT recall the command-type entry (separate rings). +$OUT send-keys M-h || exit 1 +settle +$OUT send-keys Up || exit 1 +settle +capture | grep -qF 'alpha' && fail "search prompt recalled command-type history" +$OUT send-keys Escape || exit 1 +settle + +# --- 3b. Tab completion works in a command prompt (command type only). --- +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys -l "new-w" || exit 1 +$OUT send-keys Tab || exit 1 +settle +status_line | grep -qF 'new-window' || \ + fail "Tab did not complete new-w to new-window (got '$(status_line)')" +$OUT send-keys Escape || exit 1 +settle +# A search-type prompt does not complete commands: Tab is literal / inert. +$OUT send-keys M-h || exit 1 +settle +$OUT send-keys -l "new-w" || exit 1 +$OUT send-keys Tab || exit 1 +settle +status_line | grep -qF 'new-window' && fail "search prompt completed a command" +$OUT send-keys Escape || exit 1 +settle + +# =========================================================================== +# 4. Robustness: re-entrancy guard and Escape, then liveness. +# =========================================================================== + +# --- 4a. A second prompt while one is open is refused (status path). --- +client=$($IN list-clients -F '#{client_name}' | head -1) +reset +$OUT send-keys M-s || exit 1 +settle +$OUT send-keys -l "AAA" || exit 1 +settle +$IN command-prompt -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null +settle +capture | grep -qF '(re)' && fail "a second status prompt opened over the first" +status_line | grep -qF '(stat) AAA' || fail "first status prompt was disturbed" +$OUT send-keys Escape || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "Escape ran the status prompt accept action" + +# --- 4b. A second pane prompt while one is open is refused (pane path). --- +reset +$OUT send-keys M-p || exit 1 +settle +$OUT send-keys -l "BBB" || exit 1 +settle +$IN command-prompt -P -t"$client" -p '(re)' "set -g @r 'REENTERED'" 2>/dev/null +settle +capture | grep -qF '(re)' && fail "a second pane prompt opened over the first" +capture | grep -qF '(pane) BBB' || fail "first pane prompt was disturbed" +$OUT send-keys Escape || exit 1 +settle +[ "$(got)" = "SENTINEL" ] || fail "Escape ran the pane prompt accept action" + +# --- 4c. Inner tmux survived every path and flag. --- +$IN display-message -p '#{version}' >/dev/null 2>&1 || fail "inner tmux died" + +exit 0