From fca791a6f656e918c53aeda4196ef8820f169179 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 13 Oct 2026 11:22:15 +0100 Subject: [PATCH] OSC 133 cmdline tests. --- regress/format-variables.sh | 1 + regress/input-osc133.sh | 142 ++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 regress/input-osc133.sh diff --git a/regress/format-variables.sh b/regress/format-variables.sh index dced1a711..f37c42573 100644 --- a/regress/format-variables.sh +++ b/regress/format-variables.sh @@ -109,6 +109,7 @@ pane_bg pane_bottom pane_command_duration pane_command_end_time +pane_command_line pane_command_running pane_command_start_time pane_command_status diff --git a/regress/input-osc133.sh b/regress/input-osc133.sh new file mode 100644 index 000000000..c1a425b17 --- /dev/null +++ b/regress/input-osc133.sh @@ -0,0 +1,142 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen +LC_ALL=C.UTF-8 +export TERM LC_ALL + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" +trap '$TMUX kill-server 2>/dev/null' 0 +trap 'exit 1' 1 2 15 + +check() +{ + format=$1 + expected=$2 + actual=$($TMUX -u display-message -p -t "$pane" "$format") || exit 1 + if [ "$actual" != "$expected" ]; then + printf 'format: %s\nexpected: %s\nactual: %s\n' \ + "$format" "$expected" "$actual" + exit 1 + fi +} + +emit() +{ + printf '\033]133;%s\033\\' "$1" | + $TMUX display-message -I -t "$pane" || exit 1 +} + +check_command() +{ + emit "C;$1" + check '#{pane_command_line}' "$2" + check '#{@started}' "$2" + check '#{pane_command_running}:#{pane_command_status}' '1:' + emit 'D;7' + check '#{pane_command_line}' "$2" + check '#{@finished}' "$2" + check '#{pane_command_running}:#{pane_command_status}' '0:7' +} + +$TMUX new-session -d -s test 'sleep 1000' || exit 1 +pane=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1 +other=$($TMUX new-window -d -P -F '#{pane_id}' '') || exit 1 +$TMUX set-hook -g pane-command-started \ + 'set -gF @started "#{hook_command_line}"' || exit 1 +$TMUX set-hook -g pane-command-finished \ + 'set -gF @finished "#{hook_command_line}"' || exit 1 + +check '#{pane_command_line}:#{pane_command_running}' ':0' +check_command 'cmdline_url=sleep%201' 'sleep 1' + +# Prompt markers leave the most recent command available. +emit A +emit B +check '#{pane_command_line}' 'sleep 1' +$TMUX display-message -p -t "$other" '#{pane_command_line}' | + grep -qx '' || exit 1 + +# Unknown fields are ignored and decoded semicolons belong to the value. +check_command 'other=1;cmdline_url=echo%20one%3B%20echo%20two;other=2' \ + 'echo one; echo two' +check_command 'cmdline_url=echo%20a+b%2Bc%25%3d%3D' 'echo a+b+c%==' +check_command 'cmdline_url=echo%20%C3%A9%E4%B8%AD' 'echo é中' +check_command 'cmdline_url=echo%20%2520' 'echo %20' + +# Use the same cleanup as titles for control characters and format jobs. +check_command 'cmdline_url=echo%20%23%28date%29' 'echo _(date)' +check_command 'cmdline_url=echo%20%5C' 'echo \\' + +# Multiline commands retain tabs and newlines as visible escapes, including +# in the event payloads used by hooks. +check_command 'cmdline_url=sleep%201%0Aecho%20done' 'sleep 1\necho done' +check_command 'cmdline_url=echo%09hello%0Aworld' 'echo\thello\nworld' + +# The name validator also permits these bytes before clean_name escapes them. +name=$(printf 'tab\tnewline\nname') +$TMUX rename-window -t "$other" "$name" || exit 1 +actual=$($TMUX -u display-message -p -t "$other" '#{window_name}') || exit 1 +[ "$actual" = 'tab\tnewline\nname' ] || exit 1 +$TMUX select-pane -t "$pane" -T "$name" || exit 1 +check '#{pane_title}' 'tab\tnewline\nname' + +# Every new start clears old metadata if it is missing or invalid. +emit C +check '#{pane_command_line}:#{@started}' ':' +for metadata in \ + 'cmdline_url=' \ + 'other=cmdline_url=stale' \ + 'xcmdline_url=stale' \ + 'cmdline_url=bad%' \ + 'cmdline_url=bad%2' \ + 'cmdline_url=bad%GG' \ + 'cmdline_url=bad%00tail' \ + 'cmdline_url=bad%0Dtail' \ + 'cmdline_url=bad%1Btail' \ + 'cmdline_url=bad%07tail' \ + 'cmdline_url=bad%08tail' \ + 'cmdline_url=bad%0Btail' \ + 'cmdline_url=bad%0Ctail' \ + 'cmdline_url=bad%7Ftail' \ + 'cmdline_url=bad%FF' \ + 'cmdline_url=bad%C3' \ + 'cmdline_url=bad%C0%AF' +do + check_command 'cmdline_url=previous' previous + check_command "$metadata" '' +done + +# The limit applies to decoded bytes, not the size of the OSC string. +decoded=$(printf '%5000s' '' | tr ' ' x) +check_command "cmdline_url=$decoded" "$decoded" +encoded=$(awk 'BEGIN { for (i = 0; i < 16384; i++) printf "%%78" }') +decoded=$(printf '%16384s' '' | tr ' ' x) +check_command "cmdline_url=$encoded" "$decoded" +check_command "cmdline_url=${encoded}%78" '' + +# Complete UTF-8 characters may reach the limit, but overlong lines are +# rejected in full rather than truncated, even through a character. +check_boundary() +{ + prefix=$(printf "%${1}s" '' | tr ' ' x) + check_command "cmdline_url=$prefix$2" "$prefix$3" + check_command "cmdline_url=${prefix}x$2" '' +} +check_boundary 16382 '%C3%A9' 'é' +check_boundary 16381 '%E4%B8%AD' '中' +check_boundary 16380 '%F0%9F%98%80' '😀' + +# Each hook gets a snapshot, even if multiple commands arrive in one read. +$TMUX set -g @log '' || exit 1 +$TMUX set-hook -g pane-command-started \ + 'set -gF @log "#{@log}|start:#{hook_command_line}"' || exit 1 +$TMUX set-hook -g pane-command-finished \ + 'set -gF @log "#{@log}|end:#{hook_command_line}:#{hook_command_status}"' || exit 1 +printf '\033]133;C;cmdline_url=first\007\033]133;D;0\007\033]133;C;cmdline_url=second\007\033]133;D;1\007\033]133;C\007\033]133;D;2\007' | + $TMUX display-message -I -t "$pane" || exit 1 +check '#{@log}' '|start:first|end:first:0|start:second|end:second:1|start:|end::2' +check '#{pane_command_line}' '' + +exit 0