diff --git a/regress/cmd-client-argv.sh b/regress/cmd-client-argv.sh new file mode 100755 index 000000000..193f8f8c0 --- /dev/null +++ b/regress/cmd-client-argv.sh @@ -0,0 +1,50 @@ +#!/bin/sh + +# Client argv parsing and the start-server boundary. +# +# The command line given to the client is parsed and invoked as one sequence. +# This checks: a multi-command argv runs every command and starts the server +# (new-session carries CMD_STARTSERVER); a command without start-server fails +# cleanly against no server; and a parse error leaves no stray server behind. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +# Multi-command argv: starts the server and runs all commands in order. +$TMUX new-session -d -swork -nbase \; set -g @a one \; new-window -dn W2 \; \ + set -g @b two || exit 1 +[ "$($TMUX show -gv @a)" = one ] || { echo "argv cmd 2 did not run" >&2; exit 1; } +[ "$($TMUX show -gv @b)" = two ] || { echo "argv cmd 4 did not run" >&2; exit 1; } +got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',') +[ "$got" = "base,W2," ] || { echo "argv windows: got [$got]" >&2; exit 1; } +# kill-server is asynchronous; wait for the server to actually exit before the +# checks below that require no server to be running. +$TMUX kill-server 2>/dev/null +i=0 +while $TMUX has-session 2>/dev/null; do + sleep 0.05 + i=$((i + 1)) + [ $i -gt 100 ] && break +done + +# A command without start-server fails cleanly when no server is running and +# does not fork one. +$TMUX new-window -dn ZZ 2>/dev/null && { echo "new-window unexpectedly succeeded" >&2; exit 1; } +if $TMUX has-session 2>/dev/null; then + echo "non-start-server command left a stray server" >&2 + exit 1 +fi + +# A parse error with no server running starts no server. +$TMUX 'if-shell true {' 2>/dev/null && { echo "parse error did not fail" >&2; exit 1; } +if $TMUX has-session 2>/dev/null; then + echo "parse error left a stray server" >&2 + exit 1 +fi + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-command-alias.sh b/regress/cmd-command-alias.sh new file mode 100755 index 000000000..fbf01656f --- /dev/null +++ b/regress/cmd-command-alias.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +# command-alias expansion, against a running server and at server start. +# +# An alias replaces a command name with parsed command text; arguments after the +# alias name are appended to the last command of the expansion. This covers a +# single-command alias, a multi-command alias, argument appending, a built-in +# default alias, and aliases that are defined and used as the server starts +# (both from the startup config and from the client command line that starts +# the server). + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +CONF=$(mktemp) +trap "rm -f $CONF" 0 1 15 + +wait_gone() { + i=0 + while $TMUX has-session 2>/dev/null; do + sleep 0.05 + i=$((i + 1)) + [ $i -gt 100 ] && break + done +} + +# --- Against a running server. --------------------------------------------- +$TMUX -f/dev/null start \; new-session -d -swork -nbase 2>/dev/null || exit 1 + +# Single-command alias. +$TMUX split-window -dt work || exit 1 +$TMUX set -s command-alias[200] 'zoomit=resize-pane -Z' || exit 1 +$TMUX zoomit -t work:.0 || exit 1 +[ "$($TMUX display-message -p -t work '#{window_zoomed_flag}')" = 1 ] || { + echo "single-command alias did not zoom" >&2; exit 1; } + +# Multi-command alias: both commands run. +$TMUX set -s command-alias[201] 'twowin=new-window -dn AA ; new-window -dn BB' || exit 1 +$TMUX run-shell -C 'twowin' || exit 1 +got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',') +[ "$got" = "base,AA,BB," ] || { echo "multi-command alias: got [$got]" >&2; exit 1; } + +# Arguments after the alias name are appended to the expansion. +$TMUX set -s command-alias[202] 'namewin=new-window -d -n' || exit 1 +$TMUX run-shell -C 'namewin CC' || exit 1 +$TMUX list-windows -t work -F '#{window_name}' | grep -qx CC || { + echo "alias argument append did not create window CC" >&2; exit 1; } + +# A built-in default alias (splitp -> split-window). +before=$($TMUX list-panes -t work | wc -l) +$TMUX run-shell -C 'splitp -d -t work' || exit 1 +after=$($TMUX list-panes -t work | wc -l) +[ "$after" -gt "$before" ] || { echo "built-in alias splitp did not split" >&2; exit 1; } + +$TMUX kill-server 2>/dev/null +wait_gone + +# --- At server start: alias defined and used in the startup config. -------- +cat <<'EOF' >$CONF +set -s command-alias[100] greet='set -g @g started' +set -s command-alias[101] mkwins='new-window -dn SW1 ; new-window -dn SW2' +new-session -d -swork -nbase +greet +mkwins +EOF +$TMUX -f$CONF start 2>/dev/null || exit 1 +[ "$($TMUX show -gv @g)" = started ] || { echo "startup-config alias did not run" >&2; exit 1; } +got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',') +[ "$got" = "base,SW1,SW2," ] || { echo "startup-config multi alias: got [$got]" >&2; exit 1; } + +$TMUX kill-server 2>/dev/null +wait_gone + +# --- At server start: alias from config, used on the command line that starts +# the server. --------------------------------------------------------- +cat <<'EOF' >$CONF +set -s command-alias[100] greet='set -g @g argv' +EOF +$TMUX -f$CONF new-session -d -swork \; greet 2>/dev/null || exit 1 +[ "$($TMUX show -gv @g)" = argv ] || { echo "argv-start alias did not run" >&2; exit 1; } + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-invoke-deferred.sh b/regress/cmd-invoke-deferred.sh new file mode 100755 index 000000000..6eaf24c35 --- /dev/null +++ b/regress/cmd-invoke-deferred.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +# Deferred command callbacks. +# +# Several commands store a parsed tree and invoke it later through +# cmd_invoke_get: if-shell (then/else, sync and background), run-shell -C, and - +# once a key is pressed by an attached client - a key binding, confirm-before and +# command-prompt. The headless cases run directly on the inner server; the +# client cases use the nested-tmux pattern from prompt-mechanics.sh: an outer +# server hosts the inner client in a pane and keys are injected with send-keys. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" +IN="$TEST_TMUX -Ltest2 -f/dev/null" + +$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" 0 1 15 + +fail() { echo "[FAIL] $1" >&2; exit 1; } +settle() { sleep 0.5; } + +CONF=$(mktemp) +trap "rm -f $CONF; $OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15 + +# --- Headless deferred callbacks: no client needed. ------------------------ +$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 + +$IN if-shell true 'set -g @t then' 'set -g @t else' || exit 1 +[ "$($IN show -gv @t)" = then ] || fail "if-shell true ran wrong branch" +$IN if-shell false 'set -g @f then' 'set -g @f else' || exit 1 +[ "$($IN show -gv @f)" = else ] || fail "if-shell false ran wrong branch" + +# Background (-b) if-shell evaluates its condition in a shell asynchronously. +$IN if-shell -b 'true' 'set -g @b yes' 'set -g @b no' || exit 1 +settle +[ "$($IN show -gv @b)" = yes ] || fail "background if-shell ran wrong branch" + +# A multi-command then-body runs both commands. +$IN if-shell true 'new-window -dn IF1 ; new-window -dn IF2' '' || exit 1 +$IN list-windows -F '#{window_name}' | grep -qx IF1 || fail "if-shell body cmd1 missing" +$IN list-windows -F '#{window_name}' | grep -qx IF2 || fail "if-shell body cmd2 missing" + +# run-shell -C invokes its argument as tmux commands. +$IN run-shell -C 'set -g @rc ran' || exit 1 +[ "$($IN show -gv @rc)" = ran ] || fail "run-shell -C did not run command" + +# --- Client deferred callbacks: key binding, confirm-before, command-prompt. - +$IN bind -n M-c confirm-before -p '(ok) ' 'set -g @cb confirmed' || exit 1 +$IN bind -n M-d command-prompt -I pre -p '(cmd) ' 'set -g @cp %%' || exit 1 +# A brace body must come through the lexer, so bind it from a config. +cat <<'EOF' >$CONF +bind -n M-k { new-window -dn K1 ; new-window -dn K2 } +EOF +$IN source-file $CONF || exit 1 + +$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 + +# Key binding with a stored multi-command body fires on keypress. +$OUT send-keys M-k || exit 1 +settle +$IN list-windows -F '#{window_name}' | grep -qx K1 || fail "key binding body cmd1 missing" +$IN list-windows -F '#{window_name}' | grep -qx K2 || fail "key binding body cmd2 missing" + +# confirm-before runs its command only when confirmed. +$IN set -g @cb none || exit 1 +$OUT send-keys M-c || exit 1 +settle +$OUT capture-pane -p | tail -1 | grep -qF '(ok)' || fail "confirm-before prompt not shown" +$OUT send-keys n || exit 1 +settle +[ "$($IN show -gv @cb)" = none ] || fail "confirm-before ran command after 'n'" +$OUT send-keys M-c || exit 1 +settle +$OUT send-keys y || exit 1 +settle +[ "$($IN show -gv @cb)" = confirmed ] || fail "confirm-before did not run command after 'y'" + +# command-prompt feeds the typed line (with -I prefill) into its template. +$IN set -g @cp none || exit 1 +$OUT send-keys M-d || exit 1 +settle +$OUT capture-pane -p | tail -1 | grep -qF '(cmd)' || fail "command-prompt not shown" +$OUT send-keys -l X || exit 1 +$OUT send-keys Enter || exit 1 +settle +[ "$($IN show -gv @cp)" = preX ] || fail "command-prompt recovered '$($IN show -gv @cp)'" + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-invoke-expand.sh b/regress/cmd-invoke-expand.sh new file mode 100755 index 000000000..4ad188a1b --- /dev/null +++ b/regress/cmd-invoke-expand.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +# Invoke-time expansion and conditionals. +# +# The parser builds syntax only; environment/tilde expansion, assignments and +# %if/%elif/%else are all evaluated when the tree is invoked. This drives every +# one of those through a single config and checks the resulting option values: +# - FOO=bar assignments set the environment, read back with ${FOO} and $FOO +# - %hidden assignments behave the same +# - ~ expands to the home directory +# - %if / %elif / %else selects the correct branch + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +CONF=$(mktemp) +trap "rm -f $CONF" 0 1 15 + +cat <<'EOF' >$CONF +FOO=hello +set -g @assign "${FOO}" +set -g @dollar "$FOO" +%hidden BAR=secret +set -g @hidden "${BAR}" +set -g @undef "x${NOSUCHVAR_ZZZ}y" +set -g @tilde ~ +set -g @baduser ~nosuchuser_zzz9 +%if 1 +set -g @iftrue then +%else +set -g @iftrue else +%endif +%if 0 +set -g @iffalse then +%else +set -g @iffalse else +%endif +%if 0 +set -g @elif a +%elif 1 +set -g @elif b +%else +set -g @elif c +%endif +EOF + +$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1 +$TMUX source-file $CONF || exit 1 + +check() { + got=$($TMUX show -gv "$1") + [ "$got" = "$2" ] || { echo "$1: got [$got] expected [$2]" >&2; exit 1; } +} +check @assign hello +check @dollar hello +check @hidden secret +check @undef xy # undefined environment variable expands to nothing +check @baduser "" # unknown user in ~user expands to nothing +check @iftrue then +check @iffalse else +check @elif b + +# ~ expands to an absolute home directory (value is machine dependent). +tilde=$($TMUX show -gv @tilde) +case "$tilde" in +/*) ;; +*) echo "@tilde did not expand to an absolute path: [$tilde]" >&2; exit 1;; +esac + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-invoke-failure-scope.sh b/regress/cmd-invoke-failure-scope.sh new file mode 100755 index 000000000..1e2c88c35 --- /dev/null +++ b/regress/cmd-invoke-failure-scope.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +# Command failure scope. +# +# Failure scope is the active sequence (CMD_PARSE_SEQUENCE): commands joined by +# ';' share one scope, so a failure skips the rest of that sequence; commands on +# separate lines are independent sequences and are not skipped. After a failed +# inner brace the enclosing sequence still resumes. +# +# Each case below creates windows around a deliberately invalid command and the +# resulting window list shows exactly which commands ran. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +TMP=$(mktemp) +CONF=$(mktemp) +trap "rm -f $TMP $CONF" 0 1 15 + +# kill-server is asynchronous, so wait for the old server to actually exit +# before starting a fresh one (otherwise the next start races a dying server). +fresh_server() { + $TMUX kill-server 2>/dev/null + i=0 + while $TMUX list-sessions >/dev/null 2>&1; do + sleep 0.05 + i=$((i + 1)) + [ $i -gt 100 ] && break + done + $TMUX -f/dev/null start \; new-session -d -swork -nbase || exit 1 +} + +# $1 label, $2 config body, $3 expected comma-separated window list. +run() { + fresh_server + printf '%s\n' "$2" >$CONF + $TMUX source-file $CONF >/dev/null 2>&1 + got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',') + if [ "$got" != "$3" ]; then + echo "$1: got [$got] expected [$3]" >&2 + exit 1 + fi +} + +# ';' sequence in a brace body: A runs, the bad command skips B (same sequence), +# the outer sequence resumes so C runs. +run "semicolon body" 'if-shell true { new-window -dn A ; nonexistent_cmd ; new-window -dn B } +new-window -dn C' 'base,A,C,' + +# Newlines are independent sequences: the bad command skips only itself, so both +# B and C still run. +run "newline body" 'if-shell true { + new-window -dn A + nonexistent_cmd + new-window -dn B +} +new-window -dn C' 'base,A,B,C,' + +# Top-level ';' sequence: failure skips the rest of the line. +run "top-level semicolon" 'new-window -dn A ; nonexistent_cmd ; new-window -dn B' 'base,A,' + +# Top-level newlines: independent, both run. +run "top-level newline" 'new-window -dn A +nonexistent_cmd +new-window -dn B' 'base,A,B,' + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-invoke-hooks.sh b/regress/cmd-invoke-hooks.sh new file mode 100755 index 000000000..461355125 --- /dev/null +++ b/regress/cmd-invoke-hooks.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +# Hooks run stored command trees. +# +# A hook stores a parsed command tree that is invoked through cmd_invoke_get when +# the hook fires. Covers a single-command hook, a multi-entry hook array (every +# entry fires), and that show-hooks prints the stored commands back. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +$TMUX -f/dev/null start \; new-session -d -sfirst -nbase 2>/dev/null || exit 1 + +# Single-command hook fires on session creation. +$TMUX set-hook -g session-created 'set -g @hook done' || exit 1 +$TMUX new-session -d -ssecond || exit 1 +[ "$($TMUX show -gv @hook)" = done ] || { + echo "single-command hook did not fire" >&2 + exit 1 +} + +# A hook array fires every entry. +$TMUX set-hook -g session-created[10] 'new-window -dn H1' || exit 1 +$TMUX set-hook -g session-created[11] 'set -g @hook2 two' || exit 1 +$TMUX new-session -d -sthird || exit 1 +[ "$($TMUX show -gv @hook2)" = two ] || { + echo "hook array entry 11 did not fire" >&2 + exit 1 +} +echo "$($TMUX list-windows -t third -F '#{window_name}')" | grep -qx H1 || { + echo "hook array entry 10 did not create window" >&2 + exit 1 +} + +# show-hooks prints the stored hook commands. +$TMUX show-hooks -g | grep -q 'session-created.* set -g @hook done' || { + echo "show-hooks did not print stored hook command" >&2 + exit 1 +} + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-invoke-readonly.sh b/regress/cmd-invoke-readonly.sh new file mode 100755 index 000000000..7a843e587 --- /dev/null +++ b/regress/cmd-invoke-readonly.sh @@ -0,0 +1,56 @@ +#!/bin/sh + +# Read-only client enforcement. +# +# When the invoking client is read-only, cmd_invoke builds each command and +# rejects it (without running it) unless the command entry is marked read-only. +# A read-only client is created by attaching with -r; the nested-tmux pattern +# from cmd-invoke-deferred.sh is used so a real read-only client presses a key. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +OUT="$TEST_TMUX -Ltest -f/dev/null" +IN="$TEST_TMUX -Ltest2 -f/dev/null" + +$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" 0 1 15 + +fail() { echo "[FAIL] $1" >&2; exit 1; } +settle() { sleep 0.5; } + +# Inner session: a key bound to a non-read-only command. +$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1 +$IN set -g status on || exit 1 +$IN set -g status-keys emacs || exit 1 +$IN set -g window-size manual || exit 1 +$IN bind -n M-w new-window -dn ROTRY || exit 1 + +# Outer session hosts the inner client, attached read-only with -r. +$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 -r" || exit 1 +$OUT send-keys Enter || exit 1 +sleep 1 + +[ "$($IN list-clients -F '#{client_readonly}')" = 1 ] || fail "client is not read-only" + +# Pressing the key runs new-window through the read-only client: it must be +# rejected, leave no window, and report the error. +$OUT send-keys M-w || exit 1 +settle +$IN list-windows -F '#{window_name}' | grep -qx ROTRY && \ + fail "read-only client was allowed to create a window" +$OUT capture-pane -p | grep -qi 'read-only' || fail "no read-only error was shown" + +# Positive control: the same command with no client is not read-only and runs, +# proving the command itself is valid and only the read-only client blocked it. +$IN new-window -dn OKWIN || exit 1 +$IN list-windows -F '#{window_name}' | grep -qx OKWIN || fail "control new-window did not run" + +$OUT kill-server 2>/dev/null +$IN kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-parse-errors.sh b/regress/cmd-parse-errors.sh new file mode 100755 index 000000000..e3e4e5a80 --- /dev/null +++ b/regress/cmd-parse-errors.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# Malformed input rejection and crash/leak canary. +# +# Each input below is a syntax error. For every one we require that: +# - parsing reports failure (non-zero exit), and +# - the server is still alive afterwards (a follow-up command succeeds). +# The second check is the important one: a parser that crashes or corrupts state +# on bad input would take the server down here. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1 + +n=0 +check() { + n=$((n + 1)) + # Expect a parse failure. + if $TMUX run-shell -C "$1" >/dev/null 2>&1; then + echo "case $n: expected failure but succeeded: $1" >&2 + exit 1 + fi + # Expect the server to still be responsive. + if ! $TMUX list-sessions >/dev/null 2>&1; then + echo "case $n: server died after: $1" >&2 + exit 1 + fi +} + +check 'if-shell true {' # unterminated brace body +check 'display-message a }' # stray close brace +check 'if-shell true { display-message a' # unclosed nested brace +check 'if-shell true { ; }' # empty sequence in body +check '%elif 1' # %elif with no %if +check '%endif' # %endif with no %if +check '%zzz' # unknown % directive +check '; display-message a' # leading separator +input='%if 1 +display-message a' +check "$input" # unterminated %if +input='%if 1 +%endif' +check "$input" # empty %if body + +$TMUX kill-server 2>/dev/null +exit 0 diff --git a/regress/cmd-parse-print.sh b/regress/cmd-parse-print.sh new file mode 100755 index 000000000..af1525e2c --- /dev/null +++ b/regress/cmd-parse-print.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +# Parse/print golden suite for the command parser. +# +# Binds a spread of command syntax forms into a dedicated key table, then prints +# them back with list-keys (which calls cmd_parse_print on the stored tree). A +# command-valued option is printed too. The normalized output is compared byte +# for byte against the expected block below, locking in the current parse/print +# behaviour: quoting, separators, braced and nested bodies, and preservation of +# unexpanded ${env}, ~ and #{format} syntax inside stored bodies. + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -Ltest -f/dev/null" +$TMUX kill-server 2>/dev/null + +TMP=$(mktemp) +CONF=$(mktemp) +EXP=$(mktemp) +trap "rm -f $TMP $CONF $EXP" 0 1 15 + +cat <<'EOF' >$CONF +bind -T parsetest a display-message hello +bind -T parsetest b display-message "hello world" +bind -T parsetest c display-message 'literal $HOME #{p} ~' +bind -T parsetest d display-message "" +bind -T parsetest e display-message "#{pane_id}" +bind -T parsetest f display-message a \; display-message b +bind -T parsetest g { + display-message one + display-message two +} +bind -T parsetest h if-shell true { display-message yes } { display-message no } +bind -T parsetest m if-shell true { display-message "${HOME}" "~" "~root" "#{pane_id}" } +bind -T parsetest n if-shell true { display-message "a\nb" "x;y" '#literal' } +EOF + +# Expected normalized output. Lines inside the braced bodies are indented with a +# single tab. +cat <<'EOF' >$EXP +bind-key -T parsetest a display-message hello +bind-key -T parsetest b display-message 'hello world' +bind-key -T parsetest c display-message 'literal $HOME #{p} ~' +bind-key -T parsetest d display-message '' +bind-key -T parsetest e display-message '#{pane_id}' +bind-key -T parsetest f display-message a ; display-message b +bind-key -T parsetest g display-message one +display-message two +bind-key -T parsetest h if-shell true { + display-message yes +} { + display-message no +} +bind-key -T parsetest m if-shell true { + display-message ${HOME} ~ ~root '#{pane_id}' +} +bind-key -T parsetest n if-shell true { + display-message a\nb 'x;y' '#literal' +} +--- options --- +display-message 'hi there' +EOF + +$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1 +$TMUX source-file $CONF || exit 1 +$TMUX set -g default-client-command 'display-message "hi there"' || exit 1 + +{ + $TMUX list-keys -T parsetest + echo "--- options ---" + $TMUX show -gv default-client-command +} >$TMP 2>&1 || exit 1 + +$TMUX kill-server 2>/dev/null + +cmp -s $TMP $EXP || { + echo "cmd-parse-print: output differs from expected" >&2 + diff -u $EXP $TMP >&2 + exit 1 +} + +exit 0