mirror of
https://github.com/tmux/tmux.git
synced 2026-10-09 11:44:23 +00:00
Merge branch 'master' into command_parser
# Conflicts: # cmd-parse.y # regress/control-client-exit.sh # regress/if-shell-error.sh # tmux.h
This commit is contained in:
@@ -1,33 +1,17 @@
|
||||
TESTS!= echo *.sh
|
||||
LOGDIR=logs
|
||||
|
||||
.PHONY: all
|
||||
.NOTPARALLEL: all
|
||||
.PHONY: all prepare $(TESTS)
|
||||
|
||||
all:
|
||||
@mkdir -p "$(LOGDIR)"; \
|
||||
rm -f "$(LOGDIR)"/*.log; \
|
||||
failed=0; failures=; \
|
||||
all: prepare $(TESTS)
|
||||
@failed=0; failures=; \
|
||||
for test in $(TESTS); do \
|
||||
base=$${test##*/}; \
|
||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||
rm -f "$$log"; \
|
||||
printf '%-40s ' "$$test"; \
|
||||
start=$$(date +%s); \
|
||||
env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||
sh -x "$$test" >"$$log" 2>&1; \
|
||||
if [ $$? -eq 0 ]; then \
|
||||
end=$$(date +%s); \
|
||||
rm -f "$$log"; \
|
||||
echo "PASS ($$((end - start))s)"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
echo "FAIL ($$((end - start))s)"; \
|
||||
echo " log: $$log"; \
|
||||
if [ -f "$$log" ]; then \
|
||||
failed=1; \
|
||||
failures="$$failures $$test"; \
|
||||
fi; \
|
||||
sleep 1; \
|
||||
done; \
|
||||
if [ "$$failed" -ne 0 ]; then \
|
||||
echo; \
|
||||
@@ -39,3 +23,22 @@ all:
|
||||
rmdir "$(LOGDIR)" 2>/dev/null || true; \
|
||||
fi; \
|
||||
exit $$failed
|
||||
|
||||
prepare:
|
||||
@mkdir -p "$(LOGDIR)"
|
||||
@rm -f "$(LOGDIR)"/*.log
|
||||
|
||||
$(TESTS): prepare
|
||||
@base="$@"; base=$${base##*/}; \
|
||||
log="$(LOGDIR)/$${base%.sh}.log"; \
|
||||
start=$$(date +%s); \
|
||||
if env -i LC_CTYPE=C.UTF-8 MallocNanoZone=0 \
|
||||
sh -x "$@" >"$$log" 2>&1; then \
|
||||
end=$$(date +%s); \
|
||||
rm -f "$$log"; \
|
||||
printf '%-40s PASS (%ss)\n' "$@" "$$((end - start))"; \
|
||||
else \
|
||||
end=$$(date +%s); \
|
||||
printf '%-40s FAIL (%ss)\n log: %s\n' \
|
||||
"$@" "$$((end - start))" "$$log"; \
|
||||
fi
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
# monitor-activity, monitor-bell and monitor-silence: both the
|
||||
# alert-activity, alert-bell and alert-silence hooks and the winlink alert
|
||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag and
|
||||
# the #, !, ~ characters in window_flags). The sessions are detached so
|
||||
# flags (window_activity_flag, window_bell_flag, window_silence_flag,
|
||||
# their session equivalents and the #, !, ~ characters in window_flags).
|
||||
# The sessions are detached so
|
||||
# alert flags are set even on the current window; the *-action options
|
||||
# still decide whether the hooks fire. Panes run cat: activity is
|
||||
# generated by the tty echo of send-keys and a bell by sending a BEL and
|
||||
@@ -111,6 +112,24 @@ flags_have()
|
||||
esac
|
||||
}
|
||||
|
||||
assert_session_flag()
|
||||
{
|
||||
flag="#{session_${1}_flag}"
|
||||
expected=$2
|
||||
|
||||
# Only mon exists here. Check both window contexts and list-sessions.
|
||||
for target in mon:w0 "mon:$3"; do
|
||||
value=$($TMUX display -pt "$target" "$flag") ||
|
||||
fail "display $flag failed"
|
||||
[ "$value" = "$expected" ] ||
|
||||
fail "expected $flag for $target to be '$expected' but got '$value'"
|
||||
done
|
||||
value=$($TMUX list-sessions -F "$flag") ||
|
||||
fail "list-sessions $flag failed"
|
||||
[ "$value" = "$expected" ] ||
|
||||
fail "expected list-sessions $flag to be '$expected' but got '$value'"
|
||||
}
|
||||
|
||||
flags_lack()
|
||||
{
|
||||
target=$1
|
||||
@@ -150,10 +169,12 @@ $TMUX set-hook -g alert-silence \
|
||||
# alert-bell and sets the bell flag, shown as ! in window_flags.
|
||||
$TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed"
|
||||
assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0
|
||||
assert_session_flag bell 0 bellw
|
||||
flags_lack mon:bellw '!'
|
||||
bell mon:bellw
|
||||
wait_for @log '|alert-bell:mon:bellw'
|
||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 1
|
||||
assert_session_flag bell 1 bellw
|
||||
flags_have mon:bellw '!'
|
||||
|
||||
# Bells are not deduplicated: a second bell fires the hook again even
|
||||
@@ -164,6 +185,7 @@ wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw'
|
||||
# Selecting the window clears the alert flags.
|
||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||
wait_for_fmt mon:bellw '#{window_bell_flag}' 0
|
||||
assert_session_flag bell 0 bellw
|
||||
flags_lack mon:bellw '!'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
|
||||
@@ -173,6 +195,7 @@ $TMUX set -g @log '' || fail "reset @log failed"
|
||||
bell mon:w0
|
||||
wait_for @log '|alert-bell:mon:w0'
|
||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 1
|
||||
assert_session_flag bell 1 bellw
|
||||
$TMUX selectw -t mon:bellw || fail "select-window bellw failed"
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
wait_for_fmt mon:w0 '#{window_bell_flag}' 0
|
||||
@@ -199,6 +222,7 @@ $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed"
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
activity mon:actw
|
||||
assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0
|
||||
assert_session_flag activity 0 actw
|
||||
assert_unchanged @log ''
|
||||
|
||||
# With monitor-activity on, output fires alert-activity and sets the
|
||||
@@ -208,6 +232,7 @@ $TMUX set -wt mon:actw monitor-activity on ||
|
||||
activity mon:actw
|
||||
wait_for @log '|alert-activity:mon:actw'
|
||||
wait_for_fmt mon:actw '#{window_activity_flag}' 1
|
||||
assert_session_flag activity 1 actw
|
||||
flags_have mon:actw '#'
|
||||
|
||||
# While the flag is set further activity does not fire the hook again.
|
||||
@@ -234,6 +259,7 @@ exec 3>"$OUT/fifo"
|
||||
wait_for_fmt mon: '#{session_attached}' 1
|
||||
$TMUX selectw -t mon:actw || fail "select-window actw failed"
|
||||
wait_for_fmt mon:actw '#{window_activity_flag}' 0
|
||||
assert_session_flag activity 0 actw
|
||||
flags_lack mon:actw '#'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
exec 3>&-
|
||||
@@ -250,6 +276,7 @@ $TMUX set -wt mon:w0 monitor-activity on ||
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
activity mon:w0
|
||||
wait_for_fmt mon:w0 '#{window_activity_flag}' 1
|
||||
assert_session_flag activity 1 actw
|
||||
assert_unchanged @log ''
|
||||
$TMUX set -wut mon:w0 monitor-activity ||
|
||||
fail "unset monitor-activity w0 failed"
|
||||
@@ -273,16 +300,19 @@ $TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
# running.
|
||||
$TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed"
|
||||
assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0
|
||||
assert_session_flag silence 0 silw
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
$TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed"
|
||||
wait_for @log '|alert-silence:mon:silw'
|
||||
wait_for_fmt mon:silw '#{window_silence_flag}' 1
|
||||
assert_session_flag silence 1 silw
|
||||
flags_have mon:silw '~'
|
||||
assert_unchanged @log '|alert-silence:mon:silw'
|
||||
$TMUX set -wt mon:silw monitor-silence 0 ||
|
||||
fail "reset monitor-silence failed"
|
||||
$TMUX selectw -t mon:silw || fail "select-window silw failed"
|
||||
wait_for_fmt mon:silw '#{window_silence_flag}' 0
|
||||
assert_session_flag silence 0 silw
|
||||
flags_lack mon:silw '~'
|
||||
$TMUX selectw -t mon:w0 || fail "select-window w0 failed"
|
||||
|
||||
|
||||
34
regress/alternate-screen-resize.sh
Normal file
34
regress/alternate-screen-resize.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A pending pane resize must still reach the pty when the application enters
|
||||
# the alternate screen before the resize timer fires.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$ -f/dev/null"
|
||||
|
||||
TMP=$(mktemp)
|
||||
TMP2=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$TMP2"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
|
||||
$TMUX new -d -x 80 -y 24 \
|
||||
"read x; printf '\033[?1049h'; read x; stty size >$TMP; sleep 100" \
|
||||
</dev/null || exit 1
|
||||
sleep 1
|
||||
|
||||
# The first split resizes and starts the resize timer, the second queues
|
||||
# another resize while the timer is pending.
|
||||
$TMUX splitw -h -d 'sleep 100' || exit 1
|
||||
$TMUX splitw -v -d -t:0.0 'sleep 100' || exit 1
|
||||
$TMUX send -t:0.0 Enter || exit 1
|
||||
sleep 1
|
||||
|
||||
$TMUX send -t:0.0 Enter || exit 1
|
||||
sleep 1
|
||||
$TMUX display -p -t:0.0 '#{pane_height} #{pane_width}' >$TMP2
|
||||
cmp -s "$TMP" "$TMP2" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
79
regress/attach-session-redraw.sh
Executable file
79
regress/attach-session-redraw.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Selecting a pane with attach-session from an already attached client must
|
||||
# redraw pane contents when window-style and window-active-style differ.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lattach-redraw-outer-$$ -f/dev/null"
|
||||
BEFORE=$DIR/before
|
||||
AFTER=$DIR/after
|
||||
|
||||
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
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
LEFT=$($INNER new-session -dPF '#{pane_id}' -s inner -x 40 -y 8 \
|
||||
"printf 'LEFT'; 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 -g default-terminal screen || exit 1
|
||||
$INNER split-window -h -t "$LEFT" "printf 'RIGHT'; exec sleep 100" || exit 1
|
||||
$INNER set-option -w -t "$LEFT" window-style bg=red || exit 1
|
||||
$INNER set-option -w -t "$LEFT" window-active-style bg=blue || exit 1
|
||||
$INNER bind-key -n x attach-session -t "$LEFT" || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 8 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lattach-redraw-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
sleep 1
|
||||
$OUTER send-keys -t outer:0.0 x || exit 1
|
||||
sleep 1
|
||||
[ "$($INNER display-message -p -t inner '#{pane_id}')" = "$LEFT" ] ||
|
||||
fail "attach-session did not select the target pane"
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$BEFORE" || exit 1
|
||||
|
||||
# A forced redraw produces the correct scene. It must be identical to the
|
||||
# scene drawn immediately by attach-session.
|
||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||
sleep 1
|
||||
$OUTER capture-pane -pe -t outer:0.0 >"$AFTER" || exit 1
|
||||
cmp -s "$BEFORE" "$AFTER" ||
|
||||
fail "attach-session left stale active/inactive pane styles"
|
||||
|
||||
exit 0
|
||||
38
regress/capture-pane-time.sh
Normal file
38
regress/capture-pane-time.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# capture-pane -I line timestamps
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMP=$(mktemp)
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$TMP"' 0 1 15
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
before=$(date +%s)
|
||||
$TMUX new-session -d -x 40 -y 5 'seq 1 12; sleep 10' || exit 1
|
||||
sleep 1
|
||||
after=$(date +%s)
|
||||
|
||||
$TMUX capture-pane -pILF -S - -E - >"$TMP" || exit 1
|
||||
awk -v before="$before" -v after="$after" '
|
||||
$1 !~ /^-?[0-9]+$/ || $2 !~ /^[0-9]+$/ { exit 1 }
|
||||
$4 == "1" {
|
||||
if ($2 < before || $2 > after)
|
||||
exit 1
|
||||
history = 1
|
||||
}
|
||||
$4 == "9" {
|
||||
if ($2 != 0)
|
||||
exit 1
|
||||
visible = 1
|
||||
}
|
||||
END { if (!history || !visible) exit 1 }
|
||||
' "$TMP" || {
|
||||
cat "$TMP"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
51
regress/cfg-client-free.sh
Normal file
51
regress/cfg-client-free.sh
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
# a client lost while the configuration is loading is freed
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMPDIR=$(mktemp -d) || exit 1
|
||||
TMUX="$TEST_TMUX -S$TMPDIR/tmux.sock"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
touch $TMPDIR/go
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# Poll until a command succeeds.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while ! "$@"; do
|
||||
[ $i -eq 100 ] && exit 1
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
# The run-shell holds the configuration until the go file exists.
|
||||
cat <<EOF >$TMPDIR/conf
|
||||
new-session -d -s keep
|
||||
run-shell 'touch $TMPDIR/started; i=0; while [ ! -f $TMPDIR/go ] && [ \$i -lt 100 ]; do sleep 0.1; i=\$((i + 1)); done'
|
||||
EOF
|
||||
|
||||
cd $TMPDIR || exit 1
|
||||
$TMUX -v -f$TMPDIR/conf new-session -d -s first </dev/null &
|
||||
pid=$!
|
||||
wait_for test -f $TMPDIR/started
|
||||
kill $pid
|
||||
wait $pid
|
||||
log=$(echo $TMPDIR/tmux-server-*.log)
|
||||
wait_for grep -q 'lost client' $log
|
||||
c=$(sed -n 's/.* lost client //p' $log)
|
||||
|
||||
# The client is freed straight away, well before the configuration ends.
|
||||
wait_for grep -q "free client $c " $log
|
||||
|
||||
exit 0
|
||||
90
regress/cfg-client-lost-before-wait.sh
Normal file
90
regress/cfg-client-lost-before-wait.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A client lost before its configuration callback fires must not start waiting.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
python3 - "$TEST_TMUX" "$(dirname "$0")/../tmux-protocol.h" <<'PY'
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
tmux = sys.argv[1]
|
||||
header = Path(sys.argv[2]).read_text()
|
||||
version = int(re.search(r"#define PROTOCOL_VERSION\s+(\d+)", header)[1])
|
||||
enum = re.search(r"enum msgtype\s*\{(.*?)\};", header, re.S)[1]
|
||||
enum = re.sub(r"/\*.*?\*/", "", enum, flags=re.S)
|
||||
types = {}
|
||||
value = 0
|
||||
for entry in enum.split(","):
|
||||
entry = entry.strip()
|
||||
if not entry:
|
||||
continue
|
||||
if "=" in entry:
|
||||
entry, number = entry.split("=")
|
||||
value = int(number.strip())
|
||||
types[entry.strip()] = value
|
||||
value += 1
|
||||
|
||||
def message(name, body=b""):
|
||||
return struct.pack("=IIII", types[name], 16 + len(body), version,
|
||||
os.getpid()) + body
|
||||
|
||||
def wait_for(predicate):
|
||||
end = time.monotonic() + 5
|
||||
while time.monotonic() < end:
|
||||
result = predicate()
|
||||
if result:
|
||||
return result
|
||||
time.sleep(0.05)
|
||||
raise AssertionError("timed out waiting for client cleanup")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="tmux-cfg-early-") as tmp:
|
||||
root = Path(tmp)
|
||||
sockpath = root / "socket"
|
||||
go = root / "go"
|
||||
conf = root / "conf"
|
||||
conf.write_text("run-shell 'i=0; while [ ! -f %s ] && [ $i -lt 100 ]; "
|
||||
"do sleep 0.1; i=$((i + 1)); done'\n" % go)
|
||||
server = subprocess.Popen([tmux, "-D", "-v", "-S", str(sockpath),
|
||||
"-f", str(conf)], cwd=tmp, stdout=subprocess.DEVNULL)
|
||||
logpath = root / ("tmux-server-%d.log" % server.pid)
|
||||
|
||||
def log():
|
||||
return logpath.read_text() if logpath.exists() else ""
|
||||
|
||||
try:
|
||||
wait_for(sockpath.exists)
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
|
||||
client.connect(str(sockpath))
|
||||
# Both messages are read together. The short MSG_COMMAND makes
|
||||
# proc_event_cb lose the client before server_loop runs its queue.
|
||||
client.sendall(message("MSG_IDENTIFY_CWD", tmp.encode() + b"\0") +
|
||||
message("MSG_IDENTIFY_CLIENTPID", struct.pack("=i", os.getpid())) +
|
||||
message("MSG_IDENTIFY_DONE") + message("MSG_COMMAND"))
|
||||
lost = wait_for(lambda: re.search(r"lost client (\S+)", log()))[1]
|
||||
wait_for(lambda: "free client %s (0 references)" % lost in log())
|
||||
contents = log()
|
||||
callback = "cmdq_next <client-%d>: [cfg_client_done/" % os.getpid()
|
||||
assert contents.index("lost client " + lost) < \
|
||||
contents.index(callback), contents
|
||||
# Cleanup must happen while configuration is still blocked.
|
||||
assert "cmdq_next <global>: [cfg_done/" not in contents, contents
|
||||
finally:
|
||||
go.touch()
|
||||
server.terminate()
|
||||
try:
|
||||
server.wait(timeout=3)
|
||||
except subprocess.TimeoutExpired:
|
||||
server.kill()
|
||||
server.wait()
|
||||
PY
|
||||
0
regress/cfg-client-lost.sh
Executable file → Normal file
0
regress/cfg-client-lost.sh
Executable file → Normal file
60
regress/cfg-load-once.sh
Normal file
60
regress/cfg-load-once.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/sh
|
||||
|
||||
# the configuration is loaded once if the client that started it is lost, and
|
||||
# the next client still waits for it to finish
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMPDIR=$(mktemp -d) || exit 1
|
||||
TMUX="$TEST_TMUX -S$TMPDIR/tmux.sock"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
touch $TMPDIR/go
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$TMPDIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# Poll until a command succeeds.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while ! "$@"; do
|
||||
[ $i -eq 100 ] && exit 1
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
# The run-shell holds the configuration until the go file exists.
|
||||
cat <<EOF >$TMPDIR/conf
|
||||
set -ga @runs x
|
||||
new-session -d -s keep
|
||||
run-shell 'touch $TMPDIR/started; i=0; while [ ! -f $TMPDIR/go ] && [ \$i -lt 100 ]; do sleep 0.1; i=\$((i + 1)); done'
|
||||
set -g @late 1
|
||||
EOF
|
||||
|
||||
cd $TMPDIR || exit 1
|
||||
$TMUX -v -f$TMPDIR/conf new-session -d -s first </dev/null &
|
||||
pid=$!
|
||||
wait_for test -f $TMPDIR/started
|
||||
kill $pid
|
||||
wait $pid
|
||||
log=$(echo $TMPDIR/tmux-server-*.log)
|
||||
wait_for grep -q 'lost client' $log
|
||||
|
||||
# The second client must identify before the configuration is released.
|
||||
$TMUX new-session -d -s second \; display -p '#{@runs} #{@late}' \
|
||||
</dev/null >$TMPDIR/out &
|
||||
pid=$!
|
||||
wait_for grep -q "name is client-$pid\$" $log
|
||||
touch $TMPDIR/go
|
||||
wait $pid || exit 1
|
||||
|
||||
[ "$(cat $TMPDIR/out)" = "x 1" ] || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -5,6 +5,15 @@
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
# The pane's shell must not be the user's own interactive shell: a custom
|
||||
# PS1/PROMPT_COMMAND that sets the terminal title (as many do, for tmux/xterm
|
||||
# TERM types) would redraw over the titles this test sets and checks on
|
||||
# every prompt, regardless of how long it waits first.
|
||||
shell=
|
||||
if command -v bash >/dev/null 2>&1; then
|
||||
shell='bash --noprofile --norc +o history'
|
||||
fi
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
@@ -30,7 +39,7 @@ must_equal()
|
||||
[ "$got" = "$want" ] || fail "got '$got', expected '$want'"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -x 80 -y 24 || exit 1
|
||||
$TMUX new-session -d -x 80 -y 24 -- $shell || exit 1
|
||||
$TMUX set-option -qg allow-set-title on || exit 1
|
||||
$TMUX set-option -qg allow-rename on || exit 1
|
||||
$TMUX set-option -qg automatic-rename off || exit 1
|
||||
|
||||
@@ -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
|
||||
|
||||
96
regress/clear-on-attach.sh
Normal file
96
regress/clear-on-attach.sh
Normal file
@@ -0,0 +1,96 @@
|
||||
#!/bin/sh
|
||||
|
||||
# With clear-on-attach disabled tmux must preserve the terminal contents by
|
||||
# scrolling them away, rather than entering and clearing the alternate screen.
|
||||
# Capture the bytes written by a real attached client for both settings.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER_ON="$TEST_TMUX -LtestIon$$ -f/dev/null"
|
||||
OUTER_ON="$TEST_TMUX -LtestOon$$ -f/dev/null"
|
||||
INNER_OFF="$TEST_TMUX -LtestIoff$$ -f/dev/null"
|
||||
OUTER_OFF="$TEST_TMUX -LtestOoff$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER_ON kill-server 2>/dev/null
|
||||
$INNER_ON kill-server 2>/dev/null
|
||||
$OUTER_OFF kill-server 2>/dev/null
|
||||
$INNER_OFF kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$current_inner list-clients 2>/dev/null | grep -q . && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
capture_attach()
|
||||
{
|
||||
setting=$1
|
||||
output=$2
|
||||
go=$DIR/go-$setting
|
||||
if [ "$setting" = on ]; then
|
||||
current_inner=$INNER_ON
|
||||
current_outer=$OUTER_ON
|
||||
else
|
||||
current_inner=$INNER_OFF
|
||||
current_outer=$OUTER_OFF
|
||||
fi
|
||||
|
||||
$current_inner new-session -d -s inner -x40 -y10 \
|
||||
'exec sleep 100' || exit 1
|
||||
$current_inner set-option -g status off || exit 1
|
||||
$current_inner set-option -g clear-on-attach "$setting" || exit 1
|
||||
$current_outer new-session -d -s outer -x40 -y10 \
|
||||
"while [ ! -e '$go' ]; do sleep 0.01; done; exec $current_inner attach-session -t inner" || exit 1
|
||||
$current_outer set-option -g status off || exit 1
|
||||
$current_outer pipe-pane -O "cat >'$output'" || exit 1
|
||||
touch "$go"
|
||||
wait_for_client
|
||||
sleep 0.5
|
||||
$current_outer pipe-pane -O || exit 1
|
||||
}
|
||||
|
||||
on=$DIR/on
|
||||
off=$DIR/off
|
||||
capture_attach on "$on"
|
||||
capture_attach off "$off"
|
||||
|
||||
smcup=$(printf '\033[?1049h')
|
||||
grep -Fq "$smcup" "$on" || fail "clear-on-attach on did not use smcup"
|
||||
if grep -Fq "$smcup" "$off"; then
|
||||
fail "clear-on-attach off used smcup"
|
||||
fi
|
||||
|
||||
# Some screen terminfo entries have indn (CSI Ps S), while older ones only
|
||||
# have ind (newline). The preserving path uses indn once or repeats ind once
|
||||
# for every line in the terminal plus one.
|
||||
escape=$(printf '\033')
|
||||
if ! LC_ALL=C grep -Eq "${escape}\\[[0-9]+S" "$off"; then
|
||||
hex=$(od -An -tx1 -v "$off" | tr -d ' \n')
|
||||
case "$hex" in
|
||||
*0a0a0a0a0a0a0a0a0a0a0a*) ;;
|
||||
*) fail "clear-on-attach off did not scroll the old contents away" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
exit 0
|
||||
82
regress/client-key-order-focus.sh
Normal file
82
regress/client-key-order-focus.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/bin/sh
|
||||
|
||||
# send-keys -K injects a key into a client. Commands produced by that key must
|
||||
# be inserted immediately after send-keys, before later commands in the same
|
||||
# command list. Focus events arriving between a prefix and its following key
|
||||
# must be forwarded without cancelling the prefix table.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
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
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
client=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$client" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_for_option()
|
||||
{
|
||||
option=$1
|
||||
expected=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
actual=$($INNER show-option -gv "$option" 2>/dev/null)
|
||||
[ "$actual" = "$expected" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$option is '$actual', expected '$expected'"
|
||||
}
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s inner -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g focus-events on || exit 1
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
wait_for_client
|
||||
|
||||
# The binding generated by send-keys -K must run before the command which
|
||||
# follows send-keys in this command list. Before the queue-ordering fix this
|
||||
# produces AB rather than BA.
|
||||
$INNER set-option -g @order '' || exit 1
|
||||
$INNER bind-key -n a set-option -gF @order '#{@order}B' || exit 1
|
||||
$INNER send-keys -K -c "$client" a \; \
|
||||
set-option -gF @order '#{@order}A' || exit 1
|
||||
wait_for_option @order BA
|
||||
|
||||
# Put the real attached client in the prefix table, deliver FocusIn, then the
|
||||
# bound key. FocusIn is not bindable and must not reset the prefix table.
|
||||
$INNER bind-key x set-option -g @focus-prefix yes || exit 1
|
||||
$OUTER send-keys C-b || exit 1
|
||||
focus_in=$(printf '\033[I')
|
||||
$OUTER send-keys -l "$focus_in" || exit 1
|
||||
$OUTER send-keys x || exit 1
|
||||
wait_for_option @focus-prefix yes
|
||||
|
||||
exit 0
|
||||
101
regress/clock-mode.sh
Normal file
101
regress/clock-mode.sh
Normal file
@@ -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
|
||||
75
regress/cmd-parse-condition.sh
Normal file
75
regress/cmd-parse-condition.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Assignments and commands must use the first matching conditional branch.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
CONF=$(mktemp)
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -f "$CONF"' 0
|
||||
trap 'exit 1' 1 2 15
|
||||
$TMUX new-session -d || exit 1
|
||||
|
||||
branch()
|
||||
{
|
||||
printf '%%%s %s%s' "$1" "$2" "$sep"
|
||||
printf '%sPARSE_BRANCH=%s%sset -g @branch %s%s' \
|
||||
"$prefix" "$3" "$sep" "$3" "$sep"
|
||||
}
|
||||
|
||||
# Cover no, one and two elif branches, with and without else, all condition
|
||||
# values, and nesting inside both an active and an inactive outer branch.
|
||||
for mode in normal hidden inline; do
|
||||
prefix=
|
||||
sep='
|
||||
'
|
||||
[ "$mode" = hidden ] && prefix='%hidden '
|
||||
[ "$mode" = inline ] && sep=' '
|
||||
for elifs in 0 1 2; do
|
||||
bits=0
|
||||
while [ "$bits" -lt "$((1 << (elifs + 1)))" ]; do
|
||||
for otherwise in 0 1; do
|
||||
for outer in 0 1; do
|
||||
expected=unchanged
|
||||
{
|
||||
printf '%s\n' 'PARSE_BRANCH=unchanged' \
|
||||
'set -g @branch unchanged'
|
||||
printf '%%if %s\n' "$outer"
|
||||
n=0
|
||||
while [ "$n" -le "$elifs" ]; do
|
||||
condition=$(((bits >> n) & 1))
|
||||
directive=elif
|
||||
[ "$n" -eq 0 ] && directive=if
|
||||
branch "$directive" "$condition" "$n"
|
||||
if [ "$condition" -eq 1 ] &&
|
||||
[ "$expected" = unchanged ]; then
|
||||
expected=$n
|
||||
fi
|
||||
n=$((n + 1))
|
||||
done
|
||||
if [ "$otherwise" -eq 1 ]; then
|
||||
branch else '' else
|
||||
[ "$expected" = unchanged ] && expected=else
|
||||
fi
|
||||
printf '%s\n' '%endif' '%endif' \
|
||||
'set -g @value "$PARSE_BRANCH"'
|
||||
} >"$CONF"
|
||||
[ "$outer" -eq 0 ] && expected=unchanged
|
||||
$TMUX source-file "$CONF" || exit 1
|
||||
out=$($TMUX display-message -p '#{@value}:#{@branch}')
|
||||
if [ "$out" != "$expected:$expected" ]; then
|
||||
echo "$mode, $elifs elifs, bits=$bits, else=$otherwise, outer=$outer"
|
||||
echo "Expected $expected:$expected, got $out"
|
||||
cat "$CONF"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
done
|
||||
bits=$((bits + 1))
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
exit 0
|
||||
53
regress/codepoint-widths.sh
Normal file
53
regress/codepoint-widths.sh
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Width cache ranges must include their endpoint without overflowing wchar_t.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
SERVER=
|
||||
WATCHDOG=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
if [ -n "$WATCHDOG" ]; then
|
||||
kill "$WATCHDOG" 2>/dev/null
|
||||
wait "$WATCHDOG" 2>/dev/null
|
||||
fi
|
||||
[ -n "$SERVER" ] && kill -9 "$SERVER" 2>/dev/null
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
$TMUX new-session -d 'exec sleep 60' || exit 1
|
||||
SERVER=$($TMUX display-message -p '#{pid}') || exit 1
|
||||
|
||||
# kill-server cannot stop a server stuck rebuilding the width cache.
|
||||
(
|
||||
sleep 5
|
||||
kill -9 "$SERVER" 2>/dev/null
|
||||
) &
|
||||
WATCHDOG=$!
|
||||
|
||||
$TMUX set -g codepoint-widths 'U+1F600=2' || exit 1
|
||||
|
||||
# Cover both signed and unsigned 32-bit wchar_t. Values above WCHAR_MAX
|
||||
# are ignored by the parser on platforms where they cannot be represented.
|
||||
for value in U+7FFFFFFF=1 U+7FFFFFFE-U+7FFFFFFF=2 \
|
||||
U+FFFFFFFF=1 U+FFFFFFFE-U+FFFFFFFF=2; do
|
||||
$TMUX set -g codepoint-widths "$value" || exit 1
|
||||
[ "$($TMUX display-message -p alive)" = alive ] || exit 1
|
||||
done
|
||||
|
||||
# Check that an ordinary range still includes both endpoints and stops there.
|
||||
$TMUX set -g codepoint-widths 'U+03B1-U+03B3=2' || exit 1
|
||||
$TMUX set -g @text 'αβγδ' || exit 1
|
||||
[ "$($TMUX display-message -p '#{w:@text}')" = 7 ] || exit 1
|
||||
$TMUX set -gu codepoint-widths || exit 1
|
||||
[ "$($TMUX display-message -p '#{w:@text}')" = 4 ] || exit 1
|
||||
|
||||
$TMUX kill-server || exit 1
|
||||
SERVER=
|
||||
exit 0
|
||||
150
regress/confirm-before-lock.sh
Normal file
150
regress/confirm-before-lock.sh
Normal file
@@ -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
|
||||
2
regress/control-client-exit-stalled.sh
Executable file → Normal file
2
regress/control-client-exit-stalled.sh
Executable file → Normal file
@@ -17,7 +17,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
|
||||
@@ -19,8 +19,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
SOCKET=$(mktemp -u testXXXXXX)
|
||||
TMUX="$TEST_TMUX -L$SOCKET"
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
|
||||
123
regress/control-client-new-layouts.sh
Executable file
123
regress/control-client-new-layouts.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that control client flags are available in every format expansion path
|
||||
# used to produce layouts. The layout strings themselves are tested separately.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
LANG=C.UTF-8
|
||||
export PATH TERM LC_ALL LANG
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
CFG=$DIR/flags.conf
|
||||
PID=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
[ -n "$PID" ] && kill "$PID" 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for()
|
||||
{
|
||||
pattern=$1
|
||||
i=0
|
||||
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -F -- "$pattern" "$OUT" >/dev/null 2>&1 && return 0
|
||||
if [ -n "$PID" ] && ! kill -0 "$PID" 2>/dev/null; then
|
||||
fail "control client exited waiting for: $pattern"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "missing: $pattern"
|
||||
}
|
||||
|
||||
send()
|
||||
{
|
||||
printf '%s\n' "$*" >&3
|
||||
}
|
||||
|
||||
check_commands()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
match='#{m:*new-layouts*,#{client_flags}}'
|
||||
|
||||
send "display-message -p 'DISPLAY-$name $match'"
|
||||
wait_for "DISPLAY-$name $expected"
|
||||
send "list-panes -F 'PANES-$name $match'"
|
||||
wait_for "PANES-$name $expected"
|
||||
send "list-windows -F 'WINDOWS-$name $match'"
|
||||
wait_for "WINDOWS-$name $expected"
|
||||
send "list-sessions -F 'SESSIONS-$name $match'"
|
||||
wait_for "SESSIONS-$name $expected"
|
||||
send "list-clients -F 'CLIENTS-$name $match'"
|
||||
wait_for "CLIENTS-$name $expected"
|
||||
}
|
||||
|
||||
check_config()
|
||||
{
|
||||
name=$1
|
||||
expected=$2
|
||||
|
||||
send "source-file '$CFG'"
|
||||
send "display-message -p 'CONFIG-$name #{@config-new-layouts}'"
|
||||
wait_for "CONFIG-$name $expected"
|
||||
}
|
||||
|
||||
cat >"$CFG" <<'EOF'
|
||||
%if #{m:*new-layouts*,#{client_flags}}
|
||||
set-option -g @config-new-layouts 1
|
||||
%else
|
||||
set-option -g @config-new-layouts 0
|
||||
%endif
|
||||
EOF
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX new-session -d -s layouts -x 80 -y 24 || exit 1
|
||||
$TMUX split-window -h -t layouts: || exit 1
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
: >"$OUT"
|
||||
$TMUX -C attach-session -t layouts <"$FIFO" >"$OUT" 2>&1 &
|
||||
PID=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
send 'display-message -p READY'
|
||||
wait_for READY
|
||||
|
||||
check_commands OFF 0
|
||||
check_config OFF 0
|
||||
|
||||
send 'refresh-client -f new-layouts'
|
||||
check_commands ON 1
|
||||
check_config ON 1
|
||||
|
||||
send 'refresh-client -f !new-layouts'
|
||||
check_commands OFF-AGAIN 0
|
||||
check_config OFF-AGAIN 0
|
||||
|
||||
# The list commands also run for unattached command clients. Supplying that
|
||||
# client to formats must not make the session loop dereference a NULL session.
|
||||
$TMUX list-sessions -F '#{S:all,active}' >/dev/null ||
|
||||
fail "session loop failed for unattached command client"
|
||||
$TMUX has-session -t layouts || fail "server exited"
|
||||
|
||||
exit 0
|
||||
64
regress/control-client-popup.sh
Normal file
64
regress/control-client-popup.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Popups require an attached terminal and cannot be displayed by a control
|
||||
# client.
|
||||
# A popup command from control mode must be ignored cleanly, leaving the
|
||||
# client command queue and server usable.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
FIFO=$DIR/input
|
||||
OUT=$DIR/output
|
||||
RAN=$DIR/popup-ran
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
exec 3>&-
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mkfifo "$FIFO" || exit 1
|
||||
$TMUX new-session -d -s control -x40 -y10 'exec sleep 100' || exit 1
|
||||
$TMUX -C attach-session -t control <"$FIFO" >"$OUT" 2>&1 &
|
||||
control_pid=$!
|
||||
exec 3>"$FIFO"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX list-clients -F '#{client_flags}' 2>/dev/null |
|
||||
grep -q 'control-mode' && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "control client did not attach"
|
||||
|
||||
printf '%s\n' "display-popup -E 'touch $RAN'" >&3
|
||||
printf '%s\n' "display-message -p CONTROL-POPUP-ALIVE" >&3
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
grep -q 'CONTROL-POPUP-ALIVE' "$OUT" 2>/dev/null && break
|
||||
if ! kill -0 "$control_pid" 2>/dev/null; then
|
||||
fail "control client exited after display-popup"
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "command after control-client popup did not run"
|
||||
$TMUX has-session -t control || fail "server exited after control-client popup"
|
||||
[ ! -e "$RAN" ] || fail "control client started a popup command"
|
||||
|
||||
exit 0
|
||||
38
regress/control-client-run-shell-exit.sh
Normal file
38
regress/control-client-run-shell-exit.sh
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A waited run-shell command can complete after a control client has closed its
|
||||
# input and exited. The command queue still references the client until the job
|
||||
# callback runs, but control_stop has already destroyed c->control_state. Late
|
||||
# command output must be discarded instead of dereferencing the stopped control
|
||||
# state and killing the server.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
OUT=$(mktemp)
|
||||
trap "$TMUX kill-server 2>/dev/null; rm -f $OUT" 0 1 15
|
||||
|
||||
$TMUX -f/dev/null new-session -d -s main || exit 1
|
||||
|
||||
printf '%s\n' 'run-shell "sleep 0.2; echo TEST"' |
|
||||
$TMUX -f/dev/null -C >"$OUT" 2>&1
|
||||
|
||||
sleep 1
|
||||
|
||||
$TMUX has-session -t main 2>/dev/null || {
|
||||
echo "server exited after late run-shell output"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
grep -q '^%exit' "$OUT" || {
|
||||
echo "control client did not exit"
|
||||
cat "$OUT"
|
||||
exit 1
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -30,8 +30,11 @@ killw
|
||||
EOF
|
||||
sleep 1
|
||||
$TMUX has || exit 1
|
||||
$TMUX lsp -aF '#{pane_id} #{window_layout}' >$TMP || exit 1
|
||||
cat <<EOF|cmp -s $TMP - || exit 1
|
||||
# Use a control client to request legacy layouts, keeping only pane lines
|
||||
# from the control protocol output.
|
||||
$TMUX -C lsp -aF '#{pane_id} #{window_layout}' |
|
||||
grep '^%[0-9]' >$TMP || exit 1
|
||||
cat <<EOF|cmp $TMP - || exit 1
|
||||
%0 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%3 f5ab,200x200,0,0[200x50,0,0,0,200x149,0,51,3]
|
||||
%2 dcbd,200x200,0,0[200x100,0,0,2,200x99,0,101,4]
|
||||
|
||||
@@ -10,7 +10,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
FIFO=$(mktemp -u)
|
||||
|
||||
0
regress/control-notify-guard.sh
Executable file → Normal file
0
regress/control-notify-guard.sh
Executable file → Normal file
253
regress/copy-mode-commands.sh
Normal file
253
regress/copy-mode-commands.sh
Normal file
@@ -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
|
||||
48
regress/copy-mode-no-client-scroll.sh
Normal file
48
regress/copy-mode-no-client-scroll.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
#!/bin/sh
|
||||
|
||||
# copy-mode -S can be called from a hook without an attached client.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
OUT=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR=$OUT
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null || true
|
||||
rm -rf "$OUT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
trap 'exit 1' 1 2 3 15
|
||||
|
||||
$TMUX new-session -d -s test 'sleep 300' || fail "new-session failed"
|
||||
$TMUX set -g remain-on-exit on || fail "setting remain-on-exit failed"
|
||||
$TMUX set-hook -g pane-died 'copy-mode -S' ||
|
||||
fail "setting pane-died hook failed"
|
||||
$TMUX respawn-pane -k -t test:0 'exit 0' || fail "respawn-pane failed"
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$TMUX has-session -t test 2>/dev/null || \
|
||||
fail "server died in copy-mode -S"
|
||||
[ "$($TMUX display-message -p -t test:0 '#{pane_dead}' 2>/dev/null)" = 1 ] && \
|
||||
break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "pane-died hook was not triggered"
|
||||
sleep 0.5
|
||||
$TMUX has-session -t test || fail "server died after copy-mode -S"
|
||||
|
||||
exit 0
|
||||
136
regress/copy-mode-selection-mode.sh
Normal file
136
regress/copy-mode-selection-mode.sh
Normal file
@@ -0,0 +1,136 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Changing an existing character selection to line mode must reset both ends
|
||||
# to complete lines, including when the cursor subsequently crosses the fixed
|
||||
# end. An invalid regular-expression search must also discard marks left by a
|
||||
# previous successful search.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
$TMUX new-session -d -x30 -y6 \
|
||||
"printf 'alpha\nbeta\ngamma\ndelta\n'; exec sleep 100" || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
sleep 1
|
||||
|
||||
# A valid regular-expression search creates marks. Replacing it with an
|
||||
# invalid expression, growing the mode, and scrolling must not leave an old
|
||||
# mark array indexed with the new geometry (the original bug was an ASan heap
|
||||
# overflow in this sequence).
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X search-forward beta || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 1 ] ||
|
||||
fail "successful search did not create marks"
|
||||
$TMUX send-keys -X search-forward '[' || exit 1
|
||||
$TMUX resize-window -x40 -y8 || exit 1
|
||||
$TMUX send-keys -X scroll-down || exit 1
|
||||
[ "$($TMUX display-message -p '#{search_present}')" = 0 ] ||
|
||||
fail "invalid regular expression left stale marks after resize"
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
|
||||
# Select part of alpha through part of beta, then change the existing
|
||||
# selection to line mode. Both lines, and no partial columns, must be copied.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
[ "$($TMUX display-message -p '#{selection_mode}')" = line ] ||
|
||||
fail "selection did not change to line mode"
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "line-mode selection did not expand to complete lines"
|
||||
|
||||
# Start with the cursor above the fixed end so selection-mode itself takes the
|
||||
# reverse-direction path, then extend it through another line.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode line || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X copy-selection || exit 1
|
||||
expected=$(printf 'alpha\nbeta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "reversed line-mode selection did not keep complete lines"
|
||||
|
||||
# Word mode must initialize its reset boundaries from the existing selection,
|
||||
# rather than extending it back to the origin on the next cursor movement.
|
||||
for mode in emacs vi; do
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
$TMUX set-window-option -g mode-keys "$mode" || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-right || exit 1
|
||||
$TMUX send-keys -X selection-mode word || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear -C || exit 1
|
||||
[ "$($TMUX show-buffer)" = beta ] ||
|
||||
fail "$mode: word-mode selection did not expand to the current word"
|
||||
$TMUX send-keys -X cursor-right || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear -C || exit 1
|
||||
expected=$(printf 'beta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "$mode: word-mode movement reset the selection to the origin"
|
||||
|
||||
# Reversing the character selection before changing mode must keep the
|
||||
# fixed end and its containing word when the cursor moves further back.
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-up || exit 1
|
||||
$TMUX send-keys -X selection-mode word || exit 1
|
||||
$TMUX send-keys -X cursor-left || exit 1
|
||||
$TMUX send-keys -X copy-selection-no-clear -C || exit 1
|
||||
expected=$(printf 'alpha\nbeta\ngamma')
|
||||
[ "$($TMUX show-buffer)" = "$expected" ] ||
|
||||
fail "$mode: reversed word-mode selection lost its fixed end"
|
||||
|
||||
# A stopped selection should expand to words without moving its cursor.
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X cursor-down || exit 1
|
||||
$TMUX send-keys -N2 -X cursor-right || exit 1
|
||||
$TMUX send-keys -X begin-selection || exit 1
|
||||
$TMUX send-keys -X cursor-right || exit 1
|
||||
$TMUX send-keys -X stop-selection || exit 1
|
||||
before=$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')
|
||||
$TMUX send-keys -X selection-mode word || exit 1
|
||||
[ "$($TMUX display-message -p '#{copy_cursor_x},#{copy_cursor_y}')" = \
|
||||
"$before" ] || fail "$mode: word-mode switch moved a stopped cursor"
|
||||
$TMUX send-keys -X copy-selection-no-clear -C || exit 1
|
||||
[ "$($TMUX show-buffer)" = beta ] ||
|
||||
fail "$mode: stopped word-mode selection did not contain the whole word"
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -110,4 +110,92 @@ wheel 64 5 5
|
||||
$TMUX send-keys -X copy-selection-no-clear || exit 1
|
||||
expect_buffer "$extended_start"
|
||||
|
||||
# Perform a mouse drag across a sequence of col,row coordinates (1-based).
|
||||
# SGR mouse format: \033[<btn;col;rowM (btn 0=press, 32=drag) and
|
||||
# \033[<0;col;rowm (release).
|
||||
drag()
|
||||
{
|
||||
# Mouse button 1 down (press) at the starting coordinate.
|
||||
col=${1%,*}
|
||||
row=${1#*,}
|
||||
seq=$(printf '\033[<0;%s;%sM' "$col" "$row")
|
||||
shift
|
||||
# Mouse drag (button 1 held) across each subsequent coordinate.
|
||||
for pos; do
|
||||
col=${pos%,*}
|
||||
row=${pos#*,}
|
||||
seq="$seq$(printf '\033[<32;%s;%sM' "$col" "$row")"
|
||||
done
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
# Keep the button held briefly so auto-scrolling can run, and
|
||||
# capture copy-mode state before mouse release exits copy mode.
|
||||
sleep 0.5
|
||||
scroll_pos=$($TMUX display -p '#{scroll_position}')
|
||||
cursor_y=$($TMUX display -p '#{copy_cursor_y}')
|
||||
# Mouse button 1 up (release) at the final coordinate.
|
||||
seq=$(printf '\033[<0;%s;%sm' "$col" "$row")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
}
|
||||
|
||||
# Turn off status line so all 10 pane rows are visible in the outer client,
|
||||
# and write a 24-char line on the bottom row (row 10, longer than the 18-char
|
||||
# line 79 above it).
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX send-keys -X cancel || exit 1
|
||||
$TMUX send-keys -l "line 80 xxxxxxxxxxxxxxxx" || exit 1
|
||||
sleep 0.2
|
||||
|
||||
for mode in emacs vi; do
|
||||
$TMUX setw -g mode-keys "$mode" || exit 1
|
||||
|
||||
# In emacs mode the selection end column is exclusive, so selecting all
|
||||
# characters of an 18- or 24-char line requires placing the cursor one
|
||||
# column past the last character; in vi mode the end column is inclusive.
|
||||
if [ "$mode" = emacs ]; then
|
||||
ecol18=19
|
||||
ecol24=25
|
||||
else
|
||||
ecol18=18
|
||||
ecol24=24
|
||||
fi
|
||||
|
||||
# Drag along the bottom row (row 10) past the end of the line and back
|
||||
# when already at the bottom of history; the selection must stay on
|
||||
# line 80 instead of jumping up to line 79.
|
||||
$TMUX copy-mode || exit 1
|
||||
drag 1,10 "$ecol24,10" "$((ecol24 + 1)),10" "$ecol24,10"
|
||||
expect_buffer "line 80 xxxxxxxxxxxxxxxx"
|
||||
|
||||
# Scroll up 1 line so line 80 is off-screen below, then drag from row 9
|
||||
# to the bottom row (row 10) to auto-scroll down and select lines 78..80.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X scroll-up || exit 1
|
||||
drag 1,9 23,10 "$ecol24,10"
|
||||
expect_buffer "$(printf 'line 78 xxxxxxxxxx\nline 79 xxxxxxxxxx\nline 80 xxxxxxxxxxxxxxxx')"
|
||||
|
||||
# Scroll 1 line below the top of history so line 00 is off-screen above,
|
||||
# then drag from row 2 to the top row (row 1) to auto-scroll up and
|
||||
# select lines 00..02.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
$TMUX send-keys -X scroll-down || exit 1
|
||||
drag "$ecol18,2" 1,1
|
||||
expect_buffer "$(printf 'line 00 xxxxxxxxxx\nline 01 xxxxxxxxxx\nline 02 xxxxxxxxxx')"
|
||||
|
||||
# Scroll up 5 lines, then drag onto the bottom row and keep the button
|
||||
# held to auto-scroll all the way to the bottom.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -N5 -X scroll-up || exit 1
|
||||
drag 1,9 5,10
|
||||
[ "$scroll_pos" = 0 ] || fail "$mode: drag auto-scroll stopped at $scroll_pos"
|
||||
|
||||
# At the top of history, drag onto the top row and keep the button
|
||||
# held: the cursor must stay on the top row.
|
||||
$TMUX copy-mode || exit 1
|
||||
$TMUX send-keys -X history-top || exit 1
|
||||
drag 5,2 5,1
|
||||
[ "$cursor_y" = 0 ] || fail "$mode: cursor left the top row (row $cursor_y)"
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
||||
63
regress/copy-mode-source-blank.sh
Normal file
63
regress/copy-mode-source-blank.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Trimming a blank source must leave a visible line for cursor reflow.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 3 15
|
||||
|
||||
$TMUX new-session -d -x80 -y24 -s test 'sleep 100' || exit 1
|
||||
$TMUX set-option -g window-size manual || exit 1
|
||||
$TMUX new-window -t test:1 'sleep 100' || exit 1
|
||||
$TMUX resize-window -t test:0 -x20 -y24 || exit 1
|
||||
|
||||
# Exercise widening, narrowing, and equal widths with no source history.
|
||||
for pair in '0 1' '1 0' '1 2'; do
|
||||
set -- $pair
|
||||
if [ "$2" = 2 ]; then
|
||||
$TMUX new-window -t test:2 'sleep 100' || exit 1
|
||||
$TMUX resize-window -t test:2 -x80 -y24 || exit 1
|
||||
fi
|
||||
source=test:$1.0
|
||||
target=test:$2.0
|
||||
[ "$($TMUX display-message -p -t "$source" '#{history_size}')" = 0 ] ||
|
||||
fail "source has unexpected history"
|
||||
$TMUX copy-mode -s "$source" -t "$target" || exit 1
|
||||
$TMUX has-session -t test || fail "server died copying blank source"
|
||||
[ "$($TMUX display-message -p -t "$target" \
|
||||
'#{pane_in_mode} #{copy_cursor_x} #{copy_cursor_y}')" = '1 0 0' ] ||
|
||||
fail "blank source did not enter copy mode at the first cell"
|
||||
$TMUX send-keys -t "$target" -X cancel || exit 1
|
||||
done
|
||||
|
||||
# Real content still trims trailing empty lines, clamping the cursor to beta.
|
||||
$TMUX respawn-pane -k -t test:0.0 \
|
||||
"printf 'alpha\r\nbeta\r\n\r\n\r\n'; exec sleep 100" || exit 1
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t test:0.0 '#{cursor_y}')" != 4 ]; do
|
||||
i=$((i + 1))
|
||||
[ "$i" -lt 10 ] || fail "source output did not arrive"
|
||||
sleep 1
|
||||
done
|
||||
$TMUX copy-mode -s test:0.0 -t test:1.0 || exit 1
|
||||
[ "$($TMUX display-message -p -t test:1.0 \
|
||||
'#{pane_in_mode} #{copy_cursor_y} #{copy_cursor_line}')" = '1 1 beta' ] ||
|
||||
fail "nonblank source content or trailing-line trimming changed"
|
||||
|
||||
exit 0
|
||||
0
regress/copy-mode-unicode-whitespace.sh
Executable file → Normal file
0
regress/copy-mode-unicode-whitespace.sh
Executable file → Normal file
149
regress/copy-mode-word-redraw.sh
Normal file
149
regress/copy-mode-word-redraw.sh
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Word selection can extend beyond the cursor's row. Compare what an attached
|
||||
# client drew with a full copy-mode repaint after extending and shrinking it.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
INNER="$TEST_TMUX -Lword-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lword-outer-$$ -f/dev/null"
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
exit_status=0
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 3 15
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_buffer()
|
||||
{
|
||||
$INNER send-keys -X copy-selection-no-clear || exit 1
|
||||
$INNER save-buffer "$DIR/copied" || exit 1
|
||||
cmp -s "$DIR/expected" "$DIR/copied" || fail 'unexpected copied text'
|
||||
}
|
||||
|
||||
check_redraw()
|
||||
{
|
||||
label=$1
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -ep -t outer:0.0 >"$DIR/before" || exit 1
|
||||
$INNER send-keys -X toggle-position || exit 1
|
||||
$INNER send-keys -X toggle-position || exit 1
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -ep -t outer:0.0 >"$DIR/after" || exit 1
|
||||
if ! cmp -s "$DIR/before" "$DIR/after"; then
|
||||
diff -u "$DIR/before" "$DIR/after" >&2
|
||||
echo "$label: stale word-selection highlight" >&2
|
||||
exit_status=1
|
||||
fi
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x20 -y8 'sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mode-keys emacs || exit 1
|
||||
$INNER set-option -g copy-mode-position-format '' || exit 1
|
||||
$INNER set-option -g copy-mode-selection-style 'fg=white,bg=red' || exit 1
|
||||
$OUTER new-session -d -s outer -x20 -y8 "$INNER attach -t inner" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
sleep 0.5
|
||||
|
||||
# A wrapped word ending on the penultimate row. In emacs mode end-of-line
|
||||
# moves onto the exclusive end and extends the selection into the blank row
|
||||
# below; start-of-line then removes that row from the selection.
|
||||
awk 'BEGIN { print "PREFIX"; for (i = 0; i < 115; i++) printf "a"; print "" }' \
|
||||
>"$DIR/text" || exit 1
|
||||
$INNER new-window -t inner:1 "cat '$DIR/text'; exec sleep 100" || exit 1
|
||||
$INNER resize-window -x20 -y8 || exit 1
|
||||
sleep 0.2
|
||||
$INNER copy-mode || exit 1
|
||||
$INNER send-keys -X history-top || exit 1
|
||||
$INNER send-keys -X cursor-down || exit 1
|
||||
$INNER send-keys -X select-word || exit 1
|
||||
$INNER send-keys -X end-of-line || exit 1
|
||||
[ "$($INNER display-message -p '#{selection_end_y}')" = 7 ] ||
|
||||
fail 'selection did not extend into the blank row'
|
||||
check_redraw 'extend onto blank row'
|
||||
awk 'BEGIN { for (i = 0; i < 115; i++) printf "a"; print "" }' \
|
||||
>"$DIR/expected" || exit 1
|
||||
check_buffer
|
||||
$INNER send-keys -X start-of-line || exit 1
|
||||
[ "$($INNER display-message -p '#{selection_end_y}')" = 6 ] ||
|
||||
fail 'selection did not shrink away from the blank row'
|
||||
check_redraw 'shrink away from blank row'
|
||||
awk 'BEGIN { for (i = 0; i < 115; i++) printf "a" }' \
|
||||
>"$DIR/expected" || exit 1
|
||||
check_buffer
|
||||
|
||||
# The selected next word can itself wrap across several rows even though the
|
||||
# cursor only moved within one row. All of those rows must be repainted.
|
||||
awk 'BEGIN { print "PREFIX"; print "one";
|
||||
for (i = 0; i < 65; i++) printf "b"; print ""; print "END" }' \
|
||||
>"$DIR/text" || exit 1
|
||||
$INNER new-window -t inner:2 "cat '$DIR/text'; exec sleep 100" || exit 1
|
||||
$INNER resize-window -x20 -y8 || exit 1
|
||||
sleep 0.2
|
||||
$INNER copy-mode || exit 1
|
||||
$INNER send-keys -X history-top || exit 1
|
||||
$INNER send-keys -X cursor-down || exit 1
|
||||
$INNER send-keys -X select-word || exit 1
|
||||
$INNER send-keys -X end-of-line || exit 1
|
||||
[ "$($INNER display-message -p '#{selection_end_y}')" = 5 ] ||
|
||||
fail 'selection did not extend over the wrapped next word'
|
||||
check_redraw 'extend over a wrapped next word'
|
||||
awk 'BEGIN { print "one"; for (i = 0; i < 65; i++) printf "b" }' \
|
||||
>"$DIR/expected" || exit 1
|
||||
check_buffer
|
||||
$INNER send-keys -X start-of-line || exit 1
|
||||
check_redraw 'shrink away from a wrapped next word'
|
||||
printf 'one' >"$DIR/expected" || exit 1
|
||||
check_buffer
|
||||
|
||||
# Drag a word selection onto the bottom row and back while there is still
|
||||
# history below the viewport. A redraw must not read the next backing row
|
||||
# and paint it onto the last visible row.
|
||||
awk 'BEGIN { for (i = 0; i < 30; i++) printf "line%02d abc def\n", i }' \
|
||||
>"$DIR/text" || exit 1
|
||||
$INNER new-window -t inner:3 "cat '$DIR/text'; exec sleep 100" || exit 1
|
||||
$INNER resize-window -x20 -y8 || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER unbind-key -Tcopy-mode MouseDragEnd1Pane || exit 1
|
||||
sleep 0.2
|
||||
$INNER copy-mode || exit 1
|
||||
$INNER send-keys -X history-top || exit 1
|
||||
$INNER send-keys -X cursor-down || exit 1
|
||||
$INNER send-keys -X select-word || exit 1
|
||||
$OUTER send-keys -l "$(printf '\033[<0;3;2M')" || exit 1
|
||||
sleep 0.1
|
||||
$OUTER send-keys -l "$(printf '\033[<32;8;7M')" || exit 1
|
||||
sleep 0.1
|
||||
$INNER send-keys -X selection-mode word || exit 1
|
||||
$OUTER send-keys -l "$(printf '\033[<32;8;8M')" || exit 1
|
||||
sleep 0.1
|
||||
# Leave the edge to cancel the auto-scroll timer before comparing.
|
||||
$OUTER send-keys -l "$(printf '\033[<32;8;7M')" || exit 1
|
||||
sleep 0.1
|
||||
$OUTER send-keys -l "$(printf '\033[<0;8;7m')" || exit 1
|
||||
sleep 0.1
|
||||
[ "$($INNER display-message -p '#{selection_mode}')" = word ] ||
|
||||
fail 'mouse drag did not retain word selection mode'
|
||||
[ "$($INNER display-message -p '#{scroll_position}')" -gt 0 ] ||
|
||||
fail 'mouse drag reached the bottom of history'
|
||||
check_redraw 'drag word selection at bottom of viewport'
|
||||
|
||||
exit "$exit_status"
|
||||
66
regress/display-message-client.sh
Executable file
66
regress/display-message-client.sh
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
|
||||
# display-message -c must take the client formats from the given client, even
|
||||
# when it is attached to a different session from the target pane.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
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
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
cleanup
|
||||
$INNER new-session -d -s one -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER new-session -d -s two -x40 -y10 'exec sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
|
||||
# Each client has a distinct TERM so that client_termname identifies it.
|
||||
attach()
|
||||
{
|
||||
echo "env -i PATH=/bin:/usr/bin TERM=$1 LC_ALL=C.UTF-8 $TEST_TMUX -LtestI$$ -f/dev/null attach-session -t $2"
|
||||
}
|
||||
$OUTER new-session -d -s outer -x40 -y10 "$(attach screen one)" || exit 1
|
||||
$OUTER new-window -d "$(attach xterm two)" || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
n=$($INNER list-clients -F '#{client_name}' 2>/dev/null|wc -l)
|
||||
[ "$n" -eq 2 ] && break
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$i" -lt 50 ] || fail "clients did not attach"
|
||||
|
||||
c1=$($INNER list-clients -F '#{client_name}' -f '#{==:#{client_termname},screen}')
|
||||
c2=$($INNER list-clients -F '#{client_name}' -f '#{==:#{client_termname},xterm}')
|
||||
[ -n "$c1" ] && [ -n "$c2" ] || fail "could not find both clients"
|
||||
|
||||
# The target is in session two, but the client is the one in session one.
|
||||
term=$($INNER display-message -c "$c1" -t two: -p '#{client_termname}')
|
||||
[ "$term" = screen ] || fail "client_termname was '$term', not screen"
|
||||
|
||||
# The session formats still come from the target.
|
||||
session=$($INNER display-message -c "$c1" -t two: -p '#{session_name}')
|
||||
[ "$session" = two ] || fail "session_name was '$session', not two"
|
||||
|
||||
term=$($INNER display-message -c "$c2" -t one: -p '#{client_termname}')
|
||||
[ "$term" = xterm ] || fail "client_termname was '$term', not xterm"
|
||||
|
||||
exit 0
|
||||
@@ -153,11 +153,13 @@ wait_format "$p0" '#{pane_in_mode}' '0'
|
||||
wait_option @picked "$p1"
|
||||
|
||||
# Commands after display-panes run immediately while the mode remains.
|
||||
# Record the mode in the same command queue so client startup cannot race
|
||||
# the 500ms timeout.
|
||||
$TMUX set -g @after none || fail "set @after failed"
|
||||
$TMUX display-panes -Nd 500 -t "$p0" \; set -g @after fast ||
|
||||
$TMUX display-panes -Nd 500 -t "$p0" \; \
|
||||
set -gF -t "$p0" @after '#{pane_mode}' ||
|
||||
fail "display-panes immediate command failed"
|
||||
wait_option @after fast
|
||||
wait_format "$p0" '#{pane_mode}' 'panes-mode'
|
||||
wait_option @after 'panes-mode'
|
||||
wait_format "$p0" '#{pane_in_mode}' '0'
|
||||
|
||||
# Existing zoom is restored on exit.
|
||||
|
||||
118
regress/find-window.sh
Normal file
118
regress/find-window.sh
Normal file
@@ -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
|
||||
135
regress/floating-pane-border-clients.sh
Executable file
135
regress/floating-pane-border-clients.sh
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage-only redraws must evaluate both active and inactive border styles
|
||||
# for each client, even though the cached border cells belong to the pane.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lborder-style-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lborder-style-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
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
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_for_marker()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client in $target did not receive $marker"
|
||||
}
|
||||
|
||||
cat >"$DIR/emitter.pl" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $phase (1 .. 2) {
|
||||
while (!-e "$ENV{TRIGGER}-$phase") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
# Change an unused palette entry and acknowledge it in the pane body.
|
||||
print "\e]4;200;rgb:11/22/0$phase\a\e[1;1HDAMAGE$phase";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 \
|
||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g automatic-rename off || exit 1
|
||||
$INNER set -g status-interval 0 || exit 1
|
||||
$INNER set -g pane-border-lines simple || exit 1
|
||||
$INNER set -g pane-border-status top || exit 1
|
||||
$INNER set -g pane-border-format 'CLIENT=#{client_name}' || exit 1
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 35 -y 6 -X 5 -Y 5 \
|
||||
'sleep 100') || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set -g status off || exit 1
|
||||
$OUTER set -g window-size manual || exit 1
|
||||
$OUTER set -g default-terminal screen || exit 1
|
||||
LEFT=$($OUTER display-message -p -t outer:0.0 '#{pane_id}') || exit 1
|
||||
RIGHT=$($OUTER split-window -h -PF '#{pane_id}' 'sleep 100') || exit 1
|
||||
for target in "$LEFT" "$RIGHT"; do
|
||||
$OUTER respawn-pane -k -t "$target" "$INNER attach-session -t inner" ||
|
||||
exit 1
|
||||
done
|
||||
wait_for_clients
|
||||
|
||||
NAME1=$($OUTER display-message -p -t "$LEFT" '#{pane_tty}') || exit 1
|
||||
NAME2=$($OUTER display-message -p -t "$RIGHT" '#{pane_tty}') || exit 1
|
||||
STYLE="fg=#{?#{==:#{client_name},$NAME1},red,blue}"
|
||||
$INNER set -g pane-border-style "$STYLE" || exit 1
|
||||
$INNER set -g pane-active-border-style "$STYLE" || exit 1
|
||||
|
||||
# Capture just the floating pane's rows, excluding the acknowledgement in
|
||||
# the tiled pane. Exercise each cache by changing the floating pane's focus.
|
||||
phase=1
|
||||
while [ "$phase" -le 2 ]; do
|
||||
if [ "$phase" -eq 2 ]; then
|
||||
$INNER select-pane -t "$FLOAT" || exit 1
|
||||
fi
|
||||
$INNER refresh-client -t "$NAME1" || exit 1
|
||||
$INNER refresh-client -t "$NAME2" || exit 1
|
||||
sleep 0.5
|
||||
for target in "$LEFT" "$RIGHT"; do
|
||||
$OUTER capture-pane -pe -S 5 -E 10 -t "$target" \
|
||||
>"$DIR/before-$target" || exit 1
|
||||
done
|
||||
RED=$(printf '\033[31m')
|
||||
BLUE=$(printf '\033[34m')
|
||||
grep -Fq "$RED" "$DIR/before-$LEFT" || fail "missing red border"
|
||||
grep -Fq "$BLUE" "$DIR/before-$RIGHT" || fail "missing blue border"
|
||||
|
||||
: >"$DIR/trigger-$phase"
|
||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
||||
wait_for_marker "$RIGHT" "DAMAGE$phase"
|
||||
sleep 0.2
|
||||
for target in "$LEFT" "$RIGHT"; do
|
||||
$OUTER capture-pane -pe -S 5 -E 10 -t "$target" \
|
||||
>"$CAPTURE" || exit 1
|
||||
diff -u "$DIR/before-$target" "$CAPTURE" ||
|
||||
fail "phase $phase changed $target's border style"
|
||||
done
|
||||
phase=$((phase + 1))
|
||||
done
|
||||
|
||||
exit 0
|
||||
137
regress/floating-pane-covers-tiled-pane.sh
Executable file
137
regress/floating-pane-covers-tiled-pane.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A floating pane that is larger than a tiled pane on every side (so none of
|
||||
# its edges fall inside the tiled pane) must still hide that pane: output from
|
||||
# the covered pane must not be drawn over the floating pane.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lcovers-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lcovers-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
FILLER=$DIR/filler.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t "$2" 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane $2 did not contain $marker"
|
||||
}
|
||||
|
||||
# Covered pane: waits for the trigger, then scrolls a small region.
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
# Scroll a small region: this is redrawn row by row rather than by a large
|
||||
# region redraw, so it must not draw over the floating pane.
|
||||
print "\e[1;2r\e[2;1HCOVEREDCOVERED\n";
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# Floating pane: fill it so any overwriting is visible.
|
||||
cat >"$FILLER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 8) {
|
||||
print "\e[$row;1H", 'F' x 30;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 'sleep 100' || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
|
||||
# Three rows of panes; the middle row is short and split into three so that
|
||||
# the middle pane has other panes on both sides and above and below.
|
||||
$INNER split-window -d -v -t inner:0.0 'sleep 100' || exit 1
|
||||
$INNER split-window -d -v -t inner:0.1 'sleep 100' || exit 1
|
||||
$INNER resize-pane -t inner:0.0 -y 6 || exit 1
|
||||
$INNER resize-pane -t inner:0.1 -y 4 || exit 1
|
||||
MID=$($INNER split-window -d -h -P -F '#{pane_id}' -l 20 -t inner:0.1 \
|
||||
'sleep 100') || exit 1
|
||||
$INNER split-window -d -h -l 20 -t inner:0.1 'sleep 100' || exit 1
|
||||
|
||||
# The covered pane is the one in the middle of the short row.
|
||||
set -- $($INNER list-panes -F '#{pane_left} #{pane_id}' -f '#{==:#{pane_top},7}' |
|
||||
sort -n | awk '{ print $2 }')
|
||||
[ $# -eq 3 ] || fail "expected three panes in the middle row, got $#"
|
||||
COVERED=$2
|
||||
X=$($INNER display-message -p -t "$COVERED" '#{pane_left}')
|
||||
Y=$($INNER display-message -p -t "$COVERED" '#{pane_top}')
|
||||
W=$($INNER display-message -p -t "$COVERED" '#{pane_width}')
|
||||
H=$($INNER display-message -p -t "$COVERED" '#{pane_height}')
|
||||
|
||||
# Bigger than the covered pane on all four sides.
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x $((W + 6)) -y $((H + 4)) \
|
||||
-X $((X - 3)) -Y $((Y - 2)) "perl '$FILLER'") || fail "new-pane failed"
|
||||
|
||||
$INNER respawn-pane -k -t "$COVERED" "TRIGGER='$TRIGGER' perl '$EMITTER'" ||
|
||||
exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lcovers-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_outer_has FFFFFFFFFF
|
||||
sleep 1
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has COVERED "$COVERED"
|
||||
sleep 1
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE"
|
||||
grep -q COVERED "$CAPTURE" && fail "covered pane drawn over floating pane"
|
||||
|
||||
exit 0
|
||||
88
regress/floating-pane-drag-composite.sh
Executable file
88
regress/floating-pane-drag-composite.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Each drag motion must draw the floating pane content once.
|
||||
|
||||
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
|
||||
cd "$DIR" || exit 1
|
||||
INNER="$TEST_TMUX -Ldoublecomp-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Ldoublecomp-outer-$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
cd /
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.15
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 15 "printf '\\033[15;1HOUTSIDE'; 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 -g mouse on || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
$INNER set-option -g automatic-rename off || exit 1
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
||||
"printf 'DRAGMARK'; exec sleep 100") || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 15 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Ldoublecomp-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
sleep 0.5
|
||||
|
||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
GRABCOL=$((XOFF + 3))
|
||||
BORDERROW=$YOFF
|
||||
|
||||
# Begin capture after focus changes from the initial mouse press have settled.
|
||||
mouse 0 "$GRABCOL" "$BORDERROW" M
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$DIR/output'" || exit 1
|
||||
$INNER refresh-client || exit 1
|
||||
sleep 0.5
|
||||
grep -aq DRAGMARK "$DIR/output" || fail "capture missed floating pane content"
|
||||
grep -aq OUTSIDE "$DIR/output" || fail "capture missed untouched row"
|
||||
offset=$(wc -c <"$DIR/output")
|
||||
|
||||
i=0
|
||||
steps=6
|
||||
while [ "$i" -lt "$steps" ]; do
|
||||
GRABCOL=$((GRABCOL + 1))
|
||||
mouse 32 "$GRABCOL" "$BORDERROW" M
|
||||
i=$((i + 1))
|
||||
done
|
||||
mouse 0 "$GRABCOL" "$BORDERROW" m
|
||||
sleep 0.3
|
||||
|
||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
[ "$NEWXOFF" -eq "$((XOFF + steps))" ] || fail "floating pane did not move six columns"
|
||||
tail -c +"$((offset + 1))" "$DIR/output" >"$DIR/drag-output"
|
||||
n=$(perl -0777 -ne '$n = () = /DRAGMARK/g; print "$n\n"' "$DIR/drag-output")
|
||||
[ "$n" -eq "$steps" ] ||
|
||||
fail "$steps drag motions wrote the floating pane content $n times"
|
||||
|
||||
exit 0
|
||||
108
regress/floating-pane-drag-ghost.sh
Executable file
108
regress/floating-pane-drag-ghost.sh
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test for a floating-pane drag bug: cmd_resize_pane_redraw_floating()
|
||||
# (cmd-resize-pane.c) reported damage for just a dragged floating pane's
|
||||
# content rectangle, not the one-cell border frame drawn around it (see the
|
||||
# "floating" case in screen-redraw.c, which draws that frame at
|
||||
# xoff-1/yoff-1 through xoff+sx/yoff+sy - one cell outside the pane's own
|
||||
# content area). Damage scoped to only the content area left the frame's
|
||||
# previous position undrawn as the pane moved, so dragging it left a trail
|
||||
# of un-erased border frames behind - visible as several "corners" stacked
|
||||
# up rather than just the pane's current one.
|
||||
#
|
||||
# This bug has nothing to do with images - it reproduces with a plain
|
||||
# floating pane and no image support required.
|
||||
|
||||
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"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
#
|
||||
# Write a plain (unmodified) SGR button-1 press, drag update and release at
|
||||
# 1-based positions to the outer pane holding the inner client - this
|
||||
# matches the default MouseDown1Border/MouseDrag1Border bindings used to
|
||||
# move or resize a floating pane by its border.
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 1
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g mouse on
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"'
|
||||
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
|
||||
$TMUX2 new-session -d -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
|
||||
# Sanity check: exactly one floating pane, so exactly one top-left corner,
|
||||
# before dragging anything.
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "sanity: expected 1 corner before drag, found $n"
|
||||
|
||||
# Drag the floating pane by its top border (row FTOP-1, some column within
|
||||
# its width) down several rows in a few separate steps, then release. A
|
||||
# single drag() call already does press/motion/release, so call it several
|
||||
# times in a row to simulate a multi-step real drag.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
STARTROW=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((STARTROW + i + 1))
|
||||
drag $((GRABCOL + 1)) $((STARTROW + i)) $((GRABCOL + 1)) $newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
$TMUX2 capturep -p -t "$OUTER" >$TMP || fail "capture failed"
|
||||
|
||||
# Exactly one top-left corner should remain - the pane's current position.
|
||||
# This is expected to fail before the fix: multiple corners (a trail of
|
||||
# un-erased frames) would remain from the intermediate drag positions.
|
||||
n=$(grep -o '┌' $TMP | wc -l)
|
||||
[ "$n" -eq 1 ] || fail "expected exactly 1 corner after drag, found $n (ghost frames left behind)"
|
||||
|
||||
exit 0
|
||||
160
regress/floating-pane-drag-sb-strip.sh
Executable file
160
regress/floating-pane-drag-sb-strip.sh
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Regression test: dragging a floating pane across another pane's ordinary
|
||||
# content must not redraw that other pane's scrollbar, unless the drag
|
||||
# actually crosses the scrollbar's own strip.
|
||||
#
|
||||
# cmd_resize_pane_redraw_floating() (cmd-resize-pane.c) used to flag
|
||||
# PANE_REDRAWSCROLLBAR on any pane whose whole *body* intersected the
|
||||
# floating pane's old or new rectangle, rather than just its narrow
|
||||
# scrollbar strip - so dragging a floating pane back and forth over an
|
||||
# ordinary tiled pane's content (never touching its scrollbar) still
|
||||
# needlessly redrew that pane's scrollbar on every motion step. See
|
||||
# tmux-image-redraw-known-bugs.md for the full write-up.
|
||||
#
|
||||
# This is checked by giving the non-dragged pane a distinctive scrollbar
|
||||
# colour and counting how many times its SGR code appears in the client's
|
||||
# raw output while the floating pane is dragged vertically over that pane's
|
||||
# body, well clear of its scrollbar column: with the fix, it should never
|
||||
# reappear after the initial draw.
|
||||
|
||||
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"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
# drag STARTCOL STARTROW ENDCOL ENDROW
|
||||
drag()
|
||||
{
|
||||
scol="$1"
|
||||
srow="$2"
|
||||
ecol="$3"
|
||||
erow="$4"
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$scol" "$srow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$ecol" "$erow")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "cleanup; rm -f $TMP" 0 1 15
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sh -c "sleep 100"' || exit 1
|
||||
$TMUX set -g status-right '' || fail "set status-right failed"
|
||||
$TMUX set -g mouse on || fail "set mouse failed"
|
||||
$TMUX set -g default-command 'sh -c "sleep 100"' || fail "set default-command failed"
|
||||
$TMUX set -g pane-scrollbars on || fail "set pane-scrollbars failed"
|
||||
|
||||
$TMUX split-window -h -t inner 'sh -c "sleep 100"' || fail "split-window failed"
|
||||
|
||||
PANES=$($TMUX list-panes -t inner -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] || fail "could not identify left pane"
|
||||
|
||||
# A distinctive scrollbar colour for the non-dragged (left) pane only.
|
||||
$TMUX set -p -t "$LEFT" pane-scrollbars-style 'fg=colour201,bg=colour17' ||
|
||||
fail "set pane-scrollbars-style failed"
|
||||
|
||||
ALEFT=$($TMUX display-message -p -t "$LEFT" '#{pane_left}')
|
||||
ATOP=$($TMUX display-message -p -t "$LEFT" '#{pane_top}')
|
||||
AWIDTH=$($TMUX display-message -p -t "$LEFT" '#{pane_width}')
|
||||
AHEIGHT=$($TMUX display-message -p -t "$LEFT" '#{pane_height}')
|
||||
[ "$AWIDTH" -gt 15 ] || fail "left pane too narrow for this test ($AWIDTH)"
|
||||
|
||||
# A small floating pane placed well inside the left pane's content area,
|
||||
# clear of its (right-hand) scrollbar column by several columns.
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 8 -y 5 \
|
||||
-X $((ALEFT + 2)) -Y $((ATOP + 2))) || fail "new-pane -X -Y failed"
|
||||
FTOP=$($TMUX display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($TMUX display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ $((FLEFT + FWIDTH + 3)) -lt $((ALEFT + AWIDTH)) ] ||
|
||||
fail "sanity: floating pane too close to the scrollbar column"
|
||||
|
||||
# Start the outer session with a plain shell, then start capturing before
|
||||
# triggering the attach - starting the attach as the outer pane's initial
|
||||
# command would mean pipe-pane only starts after the attach-driven initial
|
||||
# redraw (which draws the scrollbars) has already happened, missing it.
|
||||
$TMUX2 new-session -d -x 60 -y 20 || exit 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "No outer pane."
|
||||
$TMUX2 pipe-pane -t "$OUTER" -O "cat >$TMP" || fail "pipe-pane failed"
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$TMUX attach -t inner" || fail "send attach failed"
|
||||
$TMUX2 send-keys -t "$OUTER" Enter || fail "send enter failed"
|
||||
sleep 1
|
||||
|
||||
# Sanity check: the distinctive scrollbar colour reaches the client at all.
|
||||
grep -qa '48;5;201' $TMP || fail "sanity: scrollbar colour never reached the client"
|
||||
: >$TMP
|
||||
|
||||
# Drag the floating pane straight up and down by its top border, staying at
|
||||
# a fixed column the whole time - this never crosses the left pane's
|
||||
# scrollbar strip, only its ordinary content.
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2))
|
||||
row=$FTOP
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row + 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
i=0
|
||||
while [ $i -lt 6 ]; do
|
||||
newrow=$((row - 1))
|
||||
drag $GRABCOL $row $GRABCOL $newrow
|
||||
row=$newrow
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# The scrollbar colour should never reappear - its geometry never changed,
|
||||
# and the drag never crossed its column. This is expected to fail before
|
||||
# the fix - see the header comment.
|
||||
n=$(grep -ac '48;5;201' $TMP)
|
||||
[ "$n" -eq 0 ] ||
|
||||
fail "left pane's scrollbar was redrawn $n times while dragging over its body only"
|
||||
|
||||
# Cross the scrollbar and return, comparing each damaged redraw with a full
|
||||
# refresh to check that covering and uncovering its cells leaves no artifacts.
|
||||
offset=$((ALEFT + AWIDTH - FLEFT - FWIDTH / 2))
|
||||
for delta in "$offset" "$((-offset))"; do
|
||||
oldleft=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
newcol=$((GRABCOL + delta))
|
||||
drag "$GRABCOL" "$row" "$newcol" "$row"
|
||||
GRABCOL=$newcol
|
||||
newleft=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
[ "$newleft" -eq "$((oldleft + delta))" ] ||
|
||||
fail "floating pane did not move across the scrollbar as expected"
|
||||
before=$($TMUX2 capture-pane -pe -t "$OUTER" -S 0 -E "$((AHEIGHT - 1))")
|
||||
$TMUX refresh-client || fail "refresh-client failed"
|
||||
sleep 0.5
|
||||
after=$($TMUX2 capture-pane -pe -t "$OUTER" -S 0 -E "$((AHEIGHT - 1))")
|
||||
[ "$before" = "$after" ] ||
|
||||
fail "scrollbar crossing differs from a full redraw"
|
||||
done
|
||||
|
||||
exit 0
|
||||
93
regress/floating-pane-drag-sync.sh
Executable file
93
regress/floating-pane-drag-sync.sh
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Synchronized drag output must not cause a full redraw of untouched rows.
|
||||
|
||||
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
|
||||
cd "$DIR" || exit 1
|
||||
INNER="$TEST_TMUX -Lsyncdefer-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lsyncdefer-outer-$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
cd /
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.15
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 15 "printf '\\033[15;1HOUTSIDE'; 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 -g mouse on || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
$INNER set-option -g automatic-rename off || exit 1
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X 5 -Y 2 \
|
||||
"printf 'DRAGMARK'; exec sleep 100") || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 15 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$INNER set-option -as terminal-features ',screen-256color:sync' || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lsyncdefer-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
sleep 0.5
|
||||
|
||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
GRABCOL=$((XOFF + 3))
|
||||
BORDERROW=$YOFF
|
||||
|
||||
# Begin capture after focus changes from the initial mouse press have settled.
|
||||
mouse 0 "$GRABCOL" "$BORDERROW" M
|
||||
$OUTER pipe-pane -O -t outer:0.0 "cat >'$DIR/output'" || exit 1
|
||||
$INNER refresh-client || exit 1
|
||||
sleep 0.5
|
||||
grep -aq DRAGMARK "$DIR/output" || fail "capture missed floating pane content"
|
||||
grep -aq OUTSIDE "$DIR/output" || fail "capture missed untouched row"
|
||||
offset=$(wc -c <"$DIR/output")
|
||||
|
||||
i=0
|
||||
steps=6
|
||||
while [ "$i" -lt "$steps" ]; do
|
||||
GRABCOL=$((GRABCOL + 1))
|
||||
mouse 32 "$GRABCOL" "$BORDERROW" M
|
||||
i=$((i + 1))
|
||||
done
|
||||
mouse 0 "$GRABCOL" "$BORDERROW" m
|
||||
sleep 0.3
|
||||
|
||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
[ "$NEWXOFF" -eq "$((XOFF + steps))" ] || fail "floating pane did not move six columns"
|
||||
tail -c +"$((offset + 1))" "$DIR/output" >"$DIR/drag-output"
|
||||
n=$(perl -0777 -ne '$n = () = /DRAGMARK/g; print "$n\n"' "$DIR/drag-output")
|
||||
[ "$n" -ge "$steps" ] || fail "drag output did not reach the client"
|
||||
perl -0777 -ne 'exit(/\e\[\?2026h/ ? 0 : 1)' "$DIR/drag-output" ||
|
||||
fail "drag did not use synchronized output"
|
||||
if grep -aq OUTSIDE "$DIR/drag-output"; then
|
||||
fail "synchronized drag redrew an untouched row"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
200
regress/floating-pane-drag-wide-character.sh
Executable file
200
regress/floating-pane-drag-wide-character.sh
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage at a floating pane's vacated edge must always redraw a complete
|
||||
# grid character. redraw_damage_grow_span_clip() (screen-redraw.c) widens a
|
||||
# damage rectangle's left edge by one cell whenever it isn't already at the
|
||||
# span's own edge, to pull in a wide character's base half when the edge
|
||||
# lands on its padding half - but it did this unconditionally, with no
|
||||
# check of which half it was actually touching. When the edge instead
|
||||
# already lands cleanly on a fresh character's base cell, growing left
|
||||
# walks into the *previous*, unrelated character's padding cell and blanks
|
||||
# it (tty_draw_line() treats any leading padding cell as proof its own
|
||||
# range starts mid-character).
|
||||
#
|
||||
# This is a general damage-composition bug, not specific to any one kind of
|
||||
# pane, but the exact column parity needs to be deterministic to actually
|
||||
# catch it (a lucky parity draws fine). This constructs it by creating the
|
||||
# floating pane, checking its real position (the border-framing offset
|
||||
# added to -X is not something to hand-compute), and recreating it one
|
||||
# column over if necessary until the vacated rectangle's left edge lands on
|
||||
# a base cell.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lwidechar-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
FLOAT=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
slice_columns()
|
||||
{
|
||||
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
||||
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
||||
# this range) must not affect the comparison, so this only looks at
|
||||
# the narrow strip actually vacated, not the whole line. capture-pane
|
||||
# text has one decoded character per double-width cell pair (every
|
||||
# character here is width 2), so terminal columns are converted to
|
||||
# character indices by halving before slicing.
|
||||
perl -CSD -e '
|
||||
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
||||
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
||||
my @lines = <$fh>;
|
||||
my $c1 = int($col1 / 2);
|
||||
my $c2 = int(($col2 + 1) / 2);
|
||||
for my $n ($row1 .. $row2) {
|
||||
my $line = $lines[$n - 1];
|
||||
$line =~ s/\R\z//;
|
||||
print substr($line, $c1, $c2 - $c1), "\n";
|
||||
}
|
||||
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
||||
}
|
||||
|
||||
wait_old_rows_restored()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
slice_columns "$BASE" >"$DIR/want"
|
||||
slice_columns "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "wide characters under the floating pane's vacated edge were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
$| = 1;
|
||||
for my $row (1 .. 10) {
|
||||
print "\e[$row;1H", chr(0x754c) x 20;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g pane-scrollbars off || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lwidechar-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has '界界界'
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
|
||||
# Create the floating pane, then check its actual resulting position. Try
|
||||
# adjacent starting columns until the vacated rectangle's left edge
|
||||
# (xoff - 1) lands on an even (base-cell) column - the odd case is the one
|
||||
# every earlier manual test happened to land on by chance.
|
||||
startx=5
|
||||
tries=0
|
||||
while [ "$tries" -lt 2 ]; do
|
||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 12 -y 3 -X "$startx" \
|
||||
-Y 5 'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
||||
sleep 0.2
|
||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
oldleft=$((XOFF - 1))
|
||||
if [ $((oldleft % 2)) -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
startx=$((startx + 1))
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
[ $(((XOFF - 1) % 2)) -eq 0 ] || fail "could not find bad-parity starting column"
|
||||
|
||||
wait_outer_has FLOATMARK
|
||||
|
||||
ROW1=$((YOFF + 1))
|
||||
ROW2=$((YOFF + 3))
|
||||
COL1=$((XOFF - 4))
|
||||
COL2=$((XOFF + 4))
|
||||
|
||||
# Grab the pane's top border a couple of columns in (avoiding the corner
|
||||
# cells) and drag it well clear of its old rectangle.
|
||||
GRABCOL=$((XOFF + 3))
|
||||
BORDERROW=$((YOFF))
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 15))" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 15))" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
|
||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||
|
||||
wait_old_rows_restored
|
||||
|
||||
exit 0
|
||||
206
regress/floating-pane-drag-wide-right.sh
Executable file
206
regress/floating-pane-drag-wide-right.sh
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Mirror of floating-pane-drag-wide-character.sh for the *right* edge of a
|
||||
# damage rectangle. redraw_damage_grow_span_clip() (screen-redraw.c) widens
|
||||
# a damage rectangle's right edge by one cell whenever it isn't already at
|
||||
# the span's own edge, to pull in a wide character's base half when the
|
||||
# edge lands on its padding half - but it did this unconditionally, with no
|
||||
# check of which half it was actually touching. When the edge instead
|
||||
# already lands cleanly on a fresh character's base cell (a character fully
|
||||
# outside the range), growing right pulls in just that base cell -
|
||||
# tty_draw_line() sees it can't fit that character's full width in the
|
||||
# remaining range (tty_draw_line_get_empty()'s gc->data.width > nx check)
|
||||
# and clears it, exactly as the left-edge bug cleared a neighbouring
|
||||
# character's padding half.
|
||||
#
|
||||
# As with the left-edge test, the exact column parity needs to be
|
||||
# deterministic to actually catch it. This constructs it by creating the
|
||||
# floating pane, checking its real position, and recreating it one column
|
||||
# over if necessary until the vacated rectangle's right edge lands on a
|
||||
# base cell.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lwidecharr-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
BASE=$DIR/base
|
||||
CAPTURE=$DIR/capture
|
||||
FLOAT=
|
||||
PANEWIDTH=12
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
slice_columns()
|
||||
{
|
||||
# Extract terminal columns [COL1, COL2) from lines [ROW1, ROW2] of
|
||||
# $1, decoding UTF-8 - the pane's own new position (well clear of
|
||||
# this range) must not affect the comparison, so this only looks at
|
||||
# the narrow strip actually vacated, not the whole line. capture-pane
|
||||
# text has one decoded character per double-width cell pair (every
|
||||
# character here is width 2), so terminal columns are converted to
|
||||
# character indices by halving before slicing.
|
||||
perl -CSD -e '
|
||||
my ($row1, $row2, $col1, $col2, $file) = @ARGV;
|
||||
open my $fh, "<:encoding(UTF-8)", $file or die $!;
|
||||
my @lines = <$fh>;
|
||||
my $c1 = int($col1 / 2);
|
||||
my $c2 = int(($col2 + 1) / 2);
|
||||
for my $n ($row1 .. $row2) {
|
||||
my $line = $lines[$n - 1];
|
||||
$line =~ s/\R\z//;
|
||||
print substr($line, $c1, $c2 - $c1), "\n";
|
||||
}
|
||||
' "$ROW1" "$ROW2" "$COL1" "$COL2" "$1"
|
||||
}
|
||||
|
||||
wait_old_rows_restored()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
slice_columns "$BASE" >"$DIR/want"
|
||||
slice_columns "$CAPTURE" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "wide characters under the floating pane's vacated right edge were not restored"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
binmode STDOUT, ':encoding(UTF-8)';
|
||||
$| = 1;
|
||||
for my $row (1 .. 10) {
|
||||
print "\e[$row;1H", chr(0x754c) x 20;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g pane-scrollbars off || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lwidecharr-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has '界界界'
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$BASE" || exit 1
|
||||
|
||||
# Create the floating pane, then check its actual resulting position. Try
|
||||
# adjacent starting columns until the vacated rectangle's right edge
|
||||
# (xoff + PANEWIDTH + 1, the border-grown exclusive end - see
|
||||
# window_pane_damage_floating(), window.c) lands on an even (base-cell)
|
||||
# column.
|
||||
startx=5
|
||||
tries=0
|
||||
while [ "$tries" -lt 2 ]; do
|
||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x "$PANEWIDTH" -y 3 \
|
||||
-X "$startx" -Y 5 \
|
||||
'sh -c "printf FLOATMARK; exec sleep 100"') || exit 1
|
||||
sleep 0.2
|
||||
XOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
YOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
oldright=$((XOFF + PANEWIDTH + 1))
|
||||
if [ $((oldright % 2)) -eq 0 ]; then
|
||||
break
|
||||
fi
|
||||
startx=$((startx + 1))
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
[ $(((XOFF + PANEWIDTH + 1) % 2)) -eq 0 ] ||
|
||||
fail "could not find bad-parity starting column"
|
||||
|
||||
wait_outer_has FLOATMARK
|
||||
|
||||
ROW1=$((YOFF + 1))
|
||||
ROW2=$((YOFF + 3))
|
||||
COL1=$((XOFF + PANEWIDTH - 2))
|
||||
COL2=$((XOFF + PANEWIDTH + 2))
|
||||
|
||||
# Grab the pane's top border a couple of columns in (avoiding the corner
|
||||
# cells) and drag it well clear of its old rectangle - far enough right
|
||||
# that its new position starts past the checked columns above (which sit
|
||||
# just past the pane's *original* right edge).
|
||||
GRABCOL=$((XOFF + 3))
|
||||
BORDERROW=$((YOFF))
|
||||
|
||||
seq=$(printf '\033[<0;%s;%sM' "$GRABCOL" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
seq=$(printf '\033[<32;%s;%sM' "$((GRABCOL + 20))" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
seq=$(printf '\033[<0;%s;%sm' "$((GRABCOL + 20))" "$BORDERROW")
|
||||
$OUTER send-keys -t outer:0.0 -l "$seq" || exit 1
|
||||
sleep 0.1
|
||||
|
||||
NEWXOFF=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
[ "$NEWXOFF" != "$XOFF" ] || fail "sanity: floating pane did not move (still at $XOFF)"
|
||||
|
||||
wait_old_rows_restored
|
||||
|
||||
exit 0
|
||||
@@ -268,5 +268,149 @@ $TMUX kill-pane -t "$floating" || exit 1
|
||||
$TMUX kill-pane -t "$right" || exit 1
|
||||
$TMUX kill-pane -t "$lower" || exit 1
|
||||
|
||||
# --- Floating panes clamped when the window shrinks (issue #5581, PR #5582) ---
|
||||
#
|
||||
# layout_resize clamps floating panes back inside the window when it shrinks:
|
||||
# move them and, only if they cannot fit, shrink them (never below
|
||||
# PANE_MINIMUM). A floating cell is the pane's content, so with the default
|
||||
# single-line border the clamp counts 1 cell of border per side, exactly as
|
||||
# layout_floating_args_parse does on creation; with no border it counts 0.
|
||||
# Each case below gets its own window, since resize-window fixes a window at
|
||||
# a manual size for the rest of its life.
|
||||
|
||||
# Case 1: a lone floating pane -- break-pane -W on a window's only pane --
|
||||
# takes the early-return path in layout_resize, added during PR #5582's
|
||||
# review round, since there is no tiled tree to walk.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for lone float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 60 -Y 18 -> pane_left 61, pane_top 19: with the
|
||||
# border, the footprint is columns 60-79, rows 18-23, flush with the 80x24
|
||||
# window's right and bottom edges, so the float fits before it is shrunk.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for lone float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
# Shrink to 40x16. The float still fits at its own size (18 <= 40-2, 4 <=
|
||||
# 16-2) so only its position moves, flush to the new right/bottom edges:
|
||||
# xoff = 40 - 18 - 1 = 21; yoff = 16 - 4 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
|
||||
# Case 5: grow the window back. The clamp must not chase the window back
|
||||
# outward -- it only ever pulls a float in, never restores where it was.
|
||||
$TMUX resize-window -t "$win" -x 80 -y 24 ||
|
||||
fail "resize-window grow (lone float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 2: the same float, but with a tiled sibling surviving alongside it, so
|
||||
# the window's root cell stays tiled and layout_resize takes the normal path
|
||||
# (the clamp call after layout_fix_offsets) instead of the early return
|
||||
# above. Same geometry as case 1, so the same numbers should come out.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for tiled sibling failed"
|
||||
$TMUX split-window -t "$win" 'sleep 100' ||
|
||||
fail "split-window for tiled sibling failed"
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for tiled sibling failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 61
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 19
|
||||
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (tiled sibling) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 21
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 3: new-pane float, the original repro from issue #5581 and the PR
|
||||
# body -- also the normal path, via the tiled base pane new-window creates.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for new-pane repro failed"
|
||||
$TMUX resize-window -t "$win" -x 120 -y 40 ||
|
||||
fail "resize-window to 120x40 failed"
|
||||
|
||||
# -x 30 -y 10 -> pane 28x8; -X 85 -Y 25 -> pane_left 86, pane_top 26.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 30 -y 10 -X 85 -Y 25 'sleep 100') ||
|
||||
fail "new-pane for new-pane repro failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 86
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 26
|
||||
|
||||
# Shrink to 60x20: xoff = 60 - 28 - 1 = 31; yoff = 20 - 8 - 1 = 11.
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (new-pane repro) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 28
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 8
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 31
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 11
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 4: a float that already fits inside the shrunk window is left alone --
|
||||
# assert position and size are both unchanged, not just one of them.
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for untouched float failed"
|
||||
|
||||
# -x 20 -y 6 -> pane 18x4; -X 8 -Y 3 -> pane_left 9, pane_top 4 (as at the top
|
||||
# of this file). Footprint columns 8-27, rows 3-8: well inside 60x20 too.
|
||||
floating=$($TMUX new-pane -t "$win" -dPF '#{pane_id}' \
|
||||
-x 20 -y 6 -X 8 -Y 3 'sleep 100') ||
|
||||
fail "new-pane for untouched float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
|
||||
$TMUX resize-window -t "$win" -x 60 -y 20 ||
|
||||
fail "resize-window (untouched float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_width}')" 18
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_height}')" 4
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_left}')" 9
|
||||
must_equal "$($TMUX display-message -p -t "$floating" '#{pane_top}')" 4
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
# Case 6: pad = 0, via the pane-border-lines window option set to none. The
|
||||
# border arithmetic differs here (no -2/+1 adjustment either on creation or
|
||||
# in the clamp).
|
||||
win=$($TMUX new-window -dPF '#{window_id}') ||
|
||||
fail "new-window for pad=0 float failed"
|
||||
$TMUX set-option -w -t "$win" pane-border-lines none ||
|
||||
fail "set pane-border-lines none failed"
|
||||
|
||||
# No border: -x 20 -y 6 -> pane 20x6 directly; -X 60 -Y 18 -> pane_left 60,
|
||||
# pane_top 18 directly. Footprint (== content, no border) is columns 60-79,
|
||||
# rows 18-23: flush right/bottom of the 80x24 window, same as case 1.
|
||||
$TMUX break-pane -W -s "$win" -x 20 -y 6 -X 60 -Y 18 ||
|
||||
fail "break-pane -W for pad=0 float failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 60
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 18
|
||||
|
||||
# Shrink to 40x16 with pad=0: xoff = 40 - 20 - 0 = 20; yoff = 16 - 6 - 0 = 10.
|
||||
$TMUX resize-window -t "$win" -x 40 -y 16 ||
|
||||
fail "resize-window (pad=0 float) failed"
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_width}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_height}')" 6
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_left}')" 20
|
||||
must_equal "$($TMUX display-message -p -t "$win" '#{pane_top}')" 10
|
||||
$TMUX kill-window -t "$win" || exit 1
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
|
||||
102
regress/floating-pane-offscreen-alt.sh
Executable file
102
regress/floating-pane-offscreen-alt.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Returning from the alternate screen must redraw the visible part of a
|
||||
# floating pane clipped at the left edge, the top edge, or both.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Loffscreen-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 5) {
|
||||
print "\e[$row;1H", 'A' x 15;
|
||||
}
|
||||
while (!-e "$ENV{TRIGGER}-alternate") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049h";
|
||||
for my $row (1 .. 5) {
|
||||
print "\e[$row;1H", 'B' x 15;
|
||||
}
|
||||
while (!-e "$ENV{TRIGGER}-restore") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049l";
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 10 'sleep 100' || 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 -x 40 -y 10 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Loffscreen-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
for position in left top both; do
|
||||
case "$position" in
|
||||
left) x=-5; y=2 ;;
|
||||
top) x=5; y=-2 ;;
|
||||
both) x=-5; y=-2 ;;
|
||||
esac
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 15 -y 5 -X "$x" -Y "$y" \
|
||||
"TRIGGER='$DIR/$position' perl '$EMITTER'") || exit 1
|
||||
[ "$($INNER display-message -p -t "$FLOAT" '#{pane_left},#{pane_top}')" = "$((x + 1)),$((y + 1))" ] ||
|
||||
fail "$position: floating pane has unexpected position"
|
||||
wait_outer_has AAAAA
|
||||
cp "$CAPTURE" "$DIR/primary"
|
||||
: >"$DIR/$position-alternate"
|
||||
wait_outer_has BBBBB
|
||||
: >"$DIR/$position-restore"
|
||||
wait_outer_has AAAAA
|
||||
cmp -s "$DIR/primary" "$CAPTURE" ||
|
||||
fail "$position: primary screen was not completely restored"
|
||||
$INNER kill-pane -t "$FLOAT" || exit 1
|
||||
done
|
||||
|
||||
exit 0
|
||||
179
regress/floating-pane-prompt-wide-character.sh
Executable file
179
regress/floating-pane-prompt-wide-character.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A damage rectangle's clip range is grown to avoid splitting a wide
|
||||
# character, but redraw_damage_grow_span_clip() (screen-redraw.c) only ever
|
||||
# checks the span's own pane *content* grid (wp->screen) for that. For a
|
||||
# REDRAW_SPAN_PANE span, that same range is then also handed to
|
||||
# redraw_damage_draw_pane_prompt() to recompose the pane's separately
|
||||
# rendered prompt (wp->prompt, e.g. from "command-prompt -P") over the
|
||||
# damaged sub-range - but the prompt is drawn into its own, freshly
|
||||
# allocated one-line screen, unrelated to the pane's content grid, so a
|
||||
# range grown (or left ungrown) against the content is not necessarily
|
||||
# grown correctly for the prompt's own wide characters.
|
||||
#
|
||||
# This is invisible when the pane's own content is plain ASCII (as here):
|
||||
# redraw_damage_grow_span_clip() never finds anything to grow against, so
|
||||
# the raw, ungrown geometric range is passed straight through to the
|
||||
# prompt - and if that range's edge lands mid-character in the *prompt's*
|
||||
# grid, tty_draw_line() clears the character it cuts through
|
||||
# (tty_draw_line_get_empty()'s gc->data.width > nx check, for a trailing
|
||||
# base cell with no room left for its padding half).
|
||||
#
|
||||
# The trigger is a palette change (OSC 4) in a tiled pane that is one half
|
||||
# of a vertical split running the full height of the window - occluded
|
||||
# under the floating pane, but still geometrically triggering a redraw of
|
||||
# its own rectangle. Positioned so the split boundary falls inside the
|
||||
# floating pane's own CJK prompt, this reproduces exactly Codex's report:
|
||||
# "a floating pane containing a CJK prompt across a tiled-pane boundary -
|
||||
# a palette update in the tiled pane blanks a prompt character."
|
||||
|
||||
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
|
||||
cd "$DIR" || exit 1
|
||||
INNER="$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lpromptwide-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
FLOAT=
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
cd /
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_prompt_row_intact()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
line=$(sed -n "${PROMPTROW}p" "$CAPTURE")
|
||||
case $line in
|
||||
*"$PROMPTTEXT"*) return 0 ;;
|
||||
esac
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "the CJK prompt was not intact after the palette-triggered damage - got: $line"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
my $line = <STDIN>;
|
||||
print "\e]4;1;rgb:11/22/33\e\\";
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 12 "perl '$EMITTER'" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
LEFT=$($INNER list-panes -t inner -F '#{pane_id}') || exit 1
|
||||
|
||||
# Split so the boundary between the two tiled panes falls at column 17 -
|
||||
# used below to pick a floating-pane column that lands the boundary
|
||||
# mid-character inside the prompt.
|
||||
RIGHT=$($INNER split-window -t inner -h -l 43 -PF '#{pane_id}' \
|
||||
'sleep 100') || exit 1
|
||||
RX=$($INNER display-message -p -t "$RIGHT" '#{pane_left}') || exit 1
|
||||
|
||||
# Create the floating pane, then check its actual resulting position (the
|
||||
# border-framing offset added to -X is not something to hand-compute). Try
|
||||
# adjacent starting columns until the split boundary lands on an odd
|
||||
# (padding-half) column of the prompt's own numbering, and within the
|
||||
# prompt's 12-column width.
|
||||
startx=10
|
||||
tries=0
|
||||
while [ "$tries" -lt 4 ]; do
|
||||
[ -n "$FLOAT" ] && $INNER kill-pane -t "$FLOAT" 2>/dev/null
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 24 -y 5 -X "$startx" \
|
||||
-Y 2 "sh -c 'i=0; while [ \$i -lt 10 ]; do \
|
||||
printf AAAAAAAAAAAAAAAAAAAAAA\\\\n; i=\$((i+1)); done; sleep 100'") ||
|
||||
exit 1
|
||||
sleep 0.2
|
||||
X1=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
Y1=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
H1=$($INNER display-message -p -t "$FLOAT" '#{pane_height}')
|
||||
local=$((RX - X1 - 1))
|
||||
if [ "$local" -ge 1 ] && [ "$local" -le 11 ] &&
|
||||
[ $((local % 2)) -eq 1 ]; then
|
||||
break
|
||||
fi
|
||||
startx=$((startx + 1))
|
||||
tries=$((tries + 1))
|
||||
done
|
||||
local=$((RX - X1 - 1))
|
||||
[ "$local" -ge 1 ] && [ "$local" -le 11 ] && [ $((local % 2)) -eq 1 ] ||
|
||||
fail "could not find bad-parity starting column"
|
||||
|
||||
$OUTER new-session -d -s outer -x 60 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lpromptwide-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
wait_outer_has AAAAAAAAAAAAAAAAAAAAAA
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}') || exit 1
|
||||
|
||||
$INNER select-pane -t "$FLOAT" || exit 1
|
||||
PROMPTTEXT=$(printf '\344\270\255' | perl -CSD -ne 'print $_ x 6')
|
||||
$INNER command-prompt -b -P -t "$CLIENT" -p "$PROMPTTEXT" \
|
||||
'display-message -- %1' || exit 1
|
||||
wait_outer_has "$PROMPTTEXT"
|
||||
|
||||
PROMPTROW=$((Y1 + H1))
|
||||
|
||||
# Trigger the damage: unblock the emitter so it fires the palette change in
|
||||
# the left tiled pane, which is occluded under (but geometrically overlaps)
|
||||
# the floating pane's prompt row.
|
||||
$INNER send-keys -t "$LEFT" Enter || exit 1
|
||||
|
||||
wait_prompt_row_intact
|
||||
|
||||
exit 0
|
||||
150
regress/floating-pane-scroll-behind-margins.sh
Executable file
150
regress/floating-pane-scroll-behind-margins.sh
Executable file
@@ -0,0 +1,150 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A floating pane that scrolls behind a larger floating pane must not disturb
|
||||
# the larger pane, with or without left/right margin support in the terminal.
|
||||
# The larger pane spans the smaller one vertically and overlaps it only at its
|
||||
# left, so with margins the terminal would otherwise scroll the cells of the
|
||||
# larger pane (including its scrollbar) along with the smaller pane.
|
||||
|
||||
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
|
||||
INNER_NAME=testA$
|
||||
OUTER_NAME=testB$
|
||||
INNER="$TEST_TMUX -L$INNER_NAME -f/dev/null"
|
||||
OUTER="$TEST_TMUX -L$OUTER_NAME -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
FILLER=$DIR/filler.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 100 ]; do
|
||||
$INNER capture-pane -p -t "$2" 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane $2 did not contain $marker"
|
||||
}
|
||||
|
||||
# Scrolling pane: wait for the trigger, then scroll it well past its height.
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
for (1 .. 30) {
|
||||
print 'd' x 20, "\r\n";
|
||||
select undef, undef, undef, 0.02;
|
||||
}
|
||||
print "SCROLL-DONE\r\n";
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# Larger pane: numbered rows so any shift or blanking is visible.
|
||||
cat >"$FILLER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 18) {
|
||||
printf "\e[%d;1Hrow%02d %s", $row, $row, 'F' x 40;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
for margins in off on; do
|
||||
if [ "$margins" = on ]; then
|
||||
INNER_NAME=testC$
|
||||
OUTER_NAME=testD$
|
||||
INNER="$TEST_TMUX -L$INNER_NAME -f/dev/null"
|
||||
OUTER="$TEST_TMUX -L$OUTER_NAME -f/dev/null"
|
||||
fi
|
||||
|
||||
rm -f "$TRIGGER"
|
||||
$INNER new-session -d -s inner -x 80 -y 24 'sleep 100' || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g pane-scrollbars on || exit 1
|
||||
if [ "$margins" = on ]; then
|
||||
$INNER set -as terminal-features ',screen-256color:margins' ||
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The small pane is created first so the large one is above it.
|
||||
SMALL=$($INNER new-pane -d -PF '#{pane_id}' -x 30 -y 10 -X 40 -Y 6 \
|
||||
"TRIGGER='$TRIGGER' perl '$EMITTER'") || fail "new-pane failed"
|
||||
$INNER new-pane -d -x 50 -y 20 -X 2 -Y 2 "perl '$FILLER'" ||
|
||||
fail "new-pane failed"
|
||||
|
||||
$OUTER new-session -d -s outer -x 80 -y 24 'sleep 100' || exit 1
|
||||
$OUTER set -g status off || exit 1
|
||||
$OUTER set -g window-size manual || exit 1
|
||||
$OUTER set -g default-terminal screen-256color || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -L$INNER_NAME -f/dev/null attach -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_outer_has row18
|
||||
sleep 0.5
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/before"
|
||||
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has SCROLL-DONE "$SMALL"
|
||||
sleep 0.5
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE"
|
||||
|
||||
# Columns 1-52 hold the whole large pane, its border and scrollbar.
|
||||
cut -c1-52 "$DIR/before" >"$DIR/before.cut"
|
||||
cut -c1-52 "$CAPTURE" >"$DIR/after.cut"
|
||||
if ! cmp -s "$DIR/before.cut" "$DIR/after.cut"; then
|
||||
echo "large pane changed (margins $margins):" >&2
|
||||
diff "$DIR/before.cut" "$DIR/after.cut" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
done
|
||||
|
||||
exit 0
|
||||
151
regress/floating-pane-status-cross-client.sh
Executable file
151
regress/floating-pane-status-cross-client.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Damage redraws must show each client's own pane status, even though the
|
||||
# cached status screen is shared between clients.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lstatuscc-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lstatuscc-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_for_marker()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client in $target did not receive $marker"
|
||||
}
|
||||
|
||||
cat >"$DIR/emitter.pl" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $phase (1 .. 4) {
|
||||
while (!-e "$ENV{TRIGGER}-$phase") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
# Change an unused palette entry and acknowledge it in the pane body.
|
||||
print "\e]4;200;rgb:11/22/0$phase\a\e[1;1HDAMAGE$phase";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 \
|
||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g automatic-rename off || exit 1
|
||||
$INNER set -g status-interval 0 || exit 1
|
||||
$INNER set -g pane-border-lines simple || exit 1
|
||||
$INNER set -g pane-border-status top || exit 1
|
||||
$INNER set -g pane-border-format 'CLIENT=<#{client_name}>' || exit 1
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 35 -y 6 -X 5 -Y 5 \
|
||||
'sleep 100') || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set -g status off || exit 1
|
||||
$OUTER set -g window-size manual || exit 1
|
||||
$OUTER set -g default-terminal screen || exit 1
|
||||
LEFT=$($OUTER display-message -p -t outer:0.0 '#{pane_id}') || exit 1
|
||||
RIGHT=$($OUTER split-window -h -PF '#{pane_id}' 'sleep 100') || exit 1
|
||||
for target in "$LEFT" "$RIGHT"; do
|
||||
$OUTER respawn-pane -k -t "$target" "$INNER attach-session -t inner" ||
|
||||
exit 1
|
||||
done
|
||||
wait_for_clients
|
||||
|
||||
NAME1=$($OUTER display-message -p -t "$LEFT" '#{pane_tty}') || exit 1
|
||||
NAME2=$($OUTER display-message -p -t "$RIGHT" '#{pane_tty}') || exit 1
|
||||
|
||||
# Disable periodic status updates above and trigger damage without a command
|
||||
# that also requests a status redraw. Each client must keep its own title.
|
||||
$INNER refresh-client -t "$NAME1" || exit 1
|
||||
$INNER refresh-client -t "$NAME2" || exit 1
|
||||
sleep 0.5
|
||||
for phase in 0 1 2; do
|
||||
if [ "$phase" -ne 0 ]; then
|
||||
: >"$DIR/trigger-$phase"
|
||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
||||
wait_for_marker "$RIGHT" "DAMAGE$phase"
|
||||
sleep 0.2
|
||||
fi
|
||||
for target in "$LEFT" "$RIGHT"; do
|
||||
if [ "$target" = "$LEFT" ]; then
|
||||
name=$NAME1
|
||||
other=$NAME2
|
||||
else
|
||||
name=$NAME2
|
||||
other=$NAME1
|
||||
fi
|
||||
$OUTER capture-pane -p -S 5 -E 10 -t "$target" \
|
||||
>"$CAPTURE" || exit 1
|
||||
grep -Fq "CLIENT=<$name>" "$CAPTURE" ||
|
||||
fail "phase $phase: missing $name's pane status"
|
||||
if grep -Fq "CLIENT=<$other>" "$CAPTURE"; then
|
||||
fail "phase $phase: $name received $other's pane status"
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
# Leave just one client so a cache keyed only by client would remain stale.
|
||||
$OUTER respawn-pane -k -t "$RIGHT" 'sleep 100' || exit 1
|
||||
i=0
|
||||
while [ "$($INNER list-clients | wc -l)" -ne 1 ]; do
|
||||
[ "$i" -lt 50 ] || fail "second client did not detach"
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
$INNER set-environment -g TEST_STATUS_VALUE initial || exit 1
|
||||
$INNER set -g pane-border-format 'VALUE=#{TEST_STATUS_VALUE}' || exit 1
|
||||
$INNER refresh-client -t "$NAME1" || exit 1
|
||||
wait_for_marker "$LEFT" VALUE=initial
|
||||
for phase in 3 4; do
|
||||
# Changing the environment does not itself request a status redraw.
|
||||
$INNER set-environment -g TEST_STATUS_VALUE "phase$phase" || exit 1
|
||||
: >"$DIR/trigger-$phase"
|
||||
wait_for_marker "$LEFT" "DAMAGE$phase"
|
||||
wait_for_marker "$LEFT" "VALUE=phase$phase"
|
||||
done
|
||||
|
||||
exit 0
|
||||
102
regress/floating-pane-status-format.sh
Executable file
102
regress/floating-pane-status-format.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Resizing a floating pane must refresh session status formats which depend on
|
||||
# its geometry, not only the pane scene and borders.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lfloating-status-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has_status()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
tail -1 "$CAPTURE" | grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client status did not show $marker"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t outer:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
$INNER new-session -d -s inner -x 50 -y 12 'sleep 100' || exit 1
|
||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 10 -y 5 -X 5 -Y 3 \
|
||||
'sleep 100') || fail "could not create floating pane"
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g status on || exit 1
|
||||
$INNER set-option -g status-position bottom || exit 1
|
||||
$INNER set-option -g status-left 'WIDTH=#{pane_width}' || exit 1
|
||||
$INNER set-option -g status-right '' || exit 1
|
||||
$INNER set-option -g status-interval 0 || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 50 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lfloating-status-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
OLD_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
wait_outer_has_status "WIDTH=$OLD_WIDTH"
|
||||
|
||||
RIGHT=$($INNER display-message -p -t "$FLOAT" '#{pane_right}')
|
||||
TOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
X=$((RIGHT + 2))
|
||||
Y=$((TOP + 2))
|
||||
|
||||
# Grab the right frame and enlarge the floating pane.
|
||||
mouse 0 "$X" "$Y" M
|
||||
mouse 32 "$((X + 1))" "$Y" M
|
||||
mouse 32 "$((X + 8))" "$Y" M
|
||||
mouse 0 "$((X + 8))" "$Y" m
|
||||
|
||||
NEW_WIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
[ "$NEW_WIDTH" -ne "$OLD_WIDTH" ] || fail "floating pane was not resized"
|
||||
wait_outer_has_status "WIDTH=$NEW_WIDTH"
|
||||
|
||||
exit 0
|
||||
87
regress/floating-pane-top-border-drag.sh
Executable file
87
regress/floating-pane-top-border-drag.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/bin/sh
|
||||
|
||||
# With pane-border-status top, a tiled pane's status line is on the top row of
|
||||
# the window. A floating pane dragged to the top of the window has its own top
|
||||
# border on the same row, so it must still be draggable by that border rather
|
||||
# than the click going to the tiled pane underneath.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export PATH TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
|
||||
TMUX="$TEST_TMUX -Ltopborder-inner-$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Ltopborder-outer-$$ -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
$TMUX kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
# Press, drag and release with SGR mouse sequences, sent as 1-based positions
|
||||
# to the outer pane holding the inner client. This matches the default
|
||||
# MouseDown1Border/MouseDrag1Border bindings used to move a floating pane by
|
||||
# its border.
|
||||
drag()
|
||||
{
|
||||
seq=$(printf '\033[<0;%s;%sM' "$1" "$2")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<32;%s;%sM' "$3" "$4")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.2
|
||||
seq=$(printf '\033[<0;%s;%sm' "$3" "$4")
|
||||
$TMUX2 send-keys -t "$OUTER" -l "$seq" 2>/dev/null
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
pane_top()
|
||||
{
|
||||
$TMUX display-message -p -t "$FLOAT" '#{pane_top}'
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sleep 100' || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g mouse on || exit 1
|
||||
$TMUX set -g pane-border-status top || exit 1
|
||||
|
||||
FLOAT=$($TMUX new-pane -d -PF '#{pane_id}' -x 20 -y 8 -X 15 -Y 8 'sleep 100') ||
|
||||
fail "new-pane failed"
|
||||
FLEFT=$($TMUX display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
COL=$((FLEFT + 6))
|
||||
|
||||
$TMUX2 new-session -d -s outer -x 60 -y 20 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
OUTER=$($TMUX2 list-panes -F '#{pane_id}' | head -1)
|
||||
[ -n "$OUTER" ] || fail "no outer pane"
|
||||
|
||||
# Drag the floating pane up by its top border until it reaches the top of the
|
||||
# window, where the border shares a row with the tiled pane's status line.
|
||||
i=0
|
||||
while [ "$(pane_top)" -gt 1 ] && [ "$i" -lt 10 ]; do
|
||||
top=$(pane_top)
|
||||
# The border is on row top-1 (0-based); positions are 1-based.
|
||||
dest=$((top - 3))
|
||||
[ "$dest" -lt 1 ] && dest=1
|
||||
drag $((COL + 1)) "$top" $((COL + 1)) "$dest"
|
||||
i=$((i + 1))
|
||||
done
|
||||
[ "$(pane_top)" -eq 1 ] || fail "could not drag the pane to the top ($(pane_top))"
|
||||
|
||||
# Now drag it back down by the top border in row 0.
|
||||
drag $((COL + 1)) 1 $((COL + 1)) 4
|
||||
[ "$(pane_top)" -gt 1 ] ||
|
||||
fail "floating pane at the top could not be dragged by its top border"
|
||||
|
||||
exit 0
|
||||
158
regress/floating-pane-zoom.sh
Normal file
158
regress/floating-pane-zoom.sh
Normal file
@@ -0,0 +1,158 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving, resizing and reordering an over-zoom float must leave the tiled
|
||||
# pane zoomed, and the changes must survive an explicit unzoom.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -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
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
run()
|
||||
{
|
||||
$TMUX "$@" || fail "failed: $*"
|
||||
}
|
||||
|
||||
check()
|
||||
{
|
||||
got=$(run display-message -p -t "$1" "$2") || exit 1
|
||||
[ "$got" = "$3" ] || fail "$1 $2: got '$got', expected '$3'"
|
||||
}
|
||||
|
||||
check_zoom()
|
||||
{
|
||||
check "$base" '#{window_zoomed_flag}:#{pane_zoomed_flag}' '1:1'
|
||||
check "$float" '#{pane_floating_flag}:#{pane_active}' "1:$active"
|
||||
check "$base" '#{@unzoomed}' ''
|
||||
}
|
||||
|
||||
run new-session -d -x 80 -y 24
|
||||
base=$(run display-message -p '#{pane_id}') || exit 1
|
||||
other=$(run split-window -dPF '#{pane_id}') || exit 1
|
||||
layout=$(run display-message -p '#{window_layout}') || exit 1
|
||||
run set-hook -g window-unzoomed 'set -g @unzoomed 1'
|
||||
|
||||
# Cover creation before and after zoom, with both the float and the tiled
|
||||
# pane active. Modal panes use the same over-zoom flag.
|
||||
for mode in before after detached modal; do
|
||||
if [ "$mode" != before ]; then
|
||||
run resize-pane -Z -t "$base"
|
||||
fi
|
||||
case "$mode" in
|
||||
detached) flags=-Ad; active=0 ;;
|
||||
modal) flags=-O; active=1 ;;
|
||||
*) flags=-A; active=1 ;;
|
||||
esac
|
||||
float=$(run new-pane "$flags" -PF '#{pane_id}' -t "$base" \
|
||||
-x 20 -y 8 -X 8 -Y 3 '') || exit 1
|
||||
if [ "$mode" = before ]; then
|
||||
run resize-pane -Z -t "$base"
|
||||
fi
|
||||
run set -gu @unzoomed
|
||||
check_zoom
|
||||
|
||||
run move-pane -t "$float" -D
|
||||
check "$float" '#{pane_top}' 5
|
||||
check_zoom
|
||||
run move-pane -t "$float" -U 2 -R 3 -L 1
|
||||
check "$float" '#{pane_left}:#{pane_top}' '11:3'
|
||||
check_zoom
|
||||
run move-pane -t "$float" -X 25% -Y 25%
|
||||
check "$float" '#{pane_left}:#{pane_top}' '21:7'
|
||||
check_zoom
|
||||
run move-pane -t "$float" -P bottom-right
|
||||
check "$float" '#{pane_left}:#{pane_top}' '61:17'
|
||||
check_zoom
|
||||
run resize-pane -t "$float" -x 30 -y 10
|
||||
check "$float" '#{pane_width}:#{pane_height}' '28:8'
|
||||
check_zoom
|
||||
run resize-pane -t "$float" -U 1 -L 2
|
||||
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
|
||||
'59:16:30:9'
|
||||
check_zoom
|
||||
run move-pane -t "$float" -P centre
|
||||
check "$float" '#{pane_left}:#{pane_top}' '25:7'
|
||||
check_zoom
|
||||
|
||||
# Failed commands must not change zoom either.
|
||||
for command in 'move-pane -D invalid' 'move-pane -P invalid' \
|
||||
'move-pane -z invalid' 'resize-pane -x invalid' \
|
||||
'resize-pane -D invalid'; do
|
||||
$TMUX $command -t "$float" 2>/dev/null &&
|
||||
fail "unexpected success: $command"
|
||||
check_zoom
|
||||
done
|
||||
|
||||
run resize-pane -Z -t "$base"
|
||||
check "$float" '#{pane_left}:#{pane_top}:#{pane_width}:#{pane_height}' \
|
||||
'25:7:30:9'
|
||||
run kill-pane -t "$float"
|
||||
check "$base" '#{window_layout}' "$layout"
|
||||
done
|
||||
|
||||
# Put an ordinary (hidden while zoomed) float between two over-zoom floats
|
||||
# in the stacking order. Reordering must skip it when counting visible
|
||||
# positions, but keep all floats ahead of the tiled panes after unzoom.
|
||||
back=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
|
||||
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
|
||||
float=$(run new-pane -AdPF '#{pane_id}' -t "$base" '') || exit 1
|
||||
active=0
|
||||
run resize-pane -Z -t "$base"
|
||||
run set -gu @unzoomed
|
||||
|
||||
for position in backward back forward-loop; do
|
||||
run move-pane -t "$float" -P front
|
||||
run move-pane -t "$float" -P "$position"
|
||||
check "$float" '#{pane_z}' 1
|
||||
check "$back" '#{pane_z}' 0
|
||||
check_zoom
|
||||
done
|
||||
for position in forward front backward-loop; do
|
||||
run move-pane -t "$float" -P back
|
||||
run move-pane -t "$float" -P "$position"
|
||||
check "$float" '#{pane_z}' 0
|
||||
check "$back" '#{pane_z}' 1
|
||||
check_zoom
|
||||
done
|
||||
for z in 1 0 99; do
|
||||
run move-pane -t "$float" -z "$z"
|
||||
want=$z
|
||||
[ "$z" = 99 ] && want=1
|
||||
check "$float" '#{pane_z}' "$want"
|
||||
check_zoom
|
||||
done
|
||||
run resize-pane -Z -t "$base"
|
||||
check "$float" '#{pane_z}' 2
|
||||
check "$base" '#{pane_z}' 4
|
||||
run kill-pane -t "$hidden"
|
||||
run kill-pane -t "$back"
|
||||
|
||||
# Explicit zoom toggling and operations on tiled panes retain their
|
||||
# existing behaviour. The over-zoom flag alone does not make a pane a float:
|
||||
# zooming the float itself gives it a tiled cell until unzoom.
|
||||
run resize-pane -Z -t "$float"
|
||||
check "$float" '#{pane_zoomed_flag}:#{pane_floating_flag}' '1:0'
|
||||
run resize-pane -t "$float" -x 25
|
||||
check "$float" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
|
||||
'0:1:23'
|
||||
run resize-pane -Z -t "$base"
|
||||
run resize-pane -t "$other" -D 1
|
||||
check "$base" '#{window_zoomed_flag}' 0
|
||||
|
||||
# A hidden ordinary float must still be unzoomed before resizing it.
|
||||
hidden=$(run new-pane -dPF '#{pane_id}' -t "$base" '') || exit 1
|
||||
run resize-pane -Z -t "$base"
|
||||
run resize-pane -t "$hidden" -x 25
|
||||
check "$hidden" '#{window_zoomed_flag}:#{pane_floating_flag}:#{pane_width}' \
|
||||
'0:1:23'
|
||||
|
||||
exit 0
|
||||
@@ -7,6 +7,8 @@ TERM=screen
|
||||
|
||||
[ -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
|
||||
|
||||
# test_format $format $expected_result
|
||||
test_format()
|
||||
@@ -68,6 +70,8 @@ test_format '#{m/p:bash$,bash dev}' ''
|
||||
|
||||
# Inverse terms. Plain inverse terms are exact substring tests, not fuzzy.
|
||||
test_format '#{m/z:!ssh,dev bash}' '1'
|
||||
test_format '#{m/z:!long,x}' '1'
|
||||
test_format '#{m/z:!x,}' '1'
|
||||
test_format '#{m/z:!ssh,dev ssh}' '0'
|
||||
test_format '#{m/z:!ssh,s_s_h}' '1'
|
||||
test_format '#{m/z:dev !ssh,dev bash}' '1'
|
||||
|
||||
@@ -18,6 +18,24 @@ export TZ LANG LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
HOLD1=
|
||||
HOLD2=
|
||||
CC1=
|
||||
CC2=
|
||||
|
||||
cleanup()
|
||||
{
|
||||
for pid in $HOLD1 $HOLD2 $CC1 $CC2; do
|
||||
kill "$pid" 2>/dev/null
|
||||
wait "$pid" 2>/dev/null
|
||||
done
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
ESC=$(printf '\033')
|
||||
|
||||
@@ -227,6 +245,19 @@ test_format "#{R:a,3}" "aaa"
|
||||
test_format "#{R:ab,2}" "abab"
|
||||
# A long repeat exercises output-buffer growth during expansion.
|
||||
test_format "#{n:#{R:x,300}}" "300"
|
||||
# Limit the result size as well as the repeat count, including nested repeats.
|
||||
test_format "#{n:#{R:abcdefgh,8192}}" "65536"
|
||||
test_format "#{R:abcdefgh,8193}" ""
|
||||
test_format "#{R:,10000}" ""
|
||||
test_format "#{n:#{R:#{R:x,256},256}}" "65536"
|
||||
test_format "#{R:#{R:x,256},257}" ""
|
||||
test_format "#{R:#{R:abcdefgh,8192}x,1}" ""
|
||||
test_format "#{n:#{R:中文中,7281}}" "65529"
|
||||
test_format "#{R:中文中,7282}" ""
|
||||
test_format "#{n:#{R:x,10000}}" "10000"
|
||||
test_format "#{R:x,10001}" ""
|
||||
# Fuzzy matching must not receive an enormous string from nested repeats.
|
||||
test_format "#{m/p:a,#{R:#{R:#{R:a,353},38},90}}" ""
|
||||
|
||||
|
||||
# --- Width, padding and truncation ---------------------------------------
|
||||
@@ -579,8 +610,6 @@ assert_alive "verbose loop expansion"
|
||||
|
||||
# L loops over attached clients. Attach two control-mode clients, each held
|
||||
# open by a background process keeping a FIFO's write end open.
|
||||
FIFO1="${TMPDIR:-/tmp}/fmt-l-$$-1"
|
||||
FIFO2="${TMPDIR:-/tmp}/fmt-l-$$-2"
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
mkfifo "$FIFO1" "$FIFO2" || exit 1
|
||||
# Hold the write ends open so the control clients stay attached.
|
||||
@@ -605,9 +634,9 @@ test_format "#{L/nr:x}" "xx"
|
||||
test_format "#{L/r:x}" "xx"
|
||||
# Now detach one and confirm the count drops to one.
|
||||
kill $HOLD2 2>/dev/null
|
||||
wait $HOLD2 2>/dev/null
|
||||
HOLD2=
|
||||
sleep 1
|
||||
test_format "#{L:x}" "x"
|
||||
kill $HOLD1 $CC1 $CC2 2>/dev/null
|
||||
rm -f "$FIFO1" "$FIFO2"
|
||||
|
||||
exit 0
|
||||
|
||||
69
regress/format-no-current-window.sh
Normal file
69
regress/format-no-current-window.sh
Normal file
@@ -0,0 +1,69 @@
|
||||
#!/bin/sh
|
||||
|
||||
# active_window_index, last_window_index, session_stack and
|
||||
# window_active_clients read the session current window. Expanding them must
|
||||
# not kill the server. Covers the list-sessions, list-windows and list-panes
|
||||
# -F form, and commands that unlink the current window.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -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
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Formats that read the current window, expanded together.
|
||||
FMT='#{active_window_index} #{last_window_index} #{session_stack}'
|
||||
FMT="$FMT #{window_active_clients} #{window_active_clients_list}"
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# No session: the callbacks have nothing to read and must return empty.
|
||||
out=$($TMUX start\; set -s exit-empty off \; display-message -p "$FMT") ||
|
||||
fail "display with no session"
|
||||
[ "$out" = " " ] || fail "no session expanded to '$out'"
|
||||
|
||||
$TMUX new-session -d -s src -n one -- sleep 30 || fail "new-session src"
|
||||
[ "$($TMUX display-message -t src -p '#{active_window_index}')" = 0 ] ||
|
||||
fail "active window index"
|
||||
[ "$($TMUX display-message -t src -p '#{last_window_index}')" = 0 ] ||
|
||||
fail "last window index"
|
||||
[ "$($TMUX display-message -t src -p '#{session_stack}')" = 0 ] ||
|
||||
fail "session stack"
|
||||
|
||||
$TMUX new-window -d -t src -n two -- sleep 30 || fail "new-window"
|
||||
[ "$($TMUX display-message -t src -p '#{active_window_index}')" = 0 ] ||
|
||||
fail "active window index after new-window"
|
||||
[ "$($TMUX display-message -t src -p '#{last_window_index}')" = 1 ] ||
|
||||
fail "last window index after new-window"
|
||||
|
||||
$TMUX new-session -d -s dst -n only -- sleep 30 || fail "new-session dst"
|
||||
|
||||
# Replace and relink the current window, then expand again.
|
||||
$TMUX set-hook -g window-linked "list-sessions -F '$FMT'" || fail "set-hook"
|
||||
$TMUX new-window -k -t src:one -n replaced -- sleep 30 || fail "new-window -k"
|
||||
$TMUX link-window -k -s src:two -t dst:only || fail "link-window -k"
|
||||
|
||||
[ "$($TMUX display-message -t src -p '#{active_window_index}')" = 0 ] ||
|
||||
fail "active window index after replace"
|
||||
[ "$($TMUX display-message -t src -p '#{last_window_index}')" = 1 ] ||
|
||||
fail "last window index after replace"
|
||||
[ "$($TMUX display-message -t dst -p '#{active_window_index}')" = 0 ] ||
|
||||
fail "active window index after link"
|
||||
[ "$($TMUX display-message -t dst -p '#{window_active_clients}')" = 0 ] ||
|
||||
fail "window active clients"
|
||||
|
||||
$TMUX list-sessions -F "$FMT" >/dev/null || fail "list-sessions"
|
||||
$TMUX list-windows -a -F "$FMT" >/dev/null || fail "list-windows"
|
||||
$TMUX list-panes -a -F "$FMT" >/dev/null || fail "list-panes"
|
||||
$TMUX has-session -t src || fail "src session gone"
|
||||
$TMUX has-session -t dst || fail "dst session gone"
|
||||
|
||||
exit 0
|
||||
@@ -7,6 +7,8 @@ TERM=screen
|
||||
|
||||
[ -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
|
||||
|
||||
# test_format $format $expected_result
|
||||
test_format()
|
||||
|
||||
45
regress/format-time-range.sh
Normal file
45
regress/format-time-range.sh
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
# time modifiers with a value outside the range the system can convert
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
TZ=UTC
|
||||
export TZ
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP; $TMUX kill-server 2>/dev/null" 0 1 15
|
||||
|
||||
$TMUX new -d
|
||||
$TMUX set -g @ok 1700000000
|
||||
$TMUX set -g @huge 3093527980800
|
||||
$TMUX set -g @bad 9223372036854775807
|
||||
|
||||
# A convertible time still formats, so an empty result below means the value
|
||||
# was rejected and not that the modifier stopped working.
|
||||
$TMUX display -p '[#{t:@ok}]' >$TMP
|
||||
(echo "[Tue Nov 14 22:13:20 2023]"|cmp -s - $TMP) || exit 1
|
||||
$TMUX display -p '[#{t/f/zz:@ok}]' >$TMP
|
||||
(echo "[zz]"|cmp -s - $TMP) || exit 1
|
||||
$TMUX display -p '[#{t/p:@ok}]' >$TMP
|
||||
(echo "[Nov23]"|cmp -s - $TMP) || exit 1
|
||||
|
||||
# A year with more digits than ctime has room for must not be printed cut
|
||||
# short, but other modifiers can still format it.
|
||||
$TMUX display -p '[#{t:@huge}]' >$TMP
|
||||
grep -Eqx '\[\]|\[Sat Jan 1 00:00:00 100000\]' $TMP || exit 1
|
||||
$TMUX display -p '[#{t/f/zz:@huge}]' >$TMP
|
||||
(echo "[zz]"|cmp -s - $TMP) || exit 1
|
||||
|
||||
$TMUX display -p '[#{t:@bad}]' >$TMP
|
||||
(echo "[]"|cmp -s - $TMP) || exit 1
|
||||
$TMUX display -p '[#{t/p:@bad}]' >$TMP
|
||||
(echo "[]"|cmp -s - $TMP) || exit 1
|
||||
$TMUX display -p '[#{t/f/zz:@bad}]' >$TMP
|
||||
(echo "[]"|cmp -s - $TMP) || exit 1
|
||||
|
||||
exit 0
|
||||
@@ -71,7 +71,10 @@ cursor_very_visible
|
||||
cursor_x
|
||||
cursor_y
|
||||
history_all_bytes
|
||||
history_added
|
||||
history_bytes
|
||||
history_collected
|
||||
history_generation
|
||||
history_limit
|
||||
history_size
|
||||
host
|
||||
@@ -103,6 +106,11 @@ pane_at_right
|
||||
pane_at_top
|
||||
pane_bg
|
||||
pane_bottom
|
||||
pane_command_duration
|
||||
pane_command_end_time
|
||||
pane_command_running
|
||||
pane_command_start_time
|
||||
pane_command_status
|
||||
pane_current_command
|
||||
pane_current_path
|
||||
pane_dead
|
||||
@@ -120,16 +128,21 @@ pane_index
|
||||
pane_input_off
|
||||
pane_key_mode
|
||||
pane_last
|
||||
pane_last_output_time
|
||||
pane_last_prompt_time
|
||||
pane_left
|
||||
pane_marked
|
||||
pane_marked_set
|
||||
pane_modal_flag
|
||||
pane_mode
|
||||
pane_output_generation
|
||||
pane_path
|
||||
pane_pb_progress
|
||||
pane_pb_state
|
||||
pane_pid
|
||||
pane_pipe
|
||||
pane_pipe_pid
|
||||
pane_private_modes
|
||||
pane_right
|
||||
pane_search_string
|
||||
pane_start_command
|
||||
@@ -141,6 +154,8 @@ pane_title
|
||||
pane_top
|
||||
pane_tty
|
||||
pane_unseen_changes
|
||||
pane_unzoomed_height
|
||||
pane_unzoomed_width
|
||||
pane_width
|
||||
pane_x
|
||||
pane_y
|
||||
@@ -206,7 +221,10 @@ window_layout
|
||||
window_linked
|
||||
window_linked_sessions
|
||||
window_linked_sessions_list
|
||||
window_manual_height
|
||||
window_manual_width
|
||||
window_marked_flag
|
||||
window_modal_pane
|
||||
window_name
|
||||
window_offset_x
|
||||
window_offset_y
|
||||
|
||||
@@ -76,14 +76,14 @@ $TMUX set-hook -g session-closed \
|
||||
fail "set-hook session-closed failed"
|
||||
|
||||
# The only pane of the only window of a session exits: pane-exited, then
|
||||
# window-unlinked, then session-closed, each seeing the dead object in the
|
||||
# session-closed, then window-unlinked, each seeing the dead object in the
|
||||
# hook formats.
|
||||
pane=$($TMUX new -d -s doomed -n dwin -P -F '#{pane_id}' 'true') ||
|
||||
fail "new-session doomed failed"
|
||||
wait_for @log \
|
||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
||||
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||
assert_unchanged @log \
|
||||
"|pane-exited:$pane|window-unlinked:doomed:dwin|session-closed:doomed"
|
||||
"|pane-exited:$pane|session-closed:doomed|window-unlinked:doomed:dwin"
|
||||
|
||||
# The dead pane, window and session cannot be used as targets but the
|
||||
# server survives.
|
||||
@@ -110,14 +110,14 @@ $TMUX set-hook -g pane-exited \
|
||||
'set -gF @log "#{@log}|pane-exited:#{hook_pane}"' ||
|
||||
fail "restore pane-exited hook failed"
|
||||
|
||||
# kill-window on the last window: window-unlinked then session-closed but
|
||||
# kill-window on the last window: session-closed then window-unlinked but
|
||||
# no pane-exited for the panes in the killed window.
|
||||
$TMUX set -g @log '' || fail "reset @log failed"
|
||||
$TMUX new -d -s doomed2 -n dwin2 || fail "new-session doomed2 failed"
|
||||
$TMUX splitw -d -t doomed2:0 || fail "split-window doomed2 failed"
|
||||
$TMUX kill-window -t doomed2:0 || fail "kill-window failed"
|
||||
wait_for @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
||||
assert_unchanged @log '|window-unlinked:doomed2:dwin2|session-closed:doomed2'
|
||||
wait_for @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||
assert_unchanged @log '|session-closed:doomed2|window-unlinked:doomed2:dwin2'
|
||||
$TMUX has -t main || fail "server died after kill-window chain"
|
||||
|
||||
# kill-session: session-closed fires first, then window-unlinked for its
|
||||
|
||||
@@ -218,11 +218,13 @@ $TMUX set-hook -gu pane-mode-changed || fail "unset pane-mode-changed failed"
|
||||
|
||||
# pane-exited when a pane's command exits with remain-on-exit off.
|
||||
$TMUX set -g @x 0 || fail "set @x failed"
|
||||
$TMUX set-hook -g pane-exited 'set -gF @x "#{hook}:#{hook_pane}"' ||
|
||||
$TMUX set-hook -g pane-exited \
|
||||
'set -gF @x "#{hook}:#{hook_pane}:#{hook_exit_status}:#{hook_exit_success}"' ||
|
||||
fail "set-hook pane-exited failed"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' 'true') ||
|
||||
fail "split-window true failed"
|
||||
wait_for @x "pane-exited:$pane"
|
||||
pane=$($TMUX splitw -d -t main:0 -P -F '#{pane_id}' \
|
||||
'trap "" HUP; exec </dev/null >/dev/null 2>&1; sleep 1; exit 42') ||
|
||||
fail "split-window exit 42 failed"
|
||||
wait_for @x "pane-exited:$pane:42:0"
|
||||
$TMUX set-hook -gu pane-exited || fail "unset pane-exited failed"
|
||||
|
||||
# pane-died when a pane's command exits with remain-on-exit on.
|
||||
|
||||
@@ -13,11 +13,8 @@ $TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
OUT=$(mktemp)
|
||||
cleanup() {
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -f "$TMP" "$OUT"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
trap 'rm -f "$TMP" "$OUT"; $TMUX kill-server 2>/dev/null' 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
cat <<EOF >"$TMP"
|
||||
if 'true' 'wibble wobble'
|
||||
|
||||
@@ -4,6 +4,9 @@ TERM=screen
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
# Keep panes alive until the next case or cleanup, without reading replies.
|
||||
INPUT_HOLD='while :; do sleep 3600; done'
|
||||
|
||||
TMP=$(mktemp)
|
||||
EXP=$(mktemp)
|
||||
trap 'rm -f "$TMP" "$EXP"; $TMUX kill-server 2>/dev/null' 0 1 15
|
||||
@@ -37,10 +40,10 @@ start_pane_hlimit()
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "sleep 2" || exit 1
|
||||
$TMUX new-session -d -x 1 -y 1 -s test-setup "$INPUT_HOLD" || exit 1
|
||||
$TMUX set-option -g history-limit "$hlimit" || exit 1
|
||||
$TMUX new-session -d -x "$sx" -y "$sy" -s "$name" \
|
||||
"printf '$seq'; sleep 2" || exit 1
|
||||
"printf '$seq'; $INPUT_HOLD" || exit 1
|
||||
$TMUX kill-session -t test-setup
|
||||
sleep 0.3
|
||||
}
|
||||
|
||||
@@ -22,7 +22,19 @@ assert_key () {
|
||||
clear-history -t$W \; \
|
||||
send-keys -t$W "$key" 'EOL' || exit 1
|
||||
|
||||
actual_code=$($TMUX capturep -pt$W | \
|
||||
# cat echoes the keys back asynchronously, so wait for the EOL marker
|
||||
# to reach the pane instead of capturing straight away.
|
||||
i=0
|
||||
while [ $i -lt 50 ]; do
|
||||
screen=$($TMUX capturep -pt$W)
|
||||
case "$screen" in
|
||||
*EOL*) break ;;
|
||||
esac
|
||||
i=$((i + 1))
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
actual_code=$(printf '%s\n' "$screen" | \
|
||||
head -1 | \
|
||||
sed -e 's/EOL.*$//')
|
||||
|
||||
|
||||
@@ -2,6 +2,38 @@
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
# Wait for the terminator and following text to reach the parser.
|
||||
check_discard()
|
||||
{
|
||||
name=$1
|
||||
printf 'OK\n' >"$EXP"
|
||||
i=0
|
||||
while [ "$i" -lt 300 ]; do
|
||||
capture_grid "$name" >"$TMP"
|
||||
cmp -s "$TMP" "$EXP" && return 0
|
||||
sleep 0.05
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "$name (timed out waiting for discard)"
|
||||
}
|
||||
|
||||
# The missing-terminator timer may expire before the string limit is reached
|
||||
# on a slow build. Clear any payload printed after the timeout, then check
|
||||
# that the string did not change the title and normal output has resumed.
|
||||
test_discard()
|
||||
{
|
||||
name=$1
|
||||
prefix=$2
|
||||
start_cmd "$name" 8 3 "$INPUT_HOLD"
|
||||
$TMUX select-pane -T discard-test || exit 1
|
||||
$TMUX respawn-pane -k \
|
||||
"perl -e 'print qq{$prefix}, q{x} x 1100000, qq{\e\\\\\e[H\e[2JOK}'; $INPUT_HOLD" || exit 1
|
||||
check_discard "$name"
|
||||
$TMUX display-message -p -t "$name:" '#{pane_title}' >"$TMP" || exit 1
|
||||
printf 'discard-test\n' >"$EXP"
|
||||
cmp "$TMP" "$EXP" || fail "$name title"
|
||||
}
|
||||
|
||||
start_cmd csi-param-discard 8 3 \
|
||||
"perl -e 'print qq{\e[}, q{1} x 80, qq{\030OK}'; sleep 2"
|
||||
check_capture csi-param-discard 'OK'
|
||||
@@ -10,13 +42,8 @@ start_cmd csi-interm-discard 8 3 \
|
||||
"perl -e 'print qq{\e[ \030OK}'; sleep 2"
|
||||
check_capture csi-interm-discard 'OK'
|
||||
|
||||
start_cmd osc-discard 8 3 \
|
||||
"perl -e 'print qq{\e]2;}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture osc-discard 'OK'
|
||||
|
||||
start_cmd apc-discard 8 3 \
|
||||
"perl -e 'print qq{\e_}, q{x} x 1100000, qq{\e\\\\OK}'; sleep 2"
|
||||
check_capture apc-discard 'OK'
|
||||
test_discard osc-discard '\e]2;'
|
||||
test_discard apc-discard '\e_'
|
||||
|
||||
start_pane unknown-csi 8 3 '\033[?9999zOK'
|
||||
check_capture unknown-csi 'OK'
|
||||
|
||||
47
regress/input-nowrap-collect.sh
Normal file
47
regress/input-nowrap-collect.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
|
||||
. ./input-common.inc
|
||||
|
||||
# The shortest collected prefix must leave the final cell at the right edge.
|
||||
start_pane narrow 2 3 '\033[?7lABC'
|
||||
check_capture narrow 'AC'
|
||||
check_cursor narrow '1,0'
|
||||
check_flags narrow '- AC'
|
||||
|
||||
# REP may fill the final cell, but subsequent text still overwrites it.
|
||||
start_pane repeat 5 3 '\033[?7lA\033[9bYZ'
|
||||
check_capture repeat 'AAAAZ'
|
||||
check_cursor repeat '4,0'
|
||||
check_flags repeat '- AAAAZ'
|
||||
|
||||
# Style changes must flush collected cells without moving the final cursor.
|
||||
start_pane styled 5 3 '\033[?7l\033[31mAB\033[32mCDE\033[34mZ'
|
||||
check_capture styled 'ABCDZ'
|
||||
check_cursor styled '4,0'
|
||||
check_raw_matches styled \
|
||||
'C 0,0 data=\(1,1,A\).*fg=red\[1\] ' \
|
||||
'C 0,2 data=\(1,1,C\).*fg=green\[2\] ' \
|
||||
'C 0,4 data=\(1,1,Z\).*fg=blue\[4\] '
|
||||
|
||||
# Overwriting a wide character's padding must clear the old leading cell.
|
||||
start_pane padding 8 3 '\033[?7lA\343\201\202B\r\033[2CX'
|
||||
check_capture padding 'A XB'
|
||||
check_cursor padding '3,0'
|
||||
check_raw_matches padding \
|
||||
'C 0,1 data=\(1,1, \) flags=NONE\[0\]' \
|
||||
'C 0,2 data=\(1,1,X\) flags=NONE\[0\]'
|
||||
|
||||
# Insert mode must shift existing cells even when autowrap is disabled.
|
||||
start_pane insert 5 3 '\033[?7lABCD\r\033[C\033[4hXY\033[4l'
|
||||
check_capture insert 'AXYBC'
|
||||
check_cursor insert '3,0'
|
||||
|
||||
# Re-enabling wrap after a collected prefix must retain the wrap boundary.
|
||||
start_pane toggle 5 3 '\033[?7lABCD\033[?7hEF'
|
||||
check_capture toggle 'ABCDE
|
||||
F'
|
||||
check_cursor toggle '1,1'
|
||||
check_flags toggle 'W ABCDE
|
||||
- F'
|
||||
|
||||
exit $exit_status
|
||||
@@ -51,7 +51,7 @@ $TMUX kill-server 2>/dev/null
|
||||
sleep 0.1
|
||||
$TMUX new-session -d -x 5 -y 3 -s history \; \
|
||||
set-option -g history-limit 3 \; \
|
||||
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; sleep 2" || exit 1
|
||||
respawn-pane -k "printf '01\n02\n03\n04\n05\n06'; $INPUT_HOLD" || exit 1
|
||||
sleep 0.3
|
||||
$TMUX capture-pane -pN -t history: -S - -E - | normalize_capture >"$TMP"
|
||||
printf "%s\n" '01
|
||||
|
||||
137
regress/json.sh
Executable file
137
regress/json.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Test parsing and printing JSON with display-message -j.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 1
|
||||
}
|
||||
|
||||
check()
|
||||
{
|
||||
actual=$($TMUX display-message -plj "$1" 2>&1) ||
|
||||
fail "JSON was rejected: $1: $actual"
|
||||
[ "$actual" = "$2" ] ||
|
||||
fail "JSON output mismatch: $1: expected $2, got $actual"
|
||||
}
|
||||
|
||||
check_fail()
|
||||
{
|
||||
if $TMUX display-message -plj "$1" >/dev/null 2>&1; then
|
||||
fail "invalid JSON was accepted: $1"
|
||||
fi
|
||||
}
|
||||
|
||||
$TMUX new-session -d || exit 1
|
||||
|
||||
check '{}' '{}'
|
||||
check ' { "z":true, "a":-9223372036854775808, "m":[{}, {"x":false}] } ' \
|
||||
'{"a":-9223372036854775808,"m":[{},{"x":false}],"z":true}'
|
||||
check '{"max":9223372036854775807,"min":-9223372036854775808}' \
|
||||
'{"max":9223372036854775807,"min":-9223372036854775808}'
|
||||
check '{" key":" value","{key":"[value"}' \
|
||||
'{" key":" value","{key":"[value"}'
|
||||
check '{"brace":"{value","bracket":"[value","colon":":value",'\
|
||||
'"comma":",value","space":" value"}' \
|
||||
'{"brace":"{value","bracket":"[value","colon":":value",'\
|
||||
'"comma":",value","space":" value"}'
|
||||
check '{"esc":"\"\\\/\b\f\n\r\t\u0041"}' \
|
||||
'{"esc":"\"\\\/\b\f\n\r\t\u0041"}'
|
||||
check '{"unicode":"\u0123\uabcd\uABCD"}' \
|
||||
'{"unicode":"\u0123\uabcd\uABCD"}'
|
||||
|
||||
actual=$($TMUX display-message -palj '{}' 2>&1) ||
|
||||
fail "display-message -aj rejected JSON: $actual"
|
||||
[ "$actual" = '{}' ] ||
|
||||
fail "display-message -aj did not ignore -a: got $actual"
|
||||
actual=$($TMUX display-message -pIlj '{}' 2>&1) ||
|
||||
fail "display-message -Ij rejected JSON: $actual"
|
||||
[ "$actual" = '{}' ] ||
|
||||
fail "display-message -Ij did not ignore -I: got $actual"
|
||||
|
||||
check_fail ''
|
||||
check_fail ' '
|
||||
check_fail '[]'
|
||||
check_fail 'true'
|
||||
check_fail '1'
|
||||
check_fail '"string"'
|
||||
check_fail '{}{}'
|
||||
|
||||
check_fail '{'
|
||||
check_fail '{x:1}'
|
||||
check_fail '{"":1}'
|
||||
check_fail '{"x" 1}'
|
||||
check_fail '{"x",1}'
|
||||
check_fail '{"x":}'
|
||||
check_fail '{"x":,}'
|
||||
check_fail '{"x":1'
|
||||
check_fail '{"x":1 "y":2}'
|
||||
check_fail '{"x":1,,"y":2}'
|
||||
check_fail '{"x":1,}'
|
||||
check_fail '{"x":1,"x":2}'
|
||||
check_fail '{"x":{}}{}'
|
||||
check_fail '{"x":{}} trailing'
|
||||
check_fail '{"x":{'
|
||||
check_fail '{"x":{}'
|
||||
|
||||
check_fail '{"x":['
|
||||
check_fail '{"x":[}'
|
||||
check_fail '{"x":[{}'
|
||||
check_fail '{"x":[{},}'
|
||||
check_fail '{"x":[{},]}'
|
||||
check_fail '{"x":[{}{}]}'
|
||||
check_fail '{"x":[{},,{}]}'
|
||||
check_fail '{"x":[{"y":}]}'
|
||||
check_fail '{"x":""}'
|
||||
check_fail '{"x":null}'
|
||||
check_fail '{"x":[null]}'
|
||||
check_fail '{"x":[true]}'
|
||||
check_fail '{"x":["string"]}'
|
||||
check_fail '{"x":[[]]}'
|
||||
check_fail '{"x":1.0}'
|
||||
check_fail '{"x":1e2}'
|
||||
check_fail '{"x":+1}'
|
||||
check_fail '{"x":-}'
|
||||
check_fail '{"x":0x10}'
|
||||
check_fail '{"x":01}'
|
||||
check_fail '{"x":-01}'
|
||||
check_fail '{"x":[1]}'
|
||||
check_fail '{"x":9223372036854775808}'
|
||||
check_fail '{"x":-9223372036854775809}'
|
||||
check_fail '{"x":tru}'
|
||||
check_fail '{"x":True}'
|
||||
check_fail '{"x":falsee}'
|
||||
check_fail '{"x":"bad\q"}'
|
||||
check_fail '{"x":"bad\x20"}'
|
||||
check_fail '{"x":"bad\u123"}'
|
||||
check_fail '{"x":"bad\u12x4"}'
|
||||
check_fail '{"x":"unterminated}'
|
||||
check_fail '{"x":"escaped quote\"}'
|
||||
check_fail '{"x":"line
|
||||
break"}'
|
||||
|
||||
# The maximum object nesting depth is 200.
|
||||
json='{}'
|
||||
n=1
|
||||
while [ "$n" -lt 200 ]; do
|
||||
json='{"x":'"$json"'}'
|
||||
n=$((n + 1))
|
||||
done
|
||||
check "$json" "$json"
|
||||
check_fail '{"x":'"$json"'}'
|
||||
|
||||
if $TMUX display-message -pj >/dev/null 2>&1; then
|
||||
fail "display-message -j accepted a missing message"
|
||||
fi
|
||||
|
||||
$TMUX kill-server 2>/dev/null
|
||||
exit 0
|
||||
28
regress/kill-session-zoomed.sh
Executable file
28
regress/kill-session-zoomed.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Killing a session whose window is zoomed must not crash the server:
|
||||
# window_destroy used to unzoom the window, which resized the panes and
|
||||
# fired pane-resized with the window in the payload, and releasing that
|
||||
# reference destroyed the window a second time.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
$TMUX -f/dev/null new -d -sfoo || exit 1
|
||||
$TMUX split-window -d -h -tfoo:0 || exit 1
|
||||
$TMUX resize-pane -Z -tfoo:0 || exit 1
|
||||
$TMUX new -d -sbar || exit 1
|
||||
$TMUX kill-session -tfoo || exit 1
|
||||
$TMUX has-session -tbar || exit 1
|
||||
|
||||
# The same with the zoomed window in a session killed by kill-server.
|
||||
$TMUX new -d -sbaz || exit 1
|
||||
$TMUX split-window -d -v -tbaz:0 || exit 1
|
||||
$TMUX resize-pane -Z -tbaz:0 || exit 1
|
||||
$TMUX kill-server || exit 1
|
||||
|
||||
exit 0
|
||||
1026
regress/layout-custom.sh
Normal file
1026
regress/layout-custom.sh
Normal file
File diff suppressed because it is too large
Load Diff
77
regress/layout-nesting-limit.sh
Normal file
77
regress/layout-nesting-limit.sh
Normal file
@@ -0,0 +1,77 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Version 1 layouts must reject excessive nesting without losing the session or
|
||||
# changing its layout. Check both split types and the depth limit boundary.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
DIR=$(mktemp -d) || exit 1
|
||||
TMUX="$TEST_TMUX -S$DIR/socket -f/dev/null"
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0
|
||||
trap 'exit 1' 1 2 15
|
||||
|
||||
# Generate a checksum-valid v1 layout with one child in every nested group.
|
||||
layout()
|
||||
{
|
||||
awk -v depth="$1" -v open="$2" 'BEGIN {
|
||||
closing = (open == "{" ? "}" : "]")
|
||||
body = ""
|
||||
for (i = 0; i < depth; i++)
|
||||
body = body "1x1,0,0" open
|
||||
body = body "1x1,0,0"
|
||||
for (i = 0; i < depth; i++)
|
||||
body = body closing
|
||||
|
||||
ord["0"] = 48; ord["1"] = 49; ord["x"] = 120
|
||||
ord[","] = 44; ord["{"] = 123; ord["}"] = 125
|
||||
ord["["] = 91; ord["]"] = 93
|
||||
csum = 0
|
||||
for (i = 1; i <= length(body); i++) {
|
||||
c = substr(body, i, 1)
|
||||
bit = csum % 2
|
||||
csum = int(csum / 2) + bit * 32768
|
||||
csum = (csum + ord[c]) % 65536
|
||||
}
|
||||
printf "%04x,%s", csum, body
|
||||
}'
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s deep -x80 -y24 'exec sleep 100' || exit 1
|
||||
|
||||
for open in '{' '['; do
|
||||
# A valid checksum and nesting up to the limit must still be accepted.
|
||||
value=$(layout 1000 "$open") || fail "could not generate layout"
|
||||
$TMUX select-layout -t deep "$value" ||
|
||||
fail "layout at the depth limit was rejected ($open)"
|
||||
before=$($TMUX display-message -p -t deep \
|
||||
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
|
||||
|
||||
for depth in 1001 1500; do
|
||||
value=$(layout "$depth" "$open") || fail "could not generate layout"
|
||||
$TMUX select-layout -t deep "$value" >/dev/null 2>&1 &&
|
||||
fail "excessive nesting was accepted ($open, $depth)"
|
||||
$TMUX has-session -t deep ||
|
||||
fail "server died on excessive nesting ($open, $depth)"
|
||||
after=$($TMUX display-message -p -t deep \
|
||||
'#{pane_id} #{pane_width} #{pane_height} #{window_layout}') || exit 1
|
||||
[ "$before" = "$after" ] ||
|
||||
fail "pane or layout changed after rejection ($open, $depth)"
|
||||
done
|
||||
done
|
||||
|
||||
exit 0
|
||||
126
regress/layout-set.sh
Normal file
126
regress/layout-set.sh
Normal file
@@ -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
|
||||
46
regress/list-commands.sh
Normal file
46
regress/list-commands.sh
Normal file
@@ -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
|
||||
34
regress/list-keys-sort-name.sh
Normal file
34
regress/list-keys-sort-name.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
|
||||
# list-keys -O name orders by table name, then by key within each table
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestLKSN$$ -f/dev/null"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
trap "rm -f $TMP*; $TMUX kill-server 2>/dev/null" 0 1 15
|
||||
|
||||
$TMUX new -d || exit 1
|
||||
|
||||
F='#{key_table} #{key_string}'
|
||||
|
||||
# Expected: each table in name order, keys in the table's own (key) order.
|
||||
for t in $($TMUX list-keys -F '#{key_table}' | LC_ALL=C sort -fu); do
|
||||
$TMUX list-keys -T $t -F "$F"
|
||||
done >$TMP.exp
|
||||
[ -s $TMP.exp ] || exit 1
|
||||
|
||||
$TMUX list-keys -O name -F "$F" >$TMP.out
|
||||
cmp -s $TMP.exp $TMP.out || exit 1
|
||||
|
||||
# Reversed is the exact reverse.
|
||||
$TMUX list-keys -O name -r -F "$F" |
|
||||
awk '{ l[NR] = $0 } END { for (i = NR; i > 0; i--) print l[i] }' \
|
||||
>$TMP.out
|
||||
cmp -s $TMP.exp $TMP.out || exit 1
|
||||
|
||||
exit 0
|
||||
164
regress/menu-live-output.sh
Normal file
164
regress/menu-live-output.sh
Normal file
@@ -0,0 +1,164 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Inspect an attached client's terminal while panes write behind a menu.
|
||||
# Incremental output must agree with the scene renderer, including floating
|
||||
# panes and a client viewport smaller than the window.
|
||||
|
||||
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"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
TMP=$(mktemp -d)
|
||||
|
||||
cleanup() {
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
rm -f "$TMP/output" "$TMP/float" "$TMP/actual" "$TMP/expected"
|
||||
rmdir "$TMP"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# A full scene redraw is the reference for each incremental update. Check an
|
||||
# uncovered marker before refreshing, so a frozen pane cannot pass the test.
|
||||
check_update() {
|
||||
printf '%b' "$2" >&3
|
||||
printf '\033[1;1H\033[2K%s' "$1" >&3
|
||||
sleep 1
|
||||
$TMUX capturep -p >"$TMP/actual" || exit 1
|
||||
grep -q "^$1" "$TMP/actual" || fail "$1: pane stopped updating"
|
||||
$TMUX capturep -pe >"$TMP/actual" || exit 1
|
||||
$TMUX2 refresh-client || exit 1
|
||||
sleep 1
|
||||
$TMUX capturep -pe >"$TMP/expected" || exit 1
|
||||
if ! cmp -s "$TMP/actual" "$TMP/expected"; then
|
||||
diff -u "$TMP/expected" "$TMP/actual" >&2
|
||||
fail "$1: incremental output differs from scene redraw"
|
||||
fi
|
||||
}
|
||||
|
||||
menu() {
|
||||
$TMUX2 display-menu -b simple -T live -C 0 "$@" \
|
||||
alpha a "" beta b "" || exit 1
|
||||
sleep 1
|
||||
$TMUX capturep -p >"$TMP/actual" || exit 1
|
||||
grep -q alpha "$TMP/actual" || fail "menu did not appear"
|
||||
}
|
||||
|
||||
mkfifo "$TMP/output" "$TMP/float" || exit 1
|
||||
$TMUX2 new -d -x40 -y14 "exec cat '$TMP/output'" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g window-size manual || exit 1
|
||||
$TMUX2 resizew -x40 -y14 || exit 1
|
||||
$TMUX2 set -as terminal-features ',screen:clipboard' || exit 1
|
||||
$TMUX2 set -g set-clipboard on || exit 1
|
||||
exec 3>"$TMP/output"
|
||||
|
||||
$TMUX new -d -x40 -y14 "$TMUX2 attach" || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX resizew -x40 -y14 || exit 1
|
||||
$TMUX set -g set-clipboard on || exit 1
|
||||
sleep 1
|
||||
|
||||
menu -x6 -y8
|
||||
check_update text '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789'
|
||||
check_update clear-line '\033[6;1H\033[2K'
|
||||
check_update clear-end-line '\033[6;1H\033[K'
|
||||
check_update clear-start-line '\033[6;40H\033[1K'
|
||||
check_update erase-character '\033[6;1H\033[40X'
|
||||
check_update clear-screen '\033[2J'
|
||||
check_update clear-end-screen '\033[3;1H\033[J'
|
||||
check_update clear-start-screen '\033[10;40H\033[1J'
|
||||
check_update scroll '\033[14;1Hone\r\ntwo\r\nthree\r\nfour\r\n'
|
||||
check_update reverse-scroll '\033[1;1H\033M'
|
||||
check_update insert-character '\033[6;1Habcdefghijklmnopqrstuvwxyz\033[6;1H\033[3@'
|
||||
check_update delete-character '\033[6;1Habcdefghijklmnopqrstuvwxyz\033[6;1H\033[3P'
|
||||
check_update insert-line '\033[5;1H\033[2L'
|
||||
check_update delete-line '\033[5;1H\033[2M'
|
||||
check_update wide-text '\033[6;6H\347\225\214\347\225\214\347\225\214'
|
||||
check_update overwrite-wide '\033[6;5H\347\225\214\033[6;6Hx'
|
||||
check_update insert-mode '\033[6;1H\033[4habc\033[4l'
|
||||
check_update sync '\033[?2026h\033[6;1Habcdefghijklmnopqrstuvwxyz\033[?2026l'
|
||||
check_update sync-scroll '\033[?2026h\033[14;1Hone\r\ntwo\r\nthree\r\n\033[?2026l'
|
||||
|
||||
# Text on the right of the menu crosses the client viewport edge. A terminal
|
||||
# fallback which redraws the entire pane line would overwrite the menu.
|
||||
$TMUX2 resizew -x60 -y14 || exit 1
|
||||
sleep 1
|
||||
check_update viewport '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx'
|
||||
$TMUX2 resizew -x40 -y14 || exit 1
|
||||
|
||||
# Window coordinates must still be used when the status line is above it.
|
||||
$TMUX2 set -g status on || exit 1
|
||||
$TMUX2 set -g status-position top || exit 1
|
||||
$TMUX2 set -g status-format[0] '' || exit 1
|
||||
$TMUX2 resizew -y13 || exit 1
|
||||
sleep 1
|
||||
check_update top-status '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789'
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 resizew -y14 || exit 1
|
||||
|
||||
# Both the menu and floating panes obscure the base pane. The menu also
|
||||
# obscures incremental output from the floating pane itself.
|
||||
FLOAT=$($TMUX2 new-pane -dPF '#{pane_id}' -x24 -y8 -X4 -Y2 \
|
||||
"exec cat '$TMP/float'") || exit 1
|
||||
exec 4>"$TMP/float"
|
||||
sleep 1
|
||||
check_update behind-float '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789'
|
||||
printf '\033[4;1HABCDEFGHIJKLMNOPQRSTUVWX' >&4
|
||||
check_update float-text ''
|
||||
printf '\033[4;1H\033[4hxyz\033[4l' >&4
|
||||
check_update float-insert ''
|
||||
printf '\033[8;1Hone\r\ntwo\r\nthree\r\n' >&4
|
||||
check_update float-scroll ''
|
||||
$TMUX2 kill-pane -t "$FLOAT" || exit 1
|
||||
|
||||
# Without a menu, the same whole-line and insert-mode paths must continue to
|
||||
# respect floating panes. This also exercises split visible ranges.
|
||||
$TMUX send Escape || exit 1
|
||||
FLOAT=$($TMUX2 new-pane -dPF '#{pane_id}' -x12 -y6 -X6 -Y3 \
|
||||
'exec sleep 100') || exit 1
|
||||
$TMUX2 resizew -x60 -y14 || exit 1
|
||||
sleep 1
|
||||
check_update float-viewport '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwx'
|
||||
check_update float-insert-mode '\033[6;1H\033[4habc\033[4l'
|
||||
$TMUX2 kill-pane -t "$FLOAT" || exit 1
|
||||
$TMUX2 resizew -x40 -y14 || exit 1
|
||||
|
||||
# A menu covering an entire line leaves an empty visible range. Borderless
|
||||
# menus and menus larger than the window must use their actual screen size.
|
||||
menu -b none -x0 -y8
|
||||
check_update borderless '\033[7;1Habcdefghijklmnopqrstuvwxyz0123456789'
|
||||
$TMUX send Escape || exit 1
|
||||
$TMUX2 display-menu -b simple -T live -C 0 -x0 -y8 \
|
||||
'This menu is wider than the whole window' a '' beta b '' || exit 1
|
||||
sleep 1
|
||||
check_update full-width '\033[6;1Habcdefghijklmnopqrstuvwxyz0123456789'
|
||||
|
||||
# Nonvisual output is still delivered while a menu is open.
|
||||
printf '\033]52;c;bWVudS1jbGlwYm9hcmQ=\007' >&3
|
||||
sleep 1
|
||||
[ "$($TMUX show-buffer 2>/dev/null)" = menu-clipboard ] ||
|
||||
fail "menu blocked the clipboard update"
|
||||
|
||||
# The pane grid must keep updating under the menu, both for capture-pane and
|
||||
# for the redraw when the menu closes.
|
||||
printf '\033[2J\033[6;7Hlatest content' >&3
|
||||
sleep 1
|
||||
$TMUX2 capturep -p -t%0 >"$TMP/actual" || exit 1
|
||||
grep -q 'latest content' "$TMP/actual" || fail "underlying pane is stale"
|
||||
$TMUX send Escape || exit 1
|
||||
sleep 1
|
||||
$TMUX capturep -p >"$TMP/actual" || exit 1
|
||||
grep -q 'latest content' "$TMP/actual" || fail "menu close left stale content"
|
||||
|
||||
exit 0
|
||||
@@ -337,9 +337,9 @@ $TMUX set -g @modal-prefix no
|
||||
$TMUX set -g @modal-root no
|
||||
$TMUX bind -n z set -g @modal-root yes
|
||||
|
||||
modal=$($TMUX new-pane -OKPF '#{pane_id}' -t "$p0" \
|
||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'cat') ||
|
||||
fail "new-pane -OK failed"
|
||||
fail "new-pane -ODK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
@@ -356,9 +356,151 @@ new_left=$(fmt "$modal" '#{pane_left}')
|
||||
new_top=$(fmt "$modal" '#{pane_top}')
|
||||
[ "$new_left" -gt "$left" ] || [ "$new_top" -gt "$top" ] ||
|
||||
fail "key-capturing modal pane did not move"
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
modal=$($TMUX new-pane -ODKPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'trap "" INT; exec cat') ||
|
||||
fail "new-pane -ODK failed"
|
||||
sleep 1
|
||||
$TMUX2 send-keys -t "$OUTER" C-c
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
# A dead modal does not close on Escape or C-c without -D.
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit on
|
||||
sleep 2
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
|
||||
# failed-key closes successful panes and retains failed panes until a key.
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||
sleep 2
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
modal=$($TMUX new-pane -OPF '#{pane_id}' -t "$p0" \
|
||||
-x 20 -y 5 -X 20 -Y 10 'sleep 1; exit 1') ||
|
||||
fail "new-pane -O failed"
|
||||
check_ok set-option -p -t "$modal" remain-on-exit failed-key
|
||||
sleep 2
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}:#{pane_modal_flag}')" 1:1
|
||||
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
$TMUX bind P display-popup -E -t "$p0" -w 20 -h 5 -T popup-title 'cat'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b P
|
||||
sleep 1
|
||||
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||
[ -n "$modal" ] || fail "display-popup did not create a modal pane"
|
||||
must_equal "$(fmt "$modal" '#{pane_title}')" popup-title
|
||||
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-status)" top
|
||||
must_equal "$($TMUX show-options -pv -t "$modal" pane-border-format)" \
|
||||
'#{pane_title}'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b x z Enter
|
||||
sleep 1
|
||||
must_equal "$($TMUX show -gv @modal-prefix)" no
|
||||
must_equal "$($TMUX show -gv @modal-root)" no
|
||||
case "$($TMUX capture-pane -pt "$modal")" in
|
||||
*xz*) ;;
|
||||
*) fail "keys did not reach display-popup pane" ;;
|
||||
esac
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
$TMUX2 send-keys -t "$OUTER" C-c
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
# Creating a popup pane must not fire the split-window hook.
|
||||
check_ok set-hook -t modal after-split-window \
|
||||
"set-option -g @popup-after-split yes"
|
||||
check_ok set-option -g @popup-after-split no
|
||||
check_ok display-popup -E -t "$p0" true
|
||||
must_equal "$($TMUX show-option -gv @popup-after-split)" no
|
||||
check_ok set-hook -u -t modal after-split-window
|
||||
|
||||
# A borderless popup must not create a zero-sized pane in a tiny window.
|
||||
check_ok new-window -d -t modal: -n popup-small 'cat'
|
||||
check_ok set-option -w -t modal:popup-small window-size manual
|
||||
check_ok resize-window -t modal:popup-small -x 1 -y 1
|
||||
small=$(fmt modal:popup-small '#{pane_id}')
|
||||
check_ok bind Z display-popup -B -t "$small" 'cat'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b Z
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:popup-small '#{window_panes}')" 1
|
||||
must_equal "$(fmt modal:popup-small '#{window_modal_pane}')" ''
|
||||
|
||||
$TMUX bind D display-popup -t "$p0" -w 20 -h 5 'printf done'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b D
|
||||
sleep 2
|
||||
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||
[ -n "$modal" ] || fail "retained display-popup was not created"
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||
case "$($TMUX capture-pane -pt "$modal")" in
|
||||
*'Pane is dead'*) fail "display-popup showed remain-on-exit message" ;;
|
||||
esac
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
$TMUX bind K display-popup -k -t "$p0" -w 20 -h 5 'printf done'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b K
|
||||
sleep 2
|
||||
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||
[ -n "$modal" ] || fail "display-popup -k was not created"
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
$TMUX bind F display-popup -EE -t "$p0" -w 20 -h 5 'exit 1'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b F
|
||||
sleep 2
|
||||
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||
[ -n "$modal" ] || fail "failed display-popup -EE did not remain"
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
$TMUX2 send-keys -t "$OUTER" Escape
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" "$modal"
|
||||
check_ok kill-pane -t "$modal"
|
||||
sleep 1
|
||||
|
||||
check_ok display-popup -EE -t "$p0" true
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
$TMUX bind G display-popup -EE -k -t "$p0" -w 20 -h 5 'exit 1'
|
||||
$TMUX2 send-keys -t "$OUTER" C-b G
|
||||
sleep 2
|
||||
modal=$(fmt modal:0 '#{window_modal_pane}')
|
||||
[ -n "$modal" ] || fail "failed display-popup -EE -k did not remain"
|
||||
must_equal "$(fmt "$modal" '#{pane_dead}')" 1
|
||||
must_equal "$($TMUX show-options -pv -t "$modal" remain-on-exit)" failed-key
|
||||
$TMUX2 send-keys -t "$OUTER" a
|
||||
sleep 1
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
check_ok display-popup -EE -k -t "$p0" true
|
||||
must_equal "$(fmt modal:0 '#{window_modal_pane}')" ''
|
||||
|
||||
# A nonmodal floating pane may remain above zoom, and switching between it and
|
||||
# the zoomed tiled pane must not unzoom the window.
|
||||
check_ok new-window -d -t modal: -n float-over-zoom 'cat'
|
||||
|
||||
0
regress/mode-kill.sh
Executable file → Normal file
0
regress/mode-kill.sh
Executable file → Normal file
71
regress/mode-tree-menu-position.sh
Normal file
71
regress/mode-tree-menu-position.sh
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Chooser menus are centred on the mouse using their displayed width. A
|
||||
# hidden title must not move a borderless menu away from the mouse.
|
||||
|
||||
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"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s short -x 100 -y 30 'sleep 100' || exit 1
|
||||
$TMUX set -g mouse on || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX2 new-session -d -x 100 -y 30 "$TMUX attach" || exit 1
|
||||
sleep 1
|
||||
|
||||
for name in short aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; do
|
||||
$TMUX rename-session "$name" || exit 1
|
||||
for lines in none single padded; do
|
||||
$TMUX set -g menu-border-lines "$lines" || exit 1
|
||||
$TMUX choose-tree -s || exit 1
|
||||
sleep 1
|
||||
|
||||
# Right-button press at window (60, 0), away from either edge.
|
||||
seq=$(printf '\033[<2;61;1M')
|
||||
$TMUX2 send-keys -l "$seq" || exit 1
|
||||
sleep 1
|
||||
|
||||
row=2
|
||||
column=54
|
||||
if [ "$lines" = none ]; then
|
||||
row=1
|
||||
elif [ "$name" != short ]; then
|
||||
column=31
|
||||
fi
|
||||
text=$($TMUX2 capture-pane -p | awk -v row="$row" \
|
||||
-v column="$column" 'NR == row {
|
||||
# Keep byte offsets equal to columns with non-Unicode awk.
|
||||
gsub(/│/, "|")
|
||||
print substr($0, column, 7)
|
||||
}')
|
||||
[ "$text" = 'Select ' ] || \
|
||||
fail "$lines menu for $name: expected Select at ($column, $row), got '$text'"
|
||||
|
||||
# Close the menu, release the button, then leave the chooser.
|
||||
$TMUX2 send-keys Escape || exit 1
|
||||
seq=$(printf '\033[<2;61;1m')
|
||||
$TMUX2 send-keys -l "$seq" || exit 1
|
||||
$TMUX2 send-keys q || exit 1
|
||||
sleep 1
|
||||
done
|
||||
done
|
||||
|
||||
exit 0
|
||||
168
regress/mode-tree-scroll.sh
Normal file
168
regress/mode-tree-scroll.sh
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tests that the scroll position of a mode tree list (mode-tree.c) is kept
|
||||
# when the list is rebuilt, using choose-buffer. Buffer mode rebuilds its list
|
||||
# every time the status line is redrawn, which used to move the view so the
|
||||
# selected line was on the bottom row (or the list jumped back to the top)
|
||||
# even if the selected line was already visible. Also checks that when the
|
||||
# list shrinks the view does not leave empty lines at the bottom.
|
||||
#
|
||||
# The list is drawn on a mode screen which capture-pane does not show, so - as
|
||||
# in choose-buffer.sh - a second server provides a client: an inner "tmux
|
||||
# attach" runs inside a pane of the second server, and that pane is captured
|
||||
# to read what the inner client rendered.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMP=$(mktemp -d) || exit 1
|
||||
TMUX_TMPDIR="$TMP"
|
||||
export TMUX_TMPDIR
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
rm -rf "$TMP"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# capture the screen rendered by the inner client
|
||||
capture()
|
||||
{
|
||||
$TMUX2 capture-pane -p -t out:0 2>/dev/null
|
||||
}
|
||||
|
||||
# wait_for $marker
|
||||
#
|
||||
# Wait (up to ~10s) until the rendered screen contains $marker. The matching
|
||||
# capture is left in CAPTURED.
|
||||
wait_for()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CAPTURED=$(capture)
|
||||
if printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1'"
|
||||
}
|
||||
|
||||
# wait_gone $marker
|
||||
#
|
||||
# Wait (up to ~10s) until the rendered screen no longer contains $marker. The
|
||||
# matching capture is left in CAPTURED.
|
||||
wait_gone()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CAPTURED=$(capture)
|
||||
if ! printf '%s\n' "$CAPTURED" | grep -F -q "$1"; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "timed out waiting for '$1' to disappear"
|
||||
}
|
||||
|
||||
# wait_clients $n
|
||||
#
|
||||
# Wait (up to ~10s) until the test server has exactly $n clients attached.
|
||||
wait_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 10 ]; do
|
||||
c=$($TMUX list-clients -F x 2>/dev/null | grep -c x)
|
||||
[ "$c" -eq "$1" ] && return 0
|
||||
sleep 1
|
||||
i=$((i + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# first_line
|
||||
#
|
||||
# Print the name of the first buffer shown in CAPTURED.
|
||||
first_line()
|
||||
{
|
||||
printf '%s\n' "$CAPTURED" | grep -F ': S1' | head -1 | \
|
||||
sed 's/.*\(buf[0-9a-z]*\): S1.*/\1/'
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s aaa -x 80 -y 24 'cat' || exit 1
|
||||
|
||||
$TMUX2 new-session -d -s out -x 80 -y 24 "$TMUX attach -t aaa" || exit 1
|
||||
wait_clients 1 || fail "no client attached to test server"
|
||||
|
||||
# Forty buffers, more than fit on the screen.
|
||||
n=0
|
||||
while [ "$n" -lt 40 ]; do
|
||||
$TMUX set-buffer -b "$(printf 'buf%02d' "$n")" "buffer $n" || exit 1
|
||||
n=$((n + 1))
|
||||
done
|
||||
|
||||
# Without a preview the list has the whole pane (23 lines with the status
|
||||
# line), so buf00 to buf22 are visible.
|
||||
$TMUX choose-buffer -t aaa:0 -N -O name -F 'S1' || exit 1
|
||||
wait_for 'buf00: S1'
|
||||
|
||||
# --- scrolling down moves the view -------------------------------------------
|
||||
#
|
||||
# Selecting buf30 scrolls so it is on the bottom row: buf08 is at the top.
|
||||
$TMUX send-keys -t aaa:0 -N 30 Down || exit 1
|
||||
wait_for 'buf30: S1'
|
||||
top=$(first_line)
|
||||
[ "$top" = "buf08" ] || fail "top line after scrolling is $top, not buf08"
|
||||
|
||||
# Moving up to buf20 stays inside the visible lines so the view does not move.
|
||||
$TMUX send-keys -t aaa:0 -N 10 Up || exit 1
|
||||
|
||||
# --- a rebuild keeps the view where it is ------------------------------------
|
||||
#
|
||||
# Add a buffer that sorts into the visible part of the list and redraw the
|
||||
# status line so the list is rebuilt. Once buf15a appears the rebuild has
|
||||
# happened; buf08 must still be the top line. It used to jump back to buf00.
|
||||
$TMUX set-buffer -b buf15a "new buffer" || exit 1
|
||||
$TMUX refresh-client -S || exit 1
|
||||
wait_for 'buf15a: S1'
|
||||
top=$(first_line)
|
||||
[ "$top" = "buf08" ] || fail "top line after rebuild is $top, not buf08"
|
||||
|
||||
# --- a shrinking list does not leave empty lines -----------------------------
|
||||
#
|
||||
# Go to the end of the list (buf39 is on the bottom row) and delete all but
|
||||
# the last ten buffers. They now all fit, so the view goes to the top.
|
||||
$TMUX send-keys -t aaa:0 End || exit 1
|
||||
wait_for 'buf39: S1'
|
||||
for b in $($TMUX list-buffers -F '#{buffer_name}'); do
|
||||
case "$b" in
|
||||
buf3?)
|
||||
;;
|
||||
*)
|
||||
$TMUX delete-buffer -b "$b" || exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
$TMUX refresh-client -S || exit 1
|
||||
wait_gone 'buf29: S1'
|
||||
top=$(first_line)
|
||||
[ "$top" = "buf30" ] || fail "top line after shrinking is $top, not buf30"
|
||||
[ "$(printf '%s\n' "$CAPTURED" | grep -F -c ': S1')" -eq 10 ] || \
|
||||
fail "expected 10 buffers after shrinking"
|
||||
|
||||
$TMUX send-keys -t aaa:0 q
|
||||
|
||||
exit 0
|
||||
118
regress/new-pane-mouse-status.sh
Normal file
118
regress/new-pane-mouse-status.sh
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/bin/sh
|
||||
|
||||
# A Ctrl-drag starting on the status line must not create a floating pane.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
export PATH TERM
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Lmouse-status-inner-$$ -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -Lmouse-status-outer-$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$TMUX2 kill-server >/dev/null 2>&1
|
||||
$TMUX kill-server >/dev/null 2>&1
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
seq=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$TMUX2 send-keys -t outer:0.0 -l "$seq" || fail "send mouse failed"
|
||||
sleep 0.2
|
||||
}
|
||||
|
||||
check_drag()
|
||||
{
|
||||
start=$1
|
||||
end=$2
|
||||
|
||||
# First move along the status line, then into the pane. The first
|
||||
# movement establishes the drag origin without dispatching new-pane.
|
||||
mouse 16 10 "$start" M
|
||||
mouse 48 11 "$start" M
|
||||
mouse 48 16 "$end" M
|
||||
mouse 48 18 "$end" M
|
||||
mouse 16 18 "$end" m
|
||||
|
||||
panes=$($TMUX list-panes -F '#{pane_id}') || fail "server exited"
|
||||
[ "$panes" = "$BASE" ] || fail "status-line drag created a pane: $panes"
|
||||
}
|
||||
|
||||
$TMUX new-session -d -s inner -x 60 -y 20 'sleep 100' || exit 1
|
||||
$TMUX set -g mouse on || exit 1
|
||||
$TMUX set -g default-command 'sleep 100' || exit 1
|
||||
BASE=$($TMUX list-panes -F '#{pane_id}') || exit 1
|
||||
$TMUX2 new-session -d -s outer -x 60 -y 20 'sleep 100' || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 respawn-pane -k -t outer:0.0 "$TMUX attach -t inner" || exit 1
|
||||
sleep 1
|
||||
|
||||
check_drag 20 15
|
||||
|
||||
$TMUX set -g status 2 || exit 1
|
||||
sleep 0.5
|
||||
check_drag 20 14
|
||||
|
||||
$TMUX set -g status-position top || exit 1
|
||||
sleep 0.5
|
||||
check_drag 2 8
|
||||
|
||||
$TMUX set -g status on || exit 1
|
||||
sleep 0.5
|
||||
check_drag 1 7
|
||||
|
||||
# Window and pane ranges on different status rows still dispatch Status
|
||||
# bindings; a control range dispatches its Control binding.
|
||||
$TMUX set -g status 3 || exit 1
|
||||
$TMUX set -g 'status-format[0]' '#[range=window|0]WINDOW WINDOW WINDOW#[norange]' || exit 1
|
||||
$TMUX set -g 'status-format[1]' "#[range=pane|$BASE]PANE PANE PANE PANE#[norange]" || exit 1
|
||||
$TMUX set -g 'status-format[2]' '#[range=control|0]CONTROL CONTROL CONTROL#[norange]' || exit 1
|
||||
$TMUX bind -n MouseDown1Status set -gF @status-range '#{mouse_status_range}' || exit 1
|
||||
$TMUX bind -n MouseDown1Control0 set -gF @status-range '#{mouse_status_range}' || exit 1
|
||||
$TMUX bind -n C-MouseDrag1Status set -g @status-drag status || exit 1
|
||||
$TMUX bind -n C-MouseDrag1Control0 set -g @status-drag control || exit 1
|
||||
sleep 0.5
|
||||
|
||||
row=1
|
||||
for range in window pane control; do
|
||||
mouse 0 10 "$row" M
|
||||
mouse 0 10 "$row" m
|
||||
got=$($TMUX show -gv @status-range)
|
||||
[ "$got" = "$range" ] || fail "got range '$got', expected '$range'"
|
||||
$TMUX set -g @status-drag '' || exit 1
|
||||
check_drag "$row" 9
|
||||
got=$($TMUX show -gv @status-drag)
|
||||
want=status
|
||||
[ "$range" = control ] && want=control
|
||||
[ "$got" = "$want" ] || fail "got drag '$got', expected '$want'"
|
||||
row=$((row + 1))
|
||||
done
|
||||
|
||||
# A new drag inside the pane must still use the default Pane binding.
|
||||
$TMUX set -g status on || exit 1
|
||||
sleep 0.5
|
||||
mouse 16 3 4 M
|
||||
mouse 48 10 8 M
|
||||
mouse 16 10 8 m
|
||||
panes=$($TMUX list-panes -F '#{pane_floating_flag}') || fail "server exited"
|
||||
[ "$panes" = "$(printf '0\n1')" ] || fail "pane drag did not create a pane"
|
||||
|
||||
# Starting on the status line must not dispatch Empty bindings either.
|
||||
id=$($TMUX list-panes -F '#{pane_id}' | tail -n 1)
|
||||
$TMUX kill-pane -t "$id" || exit 1
|
||||
$TMUX break-pane -W -s "$BASE" || exit 1
|
||||
$TMUX resize-pane -t "$BASE" -x 10 -y 4 || exit 1
|
||||
$TMUX move-pane -t "$BASE" -P top-left || exit 1
|
||||
sleep 0.5
|
||||
check_drag 1 10
|
||||
|
||||
exit 0
|
||||
@@ -86,6 +86,11 @@ check_value "-gv status-keys" "vi"
|
||||
check_fail "unknown value: bogus" set -g status-keys bogus
|
||||
check_value "-gv status-keys" "vi"
|
||||
|
||||
# pane-border-lines accepts rounded as a pane border style.
|
||||
check_ok set -gw pane-border-lines rounded
|
||||
check_value "-gwv pane-border-lines" "rounded"
|
||||
check_ok set -gw pane-border-lines single
|
||||
|
||||
# --- flag options ---------------------------------------------------------
|
||||
#
|
||||
# focus-events is an on/off flag. Setting with no value toggles it; explicit
|
||||
|
||||
@@ -104,6 +104,25 @@ assert_alive()
|
||||
fi
|
||||
}
|
||||
|
||||
# Compare layout geometry and pane ordering, ignoring the active pane and
|
||||
# last-pane history in the JSON layout. Selection is checked separately.
|
||||
pane_layout()
|
||||
{
|
||||
$TMUX display-message -p -t "$1" '#{window_layout}' |
|
||||
sed 's/,"a":true//g; s/,"l":[0-9][0-9]*//g'
|
||||
}
|
||||
|
||||
check_layout()
|
||||
{
|
||||
out=$(pane_layout "$1")
|
||||
if [ "$out" != "$2" ]; then
|
||||
echo "Layout for '$1' wrong."
|
||||
echo "Expected: '$2'"
|
||||
echo "But got: '$out'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# split-window geometry.
|
||||
|
||||
@@ -309,12 +328,12 @@ check_ok kill-pane -t "$p6"
|
||||
|
||||
# Zoom and unzoom preserve the exact tiled layout. Selecting another pane
|
||||
# without -Z unzooms, while -Z transfers zoom to the selected pane.
|
||||
layout=$($TMUX display-message -p -t P:0 '#{window_layout}')
|
||||
layout=$(pane_layout P:0)
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_ok select-pane -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_active}' '0:1'
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
check_ok select-pane -t "$p0"
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
@@ -322,7 +341,7 @@ check_ok select-pane -Z -t "$p2"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Directional selection temporarily restores the full layout to find its
|
||||
# neighbour, then follows the same unzoom or -Z transfer rules.
|
||||
@@ -340,7 +359,7 @@ check_ok select-pane -D -Z -t "$p0"
|
||||
check_fmt "$p2" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p2"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# The last-pane path has separate zoom handling, both with and without -Z.
|
||||
check_ok select-pane -t "$p0"
|
||||
@@ -357,7 +376,7 @@ check_ok select-pane -l -Z -t P:0
|
||||
check_fmt "$p0" '#{window_zoomed_flag}:#{pane_zoomed_flag}:#{pane_active}' \
|
||||
'1:1:1'
|
||||
check_ok resize-pane -Z -t "$p0"
|
||||
check_fmt P:0 '#{window_layout}' "$layout"
|
||||
check_layout P:0 "$layout"
|
||||
|
||||
# Killing either a hidden ordinary pane or the zoom target unzooms.
|
||||
check_ok new-window -d -t P:12 -n zoom-kill 'cat'
|
||||
|
||||
@@ -217,7 +217,36 @@ $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. ---
|
||||
# --- 11. Invalid UTF-8 input must not desynchronize the prompt buffer. ---
|
||||
$OUT send-keys -H e6 85 5f || exit 1 # invalid UTF-8, then "_"
|
||||
settle
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
|
||||
fail "invalid UTF-8 append killed inner tmux"
|
||||
status_line | grep -qF "> _" || \
|
||||
fail "invalid UTF-8 append did not keep prompt usable (got '$(status_line)')"
|
||||
$OUT send-keys Enter || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "_" ] || \
|
||||
fail "invalid UTF-8 append recovered '$($IN show -gv @r)', wanted '_'"
|
||||
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -H e6 85 04 || exit 1 # invalid UTF-8, then C-d
|
||||
settle
|
||||
$IN display-message -p '#{version}' >/dev/null 2>&1 || \
|
||||
fail "invalid UTF-8 delete killed inner tmux"
|
||||
status_line | grep -q '>' || \
|
||||
fail "invalid UTF-8 delete closed the prompt (got '$(status_line)')"
|
||||
$OUT send-keys Escape || exit 1
|
||||
settle
|
||||
[ "$($IN show -gv @r)" = "SENTINEL" ] || \
|
||||
fail "invalid UTF-8 delete accepted the prompt"
|
||||
|
||||
# --- 12. emacs cursor-marker edit, accept recovers the exact buffer. ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
$OUT send-keys -l "abc" || exit 1
|
||||
$OUT send-keys Home || exit 1
|
||||
$OUT send-keys -l "X" || exit 1
|
||||
@@ -229,7 +258,7 @@ 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. ---
|
||||
# --- 13. Unicode on the status line: insert, move, delete wide char. ---
|
||||
$IN set -g @r "" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
@@ -249,7 +278,7 @@ 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. ---
|
||||
# --- 14. 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
|
||||
@@ -264,7 +293,7 @@ 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. ---
|
||||
# --- 15. Escape closes the status-line prompt cleanly. ---
|
||||
$IN set -g @r "SENTINEL" || exit 1
|
||||
$OUT send-keys M-r || exit 1
|
||||
settle
|
||||
|
||||
173
regress/prompt-words-history.sh
Normal file
173
regress/prompt-words-history.sh
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/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
|
||||
# Wait until Escape has closed the prompt before sending another Meta key.
|
||||
# Otherwise the two can be parsed together as a single escape sequence.
|
||||
i=0
|
||||
while capture | grep -Fq '(word)'; do
|
||||
[ "$i" -lt 50 ] || fail "completion prompt did not close"
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
# 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
|
||||
113
regress/redraw-damage-accumulate.sh
Normal file
113
regress/redraw-damage-accumulate.sh
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Many wrapped rows crossing a panned viewport produce disjoint damage.
|
||||
# Every row must survive the rectangle-count limit and subsequent merging.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Laccumulate-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Laccumulate-outer-$$ -f/dev/null"
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
wait_marker()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/capture" || exit 1
|
||||
grep -q "$1" "$DIR/capture" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client did not receive $1"
|
||||
}
|
||||
cat >"$DIR/emitter.pl" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
$| = 1;
|
||||
for my $row (1 .. 80) {
|
||||
printf "\e[%d;1HROW%02d-%s", $row, $row, 'abcdefghij' x 6;
|
||||
}
|
||||
print "\e[1;1H";
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
my $output = '';
|
||||
# Each wrapped second row needs a separate damage rectangle. Keep the
|
||||
# batch below the PTY read size so all 18 arrive in the same input pass.
|
||||
for my $region (0 .. 17) {
|
||||
my $top = 1 + $region * 4;
|
||||
$output .= "\e[$top;1H" . ('A' x 80) . ('B' x 24) .
|
||||
sprintf('DAMAGE%02d', $region) . ('B' x 48);
|
||||
}
|
||||
$output .= "\e[80;21HDONE";
|
||||
syswrite STDOUT, $output;
|
||||
while (!-e "$ENV{TRIGGER}-merge") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
# Returning from the alternate screen requests a full-pane rectangle. A
|
||||
# later wrapped-row rectangle overlaps it and must not shrink that damage.
|
||||
$output = "\e[?1049h\e[40;21HALT-VISIBLE\e[?1049l";
|
||||
$output .= "\e[1;1H" . ('C' x 80) . ('D' x 24) . 'MERGE00' . ('D' x 49);
|
||||
$output .= "\e[80;21HMERGED";
|
||||
syswrite STDOUT, $output;
|
||||
sleep 100;
|
||||
PERL
|
||||
$INNER new-session -d -s inner -x 80 -y 80 \
|
||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g automatic-rename off || exit 1
|
||||
$INNER set -g status-interval 0 || exit 1
|
||||
$INNER set -as terminal-features ',screen:sync' || exit 1
|
||||
$INNER set -g pane-border-lines simple || exit 1
|
||||
$OUTER new-session -d -s outer -x 40 -y 80 'sleep 100' || exit 1
|
||||
$OUTER set -g status off || exit 1
|
||||
$OUTER set -g window-size manual || exit 1
|
||||
$OUTER set -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 "$INNER attach -t inner" || exit 1
|
||||
wait_marker ROW80
|
||||
CLIENT=$($OUTER display -p -t outer:0.0 '#{pane_tty}') || exit 1
|
||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||
# Keep the emitter inactive so its writes use a synchronized frame. Otherwise
|
||||
# its own queued output can force a full redraw and hide lost rectangles.
|
||||
$INNER new-pane -x 6 -y 3 -X 65 -Y 74 'sleep 100' || exit 1
|
||||
sleep 0.2
|
||||
: >"$DIR/trigger"
|
||||
wait_marker DONE
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/before" || exit 1
|
||||
region=0
|
||||
while [ "$region" -lt 18 ]; do
|
||||
marker=$(printf 'DAMAGE%02d' "$region")
|
||||
sed -n "$((2 + region * 4))p" "$DIR/before" | grep -q "$marker" ||
|
||||
fail "missing damage for region $region"
|
||||
region=$((region + 1))
|
||||
done
|
||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/after" || exit 1
|
||||
diff -u "$DIR/before" "$DIR/after" || fail "accumulated redraw differs from full redraw"
|
||||
: >"$DIR/trigger-merge"
|
||||
wait_marker MERGED
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/before" || exit 1
|
||||
sed -n '2p' "$DIR/before" | grep -q MERGE00 || fail "merged damage lost the wrapped row"
|
||||
$INNER refresh-client -t "$CLIENT" || exit 1
|
||||
sleep 0.2
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$DIR/after" || exit 1
|
||||
diff -u "$DIR/before" "$DIR/after" || fail "merged redraw differs from full redraw"
|
||||
exit 0
|
||||
140
regress/redraw-damage-blocked-client.sh
Normal file
140
regress/redraw-damage-blocked-client.sh
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/bin/sh
|
||||
|
||||
# One terminal stops reading while another consumes floating-pane damage.
|
||||
# The slow client must catch up after the window's shared damage is cleared.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lblocked-inner-$$ -f/dev/null"
|
||||
FAST="$TEST_TMUX -Lblocked-fast-$$ -f/dev/null"
|
||||
SLOW="$TEST_TMUX -Lblocked-slow-$$ -f/dev/null"
|
||||
STOPPED=
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$STOPPED" ] && kill -CONT "$STOPPED" 2>/dev/null
|
||||
$FAST kill-server 2>/dev/null
|
||||
$SLOW kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
wait_marker()
|
||||
{
|
||||
terminal=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 100 ]; do
|
||||
$terminal capture-pane -p >"$DIR/capture" || exit 1
|
||||
grep -q "$marker" "$DIR/capture" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "client did not receive $marker"
|
||||
}
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$FAST send-keys -l "$sequence" || exit 1
|
||||
sleep 0.2
|
||||
}
|
||||
cat >"$DIR/emitter.pl" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
$| = 1;
|
||||
print 'READY';
|
||||
while (!-e "$ENV{TRIGGER}-flood") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
my $frame = 0;
|
||||
while (!-e "$ENV{TRIGGER}-stop") {
|
||||
my $output = '';
|
||||
for my $row (1 .. 20) {
|
||||
$output .= "\e[$row;1H" . join('', map { chr(33 + ($_ + $frame) % 80) } 0 .. 77);
|
||||
}
|
||||
print $output;
|
||||
$frame++;
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
for my $row (1 .. 20) {
|
||||
printf "\e[%d;1HFINAL%02d-%s", $row, $row, '0123456789' x 7;
|
||||
}
|
||||
print "\e[20;1HDONE";
|
||||
sleep 100;
|
||||
PERL
|
||||
$INNER new-session -d -s inner -x 160 -y 80 \
|
||||
"TRIGGER='$DIR/trigger' perl '$DIR/emitter.pl'" || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g automatic-rename off || exit 1
|
||||
$INNER set -g status-interval 0 || exit 1
|
||||
$INNER set -g mouse on || exit 1
|
||||
$INNER set -g pane-border-lines simple || exit 1
|
||||
FLOAT=$($INNER new-pane -PF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5 \
|
||||
'printf FLOAT; exec sleep 100') || exit 1
|
||||
for terminal in "$FAST" "$SLOW"; do
|
||||
$terminal new-session -d -x 160 -y 80 'sleep 100' || exit 1
|
||||
$terminal set -g status off || exit 1
|
||||
$terminal set -g window-size manual || exit 1
|
||||
$terminal set -g default-terminal screen || exit 1
|
||||
$terminal respawn-pane -k "$INNER attach -t inner" || exit 1
|
||||
wait_marker "$terminal" READY
|
||||
done
|
||||
FASTCLIENT=$($FAST display -p '#{pane_tty}') || exit 1
|
||||
SLOWCLIENT=$($SLOW display -p '#{pane_tty}') || exit 1
|
||||
SLOWPID=$($SLOW display -p '#{pid}') || exit 1
|
||||
before=$($INNER display -p -c "$FASTCLIENT" '#{client_written}') || exit 1
|
||||
slowbefore=$($INNER display -p -c "$SLOWCLIENT" '#{client_written}') || exit 1
|
||||
# The large terminal keeps this backlog below the discard threshold, so
|
||||
# automatic recovery from discarded output cannot mask lost damage.
|
||||
# Stop only our outer server, leaving its inner client attached to a PTY
|
||||
# whose master is no longer read. This creates real terminal backpressure.
|
||||
STOPPED=$SLOWPID
|
||||
kill -STOP "$SLOWPID" || exit 1
|
||||
: >"$DIR/trigger-flood"
|
||||
i=0
|
||||
while :; do
|
||||
written=$($INNER display -p -c "$SLOWCLIENT" '#{client_written}') || exit 1
|
||||
fastwritten=$($INNER display -p -c "$FASTCLIENT" '#{client_written}') || exit 1
|
||||
# More than a PTY can buffer has been queued for the stopped terminal,
|
||||
# while the other terminal is still receiving the same output.
|
||||
[ "$written" -gt "$((slowbefore + 65536))" ] &&
|
||||
[ "$fastwritten" -gt "$((before + 65536))" ] && break
|
||||
[ "$i" -lt 100 ] || fail "slow client did not become blocked"
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
: >"$DIR/trigger-stop"
|
||||
wait_marker "$FAST" DONE
|
||||
[ "$($INNER display -p -c "$FASTCLIENT" '#{client_discarded}')" -eq 0 ] ||
|
||||
fail "fast client also became blocked"
|
||||
mouse 0 12 6 M
|
||||
mouse 32 42 6 M
|
||||
mouse 0 42 6 m
|
||||
[ "$($INNER display -p -t "$FLOAT" '#{pane_left}')" -eq 36 ] || fail "pane did not move"
|
||||
$FAST capture-pane -p >"$DIR/fast-before" || exit 1
|
||||
# Ensure the fast client already restored the vacated frame.
|
||||
sed -n '6p' "$DIR/fast-before" | grep -q '^FINAL06-0123456789' ||
|
||||
fail "fast client did not restore old footprint"
|
||||
kill -CONT "$SLOWPID" || exit 1
|
||||
STOPPED=
|
||||
wait_marker "$SLOW" DONE
|
||||
# Allow queued terminal output and deferred redraws to drain.
|
||||
sleep 0.5
|
||||
[ "$($INNER display -p -c "$SLOWCLIENT" '#{client_discarded}')" -eq 0 ] ||
|
||||
fail "discard recovery could mask lost deferred damage"
|
||||
$SLOW capture-pane -p >"$DIR/slow-before" || exit 1
|
||||
diff -u "$DIR/fast-before" "$DIR/slow-before" || fail "slow client did not catch up"
|
||||
$INNER refresh-client -t "$FASTCLIENT" || exit 1
|
||||
sleep 0.2
|
||||
$FAST capture-pane -p >"$DIR/after" || exit 1
|
||||
diff -u "$DIR/fast-before" "$DIR/after" || fail "damage redraw differed from full redraw"
|
||||
exit 0
|
||||
112
regress/redraw-damage-only.sh
Executable file
112
regress/redraw-damage-only.sh
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check a redraw callback which has no accompanying client redraw flags. A
|
||||
# wrapped row crossing a panned viewport cannot use the direct tty path.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Ldamage-only-outer-$$ -f/dev/null"
|
||||
EMITTER=$DIR/emitter.pl
|
||||
TRIGGER=$DIR/trigger
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_client()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
CLIENT=$($INNER list-clients -F '#{client_name}' 2>/dev/null)
|
||||
[ -n "$CLIENT" ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner client did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
for my $row (1 .. 12) {
|
||||
print "\e[$row;1H", 'o' x 79;
|
||||
}
|
||||
print "\e[1;1H";
|
||||
while (!-e $ENV{TRIGGER}) {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
my $second = ('B' x 24) . 'DAMAGE-ONLY' . ('B' x 45);
|
||||
print "\e[5;1H", ('A' x 80), $second;
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
$INNER new-session -d -s inner -x 80 -y 12 \
|
||||
"TRIGGER='$TRIGGER' perl '$EMITTER'" || 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 -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Ldamage-only-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_client
|
||||
$INNER refresh-client -t "$CLIENT" -R 20 || exit 1
|
||||
wait_outer_has oooooooooo
|
||||
|
||||
: >"$TRIGGER"
|
||||
wait_inner_has DAMAGE-ONLY
|
||||
wait_outer_has DAMAGE-ONLY
|
||||
|
||||
exit 0
|
||||
112
regress/redraw-damage-viewports.sh
Normal file
112
regress/redraw-damage-viewports.sh
Normal file
@@ -0,0 +1,112 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Moving and interactively resizing floating panes must restore both clients,
|
||||
# including a smaller client panned horizontally and vertically.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lviewports-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lviewports-outer-$$ -f/dev/null"
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$1" "$2" "$3" "$4")
|
||||
$OUTER send-keys -t wide:0.0 -l "$sequence" || exit 1
|
||||
sleep 0.2
|
||||
}
|
||||
assert_scene()
|
||||
{
|
||||
# Compare what each terminal actually received with a fresh full redraw.
|
||||
# Capture both first: refreshing one client must not repair the other.
|
||||
for target in wide small; do
|
||||
$OUTER capture-pane -p -t "$target:0.0" >"$DIR/$target-before" || exit 1
|
||||
done
|
||||
$INNER refresh-client -t "$WIDE" || exit 1
|
||||
$INNER refresh-client -t "$SMALL" || exit 1
|
||||
sleep 0.2
|
||||
for target in wide small; do
|
||||
$OUTER capture-pane -p -t "$target:0.0" >"$DIR/$target-after" || exit 1
|
||||
diff -u "$DIR/$target-before" "$DIR/$target-after" ||
|
||||
fail "$1: $target client differed from a full redraw"
|
||||
done
|
||||
}
|
||||
|
||||
cat >"$DIR/background.pl" <<'PERL'
|
||||
$| = 1;
|
||||
for my $row (1 .. 24) {
|
||||
printf "\e[%d;1HROW%02d-", $row, $row;
|
||||
print '0123456789' x 7;
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
$INNER new-session -d -s inner -x 80 -y 24 "perl '$DIR/background.pl'" || exit 1
|
||||
$INNER set -g status off || exit 1
|
||||
$INNER set -g window-size manual || exit 1
|
||||
$INNER set -g automatic-rename off || exit 1
|
||||
$INNER set -g status-interval 0 || exit 1
|
||||
$INNER set -g mouse on || exit 1
|
||||
$INNER set -g default-command 'sleep 100' || exit 1
|
||||
$INNER set -g pane-border-lines simple || exit 1
|
||||
$OUTER new-session -d -s wide -x 80 -y 24 'sleep 100' || exit 1
|
||||
$OUTER set -g status off || exit 1
|
||||
$OUTER set -g window-size manual || exit 1
|
||||
$OUTER set -g default-terminal screen || exit 1
|
||||
$OUTER new-session -d -s small -x 40 -y 12 'sleep 100' || exit 1
|
||||
for target in wide small; do
|
||||
$OUTER respawn-pane -k -t "$target:0.0" "$INNER attach -t inner" || exit 1
|
||||
done
|
||||
i=0
|
||||
while [ "$($INNER list-clients | wc -l)" -ne 2 ]; do
|
||||
[ "$i" -lt 50 ] || fail "two clients did not attach"
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
WIDE=$($OUTER display -p -t wide:0.0 '#{pane_tty}') || exit 1
|
||||
SMALL=$($OUTER display -p -t small:0.0 '#{pane_tty}') || exit 1
|
||||
$INNER refresh-client -t "$SMALL" -R 20 || exit 1
|
||||
$INNER refresh-client -t "$SMALL" -D 6 || exit 1
|
||||
FLOAT=$($INNER new-pane -d -PF '#{pane_id}' -x 16 -y 6 -X 25 -Y 9) || exit 1
|
||||
sleep 0.3
|
||||
assert_scene initial
|
||||
# Confirm that the two clients really have different viewports.
|
||||
[ "$(head -1 "$DIR/wide-before" | cut -c1-6)" = ROW01- ] || fail "wrong wide viewport"
|
||||
[ "$(head -1 "$DIR/small-before" | cut -c1-6)" != ROW01- ] || fail "small client was not panned"
|
||||
|
||||
# Meta-drag the body using move-pane -M, including a partially clipped position.
|
||||
mouse 8 30 12 M
|
||||
mouse 40 48 15 M
|
||||
[ "$($INNER display -p -t "$FLOAT" '#{pane_left}')" -eq 44 ] || fail "Meta-drag did not move pane"
|
||||
assert_scene move-right
|
||||
mouse 40 28 10 M
|
||||
assert_scene move-back
|
||||
mouse 8 28 10 m
|
||||
|
||||
# Ctrl-drag creates a new floating pane, then changes its size while held.
|
||||
mouse 16 52 14 M
|
||||
mouse 48 75 23 M
|
||||
NEW=$($INNER display -p '#{pane_id}') || exit 1
|
||||
[ "$NEW" != "$FLOAT" ] || fail "Ctrl-drag did not create a pane"
|
||||
[ "$($INNER display -p -t "$NEW" '#{pane_floating_flag}')" -eq 1 ] || fail "new pane is not floating"
|
||||
assert_scene create
|
||||
mouse 48 64 19 M
|
||||
[ "$($INNER display -p -t "$NEW" '#{pane_width}')" -eq 11 ] || fail "Ctrl-drag did not shrink pane"
|
||||
assert_scene shrink
|
||||
mouse 16 64 19 m
|
||||
assert_scene release
|
||||
exit 0
|
||||
186
regress/redraw-multiclient.sh
Executable file
186
regress/redraw-multiclient.sh
Executable file
@@ -0,0 +1,186 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Redraw a moved floating pane on both attached clients viewing the same
|
||||
# window. Window redraw work must not be consumed by only one client.
|
||||
#
|
||||
# Uses ASCII pane borders (rather than the default UTF-8 box-drawing) because
|
||||
# this test nests a real tmux client inside another tmux's pane to get a
|
||||
# genuine terminal to capture from; that nested-tmux relay has been observed
|
||||
# to mis-render a cell that previously held a multi-byte UTF-8 border
|
||||
# character being overwritten later by plain content, on the outer instance's
|
||||
# own interpretation, independent of anything the inner tmux sends. That is a
|
||||
# nested-test-harness artifact, not a real tmux bug - confirmed by replaying
|
||||
# the exact same drag sequence against a real terminal (xterm), where it
|
||||
# never reproduces. ASCII borders avoid the artifact entirely.
|
||||
|
||||
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
|
||||
INNER="$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-multi-outer-$$ -f/dev/null"
|
||||
CAPTURE=$DIR/capture
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
$OUTER kill-server 2>/dev/null
|
||||
$INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_for_clients()
|
||||
{
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
count=$($INNER list-clients 2>/dev/null | wc -l)
|
||||
[ "$count" -eq 2 ] && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "two inner clients did not attach"
|
||||
}
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
target=$1
|
||||
marker=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer pane $target did not show $marker"
|
||||
}
|
||||
|
||||
wait_float_left()
|
||||
{
|
||||
comparison=$1
|
||||
limit=$2
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
left=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
if [ "$comparison" = gt ] && [ "$left" -gt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$comparison" = lt ] && [ "$left" -lt "$limit" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "floating pane did not move"
|
||||
}
|
||||
|
||||
mouse()
|
||||
{
|
||||
sequence=$(printf '\033[<%s;%s;%s%s' "$2" "$3" "$4" "$5")
|
||||
$OUTER send-keys -t "$1" -l "$sequence" || exit 1
|
||||
sleep 0.1
|
||||
}
|
||||
|
||||
drag_float()
|
||||
{
|
||||
target=$1
|
||||
startcol=$2
|
||||
startrow=$3
|
||||
endcol=$4
|
||||
mouse "$target" 0 "$startcol" "$startrow" M
|
||||
mouse "$target" 32 "$endcol" "$startrow" M
|
||||
mouse "$target" 0 "$endcol" "$startrow" m
|
||||
}
|
||||
|
||||
assert_scene()
|
||||
{
|
||||
target=$1
|
||||
base=$2
|
||||
firstcol=$3
|
||||
lastcol=$4
|
||||
|
||||
$OUTER capture-pane -p -t "$target" >"$CAPTURE" || exit 1
|
||||
|
||||
# With ASCII (simple) borders every corner and junction is the same
|
||||
# '+', so one rectangular floating pane always draws exactly 4 of
|
||||
# them; more means a stale frame was left behind somewhere.
|
||||
corners=$(grep -o '+' "$CAPTURE" | wc -l)
|
||||
[ "$corners" -eq 4 ] ||
|
||||
fail "outer pane $target had $corners floating frames"
|
||||
|
||||
sed -n '6,11p' "$base" | cut -c"$firstcol-$lastcol" >"$DIR/want"
|
||||
sed -n '6,11p' "$CAPTURE" | cut -c"$firstcol-$lastcol" >"$DIR/got"
|
||||
cmp -s "$DIR/want" "$DIR/got" ||
|
||||
fail "outer pane $target did not restore the old floating area"
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 20 ]; do printf \"\\033[%d;1HBG-ROW-%02d-abcdefghijklmnopqrstuvwxyz0123456789\" \$((i + 1)) \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
$INNER new-session -d -s inner -x 60 -y 20 "$C" || exit 1
|
||||
$INNER set-option -g status off || exit 1
|
||||
$INNER set-option -g window-size manual || exit 1
|
||||
$INNER set-option -g mouse on || exit 1
|
||||
$INNER set-option -g default-command 'sleep 100' || exit 1
|
||||
$INNER set-option -g pane-border-lines simple || exit 1
|
||||
|
||||
$OUTER new-session -d -s outer -x 121 -y 20 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER split-window -h -t outer:0.0 'sleep 100' || exit 1
|
||||
|
||||
PANES=$($OUTER list-panes -t outer:0 -F '#{pane_id} #{pane_left}')
|
||||
LEFT=$(echo "$PANES" | sort -k2 -n | head -1 | cut -d' ' -f1)
|
||||
RIGHT=$(echo "$PANES" | sort -k2 -n | tail -1 | cut -d' ' -f1)
|
||||
[ -n "$LEFT" ] && [ -n "$RIGHT" ] || fail "could not find outer panes"
|
||||
|
||||
$OUTER respawn-pane -k -t "$LEFT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
$OUTER respawn-pane -k -t "$RIGHT" \
|
||||
"$TEST_TMUX -Lredraw-multi-inner-$$ -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
|
||||
wait_for_clients
|
||||
wait_outer_has "$LEFT" BG-ROW-19
|
||||
wait_outer_has "$RIGHT" BG-ROW-19
|
||||
$OUTER capture-pane -p -t "$LEFT" >"$DIR/base-left" || exit 1
|
||||
$OUTER capture-pane -p -t "$RIGHT" >"$DIR/base-right" || exit 1
|
||||
|
||||
FLOAT=$($INNER new-pane -dPF '#{pane_id}' -x 16 -y 5 -X 5 -Y 5) ||
|
||||
fail "could not create floating pane"
|
||||
wait_outer_has "$LEFT" '+'
|
||||
wait_outer_has "$RIGHT" '+'
|
||||
|
||||
FTOP=$($INNER display-message -p -t "$FLOAT" '#{pane_top}')
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
FWIDTH=$($INNER display-message -p -t "$FLOAT" '#{pane_width}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
|
||||
# Move right through one client and require both clients to restore the old
|
||||
# left-hand footprint.
|
||||
drag_float "$LEFT" "$GRABCOL" "$FTOP" $((GRABCOL + 30))
|
||||
wait_float_left gt 30
|
||||
assert_scene "$LEFT" "$DIR/base-left" 1 20
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 1 20
|
||||
|
||||
# Move back through the other client and check the old right-hand footprint.
|
||||
FLEFT=$($INNER display-message -p -t "$FLOAT" '#{pane_left}')
|
||||
GRABCOL=$((FLEFT + FWIDTH / 2 + 1))
|
||||
drag_float "$RIGHT" "$GRABCOL" "$FTOP" $((GRABCOL - 30))
|
||||
wait_float_left lt 10
|
||||
assert_scene "$LEFT" "$DIR/base-left" 35 60
|
||||
assert_scene "$RIGHT" "$DIR/base-right" 35 60
|
||||
|
||||
exit 0
|
||||
208
regress/redraw-screen-write.sh
Executable file
208
regress/redraw-screen-write.sh
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check that full and region screen-write fallbacks update an attached client,
|
||||
# not only tmux's internal pane grid.
|
||||
|
||||
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
|
||||
EMITTER=$DIR/emitter.pl
|
||||
CAPTURE=$DIR/capture
|
||||
INNER=
|
||||
OUTER=
|
||||
N=0
|
||||
|
||||
fail()
|
||||
{
|
||||
echo "$*" >&2
|
||||
[ -s "$CAPTURE" ] && cat "$CAPTURE" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup()
|
||||
{
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
rm -rf "$DIR"
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
wait_outer_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client did not show $marker"
|
||||
}
|
||||
|
||||
wait_outer_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$OUTER capture-pane -p -t outer:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "outer client still showed $marker"
|
||||
}
|
||||
|
||||
wait_inner_has()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 2>/dev/null |
|
||||
grep -q "$marker" && return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane did not contain $marker"
|
||||
}
|
||||
|
||||
wait_inner_lacks()
|
||||
{
|
||||
marker=$1
|
||||
i=0
|
||||
while [ "$i" -lt 50 ]; do
|
||||
$INNER capture-pane -p -t inner:0.0 >"$CAPTURE" 2>/dev/null || true
|
||||
grep -q "$marker" "$CAPTURE" || return 0
|
||||
sleep 0.1
|
||||
i=$((i + 1))
|
||||
done
|
||||
fail "inner pane still contained $marker"
|
||||
}
|
||||
|
||||
setup()
|
||||
{
|
||||
mode=$1
|
||||
[ -n "$OUTER" ] && $OUTER kill-server 2>/dev/null
|
||||
[ -n "$INNER" ] && $INNER kill-server 2>/dev/null
|
||||
N=$((N + 1))
|
||||
INNER="$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null"
|
||||
OUTER="$TEST_TMUX -Lredraw-write-outer-$$-$N -f/dev/null"
|
||||
|
||||
$INNER new-session -d -s inner -x 40 -y 12 \
|
||||
"MODE=$mode READY='$DIR/ready-$N' TRIGGER='$DIR/trigger-$N' perl '$EMITTER'" ||
|
||||
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 -x 40 -y 12 'sleep 100' || exit 1
|
||||
$OUTER set-option -g status off || exit 1
|
||||
$OUTER set-option -g window-size manual || exit 1
|
||||
$OUTER set-option -g default-terminal screen || exit 1
|
||||
$OUTER respawn-pane -k -t outer:0.0 \
|
||||
"$TEST_TMUX -Lredraw-write-inner-$$-$N -f/dev/null attach-session -t inner" ||
|
||||
exit 1
|
||||
}
|
||||
|
||||
trigger()
|
||||
{
|
||||
: >"$DIR/trigger-$N-${1:-1}"
|
||||
}
|
||||
|
||||
cat >"$EMITTER" <<'PERL'
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
$| = 1;
|
||||
my $mode = $ENV{MODE};
|
||||
my $ready = $ENV{READY};
|
||||
my $trigger = $ENV{TRIGGER};
|
||||
|
||||
sub fill_screen {
|
||||
my ($prefix) = @_;
|
||||
print "\e[2J\e[H";
|
||||
for my $row (0 .. 11) {
|
||||
printf "\e[%d;1H%s-ROW-%02d", $row + 1, $prefix, $row;
|
||||
}
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
fill_screen('RIS');
|
||||
} elsif ($mode eq 'alternate') {
|
||||
fill_screen('BASE');
|
||||
} elsif ($mode eq 'scroll') {
|
||||
fill_screen('SCROLL');
|
||||
} else {
|
||||
die "unknown mode $mode\n";
|
||||
}
|
||||
|
||||
open my $fh, '>', $ready or die "$ready: $!\n";
|
||||
close $fh;
|
||||
while (!-e "$trigger-1") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
|
||||
if ($mode eq 'ris') {
|
||||
print "\ec";
|
||||
} elsif ($mode eq 'alternate') {
|
||||
print "\e[?1049h";
|
||||
fill_screen('ALT');
|
||||
while (!-e "$trigger-2") {
|
||||
select undef, undef, undef, 0.01;
|
||||
}
|
||||
print "\e[?1049l";
|
||||
} else {
|
||||
print "\e[12;1H\r\nSCROLL-NEW";
|
||||
}
|
||||
sleep 100;
|
||||
PERL
|
||||
|
||||
# RIS clears the complete screen. The source pane and attached client must both
|
||||
# lose every old row.
|
||||
setup ris
|
||||
wait_outer_has RIS-ROW-11
|
||||
trigger
|
||||
wait_inner_lacks RIS-ROW
|
||||
wait_outer_lacks RIS-ROW
|
||||
|
||||
# Leaving the alternate screen restores every row of the base screen.
|
||||
setup alternate
|
||||
wait_outer_has BASE-ROW-11
|
||||
trigger
|
||||
wait_inner_has ALT-ROW-11
|
||||
wait_outer_has ALT-ROW-11
|
||||
wait_outer_lacks BASE-ROW
|
||||
trigger 2
|
||||
wait_inner_has BASE-ROW-11
|
||||
wait_outer_has BASE-ROW-11
|
||||
wait_outer_lacks ALT-ROW
|
||||
|
||||
# Scrolling a pane which is narrower than the terminal redraws its complete
|
||||
# region. Check the physical client row by row after the source grid shifts.
|
||||
setup scroll
|
||||
$INNER split-window -h -t inner:0 'sleep 100' || exit 1
|
||||
wait_outer_has SCROLL-ROW-11
|
||||
trigger
|
||||
wait_inner_has SCROLL-NEW
|
||||
wait_outer_has SCROLL-NEW
|
||||
row=1
|
||||
while [ "$row" -le 11 ]; do
|
||||
expected=$(printf 'SCROLL-ROW-%02d' "$row")
|
||||
actual=$(sed -n "${row}p" "$CAPTURE")
|
||||
case "$actual" in
|
||||
"$expected"*) ;;
|
||||
*) fail "outer row $row was not redrawn as $expected" ;;
|
||||
esac
|
||||
row=$((row + 1))
|
||||
done
|
||||
actual=$(sed -n '12p' "$CAPTURE")
|
||||
case "$actual" in
|
||||
SCROLL-NEW*) ;;
|
||||
*) fail "outer bottom row was not redrawn as SCROLL-NEW" ;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
52
regress/renumber-window-limit.sh
Normal file
52
regress/renumber-window-limit.sh
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/bin/sh
|
||||
|
||||
# renumber-windows with more windows than fit above base-index
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestRWLA$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
TMP=$(mktemp)
|
||||
CONF=$(mktemp)
|
||||
trap "rm -f $TMP $CONF" 0 1 15
|
||||
|
||||
cat <<EOF >$CONF
|
||||
set -g base-index 10
|
||||
set -g renumber-windows on
|
||||
EOF
|
||||
|
||||
$TMUX -f$CONF new -d -s a
|
||||
$TMUX neww -d -t a
|
||||
$TMUX neww -d -t a
|
||||
echo $($TMUX lsw -t a -F'#{window_index}') >$TMP
|
||||
(echo "10 11 12"|cmp -s - $TMP) || exit 1
|
||||
$TMUX killw -t a:11
|
||||
echo $($TMUX lsw -t a -F'#{window_index}') >$TMP
|
||||
(echo "10 11"|cmp -s - $TMP) || exit 1
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
# Use a different socket because the previous server may still be exiting.
|
||||
TMUX="$TEST_TMUX -LtestRWLB$$"
|
||||
|
||||
cat <<EOF >$CONF
|
||||
set -g base-index 2147483646
|
||||
set -g renumber-windows on
|
||||
EOF
|
||||
|
||||
$TMUX -f$CONF new -d -s a
|
||||
$TMUX neww -d -t a
|
||||
$TMUX neww -d -t a
|
||||
$TMUX neww -d -t a
|
||||
echo $($TMUX lsw -t a -F'#{window_index}') >$TMP
|
||||
(echo "0 1 2147483646 2147483647"|cmp -s - $TMP) || exit 1
|
||||
|
||||
# Three windows do not fit from 2147483646, so the indexes stay as they are.
|
||||
$TMUX killw -t a:2147483647
|
||||
echo $($TMUX lsw -t a -F'#{window_index}') >$TMP
|
||||
(echo "0 1 2147483646"|cmp -s - $TMP) || exit 1
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
exit 0
|
||||
@@ -15,7 +15,7 @@ PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -Ltest"
|
||||
TMUX="$TEST_TMUX -Ltest$$"
|
||||
$TMUX kill-server 2>/dev/null
|
||||
|
||||
DIR=$(mktemp -d)
|
||||
|
||||
55
regress/run-shell-cwd.sh
Normal file
55
regress/run-shell-cwd.sh
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/bin/sh
|
||||
|
||||
# run-shell -c should expand formats using the target pane.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
|
||||
|
||||
TMP=$(mktemp -d)
|
||||
trap '$TMUX kill-server 2>/dev/null; rm -rf "$TMP"' 0 1 15
|
||||
TMP=$(cd "$TMP" && pwd -P)
|
||||
mkdir "$TMP/first" "$TMP/second dir" || exit 1
|
||||
|
||||
check_directory()
|
||||
{
|
||||
actual=$(cat "$TMP/out")
|
||||
if [ "$actual" != "$1" ]; then
|
||||
echo "Expected directory '$1', got '$actual'"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# The start path is available immediately, before the child is scheduled.
|
||||
# Use it to test the format context in the same command queue as creation.
|
||||
$TMUX new-session -d -s test -c "$TMP/first" 'sleep 60' \; \
|
||||
run-shell -c '#{pane_start_path}' "pwd >'$TMP/out'" || exit 1
|
||||
check_directory "$TMP/first"
|
||||
|
||||
# An explicit target must supply the format context, including with a delay.
|
||||
pane=$($TMUX new-window -d -P -F '#{pane_id}' -t test \
|
||||
-c "$TMP/second dir" 'sleep 60') || exit 1
|
||||
# The current path depends on the operating system finding the child process.
|
||||
i=0
|
||||
while [ "$($TMUX display-message -p -t "$pane" '#{pane_current_path}')" != \
|
||||
"$TMP/second dir" ]; do
|
||||
if [ "$i" -ge 100 ]; then
|
||||
echo "Timed out waiting for pane current directory"
|
||||
exit 1
|
||||
fi
|
||||
sleep 0.05
|
||||
i=$((i + 1))
|
||||
done
|
||||
$TMUX run-shell -t "$pane" -d 0.1 -c '#{pane_current_path}' \
|
||||
"pwd >'$TMP/out'" || exit 1
|
||||
check_directory "$TMP/second dir"
|
||||
|
||||
# Literal directories and the default client directory still work.
|
||||
$TMUX run-shell -c "$TMP/second dir" "pwd >'$TMP/out'" || exit 1
|
||||
check_directory "$TMP/second dir"
|
||||
(cd "$TMP/first" && $TMUX run-shell "pwd >'$TMP/out'") || exit 1
|
||||
check_directory "$TMP/first"
|
||||
|
||||
exit 0
|
||||
@@ -29,7 +29,25 @@ fail() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
wait_for_panes() {
|
||||
tries=0
|
||||
while :; do
|
||||
panes=$($TMUX2 list-panes -t"$window" -F '#{pane_dead}') || exit 1
|
||||
case "$panes" in
|
||||
*0*)
|
||||
;;
|
||||
*)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
tries=$((tries + 1))
|
||||
[ "$tries" -lt 100 ] || fail "timed out waiting for panes to exit"
|
||||
sleep 0.1
|
||||
done
|
||||
}
|
||||
|
||||
compare() {
|
||||
wait_for_panes
|
||||
sleep 1
|
||||
$TMUX capturep -p $2 >$TMP || exit 1
|
||||
if [ -n "$GENERATE" ]; then
|
||||
@@ -43,14 +61,16 @@ compare() {
|
||||
|
||||
# new_scene <width> <height>: fresh inner window of the given window size.
|
||||
new_scene() {
|
||||
$TMUX2 neww -d "sh -c 'printf base; exec sleep 100'" || exit 1
|
||||
$TMUX2 selectw -t:\$ || exit 1
|
||||
window=$($TMUX2 neww -dP -F '#{window_id}' \
|
||||
"sh -c 'printf base'") || exit 1
|
||||
$TMUX2 selectw -t"$window" || exit 1
|
||||
$TMUX2 killw -a -t"$window" || exit 1
|
||||
$TMUX2 resizew -x$1 -y$2 || exit 1
|
||||
}
|
||||
|
||||
# tile_2x2: split the current window into a 2x2 grid of tiled panes.
|
||||
tile_2x2() {
|
||||
C="sh -c 'exec sleep 100'"
|
||||
C=""
|
||||
$TMUX2 splitw -h "$C" || exit 1
|
||||
$TMUX2 splitw -v "$C" || exit 1
|
||||
$TMUX2 selectp -t0 || exit 1
|
||||
@@ -60,13 +80,13 @@ tile_2x2() {
|
||||
|
||||
# fill_base_pattern: make clipped floating panes visibly cover every column.
|
||||
fill_base_pattern() {
|
||||
$TMUX2 respawnp -k "sh -c 'i=0; while [ \$i -lt 12 ]; do printf \"%02d:abcdefghijklmnopqrstuvwxyz0123456789\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
|
||||
$TMUX2 respawnp -k "sh -c 'i=0; while [ \$i -lt 12 ]; do printf \"%02d:abcdefghijklmnopqrstuvwxyz0123456789\n\" \$i; i=\$((i + 1)); done'" || exit 1
|
||||
}
|
||||
|
||||
# fill_all_panes: make each pane visibly nonblank.
|
||||
fill_all_panes() {
|
||||
for pane in $($TMUX2 list-panes -F '#{pane_id}'); do
|
||||
$TMUX2 respawnp -k -t "$pane" "sh -c 'i=0; while [ \$i -lt 5 ]; do printf \"TILE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
|
||||
$TMUX2 respawnp -k -t "$pane" "sh -c 'i=0; while [ \$i -lt 5 ]; do printf \"TILE%02d abcdefghij\n\" \$i; i=\$((i + 1)); done'" || exit 1
|
||||
done
|
||||
}
|
||||
|
||||
@@ -74,6 +94,8 @@ $TMUX kill-server 2>/dev/null
|
||||
$TMUX2 kill-server 2>/dev/null
|
||||
|
||||
$TMUX2 new -d -x40 -y12 "sh -c 'printf base; exec sleep 100'" || exit 1
|
||||
$TMUX2 set -g remain-on-exit on || exit 1
|
||||
$TMUX2 set -g remain-on-exit-format "" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g window-size manual || exit 1
|
||||
$TMUX2 set -g pane-border-format " #{pane_title} " || exit 1
|
||||
@@ -88,7 +110,7 @@ sleep 1
|
||||
|
||||
# Basic floating pane, well inside the window.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT'" || exit 1
|
||||
compare floating-basic
|
||||
|
||||
# Floating pane with a title on its border. pane-border-status also draws the
|
||||
@@ -98,65 +120,71 @@ new_scene 40 12
|
||||
$TMUX2 setw pane-border-status top || exit 1
|
||||
$TMUX2 selectp -t:.0 -T base || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y2 -T title \
|
||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf FLOAT'" || exit 1
|
||||
compare floating-title
|
||||
|
||||
# Larger floating pane with double border lines.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B double \
|
||||
"sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf FLOAT'" || exit 1
|
||||
compare floating-border-double
|
||||
|
||||
# Larger floating pane with rounded border lines.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x28 -y8 -X4 -Y1 -B rounded \
|
||||
"sh -c 'printf FLOAT'" || exit 1
|
||||
compare floating-border-rounded
|
||||
|
||||
# Floating pane with no border lines: redraw_mark_pane_borders returns early so
|
||||
# the float has no border at all, only its (clipped) content over the base pane.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x20 -y6 -X10 -Y3 -B none \
|
||||
"sh -c 'printf NOBORDER; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf NOBORDER'" || exit 1
|
||||
compare floating-noborder
|
||||
|
||||
# Floating pane positioned past the right and bottom edges: must clip.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x20 -y6 -X30 -Y8 "sh -c 'printf CLIP; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X30 -Y8 "sh -c 'printf CLIP'" || exit 1
|
||||
compare floating-clip-edge
|
||||
|
||||
# Window smaller than the client (outside area filled), with a floating pane
|
||||
# that overlaps the boundary into the outside region.
|
||||
new_scene 28 8
|
||||
$TMUX2 new-pane -x16 -y5 -X18 -Y4 "sh -c 'printf OUT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y5 -X18 -Y4 "sh -c 'printf OUT'" || exit 1
|
||||
compare floating-outside
|
||||
|
||||
# Floating pane whose right border is exactly on the window right edge. The
|
||||
# floating pane is clipped so the tiled pane's right border remains unbroken.
|
||||
new_scene 28 8
|
||||
$TMUX2 respawnp -k "sh -c 'i=0; while [ \$i -lt 8 ]; do printf \"%02d:abcdefghijklmnopq\n\" \$i; i=\$((i + 1)); done; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x10 -y5 -X19 -Y2 "sh -c 'printf EDGE; exec sleep 100'" || exit 1
|
||||
$TMUX2 respawnp -k "sh -c 'i=0; while [ \$i -lt 8 ]; do printf \"%02d:abcdefghijklmnopq\n\" \$i; i=\$((i + 1)); done'" || exit 1
|
||||
$TMUX2 new-pane -x10 -y5 -X19 -Y2 "sh -c 'printf EDGE'" || exit 1
|
||||
compare floating-clip-window-edge
|
||||
|
||||
# Floating pane clipped at the top-left corner (negative offsets).
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x18 -y6 -X-4 -Y-2 "sh -c 'printf TL; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x18 -y6 -X-4 -Y-2 "sh -c 'printf TL'" || exit 1
|
||||
compare floating-clip-topleft
|
||||
|
||||
# Floating panes clipped at each edge. The patterned base pane makes sure the
|
||||
# clipped edge still obscures both content and borders underneath.
|
||||
new_scene 40 12
|
||||
fill_base_pattern
|
||||
$TMUX2 new-pane -x20 -y6 -X-10 -Y3 "sh -c 'printf LEFT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X-10 -Y3 "sh -c 'printf LEFT'" || exit 1
|
||||
compare floating-clip-left
|
||||
|
||||
new_scene 40 12
|
||||
fill_base_pattern
|
||||
$TMUX2 new-pane -x20 -y6 -X30 -Y3 "sh -c 'printf RIGHT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X30 -Y3 "sh -c 'printf RIGHT'" || exit 1
|
||||
compare floating-clip-right
|
||||
|
||||
new_scene 40 12
|
||||
fill_base_pattern
|
||||
$TMUX2 new-pane -x20 -y6 -X10 -Y-3 "sh -c 'printf TOP; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X10 -Y-3 "sh -c 'printf TOP'" || exit 1
|
||||
compare floating-clip-top
|
||||
|
||||
new_scene 40 12
|
||||
fill_base_pattern
|
||||
$TMUX2 new-pane -x20 -y6 -X10 -Y9 "sh -c 'printf BOTTOM; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X10 -Y9 "sh -c 'printf BOTTOM'" || exit 1
|
||||
compare floating-clip-bottom
|
||||
|
||||
# Floating pane over a tiled 2x2 grid: the float draws a complete box and must
|
||||
@@ -164,7 +192,7 @@ compare floating-clip-bottom
|
||||
new_scene 40 12
|
||||
tile_2x2
|
||||
fill_all_panes
|
||||
$TMUX2 new-pane -x16 -y6 -X11 -Y3 "sh -c 'printf FLT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X11 -Y3 "sh -c 'printf FLT'" || exit 1
|
||||
compare floating-over-tiled
|
||||
|
||||
# Same, but the float uses double border lines while the tiled panes use single:
|
||||
@@ -173,13 +201,13 @@ new_scene 40 12
|
||||
tile_2x2
|
||||
fill_all_panes
|
||||
$TMUX2 new-pane -x16 -y6 -X11 -Y3 -B double \
|
||||
"sh -c 'printf FLT; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf FLT'" || exit 1
|
||||
compare floating-over-tiled-double
|
||||
|
||||
# Two overlapping floating panes: the later (top) float draws over the earlier.
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x16 -y6 -X4 -Y2 "sh -c 'printf AAA; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X14 -Y6 "sh -c 'printf BBB; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X4 -Y2 "sh -c 'printf AAA'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X14 -Y6 "sh -c 'printf BBB'" || exit 1
|
||||
compare floating-overlap
|
||||
|
||||
# Two floating panes with different per-pane configuration: one has its status on
|
||||
@@ -188,11 +216,11 @@ compare floating-overlap
|
||||
new_scene 40 12
|
||||
$TMUX2 setw pane-border-format " #{pane_title} " || exit 1
|
||||
$TMUX2 new-pane -x16 -y4 -X3 -Y1 -T one \
|
||||
"sh -c 'printf ONE; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf ONE'" || exit 1
|
||||
$TMUX2 set -p pane-border-status top || exit 1
|
||||
$TMUX2 set -p pane-border-lines single || exit 1
|
||||
$TMUX2 new-pane -x16 -y4 -X18 -Y6 -T two \
|
||||
"sh -c 'printf TWO; exec sleep 100'" || exit 1
|
||||
"sh -c 'printf TWO'" || exit 1
|
||||
$TMUX2 set -p pane-border-status bottom || exit 1
|
||||
$TMUX2 set -p pane-border-lines heavy || exit 1
|
||||
compare floating-mixed-config
|
||||
@@ -202,7 +230,7 @@ compare floating-mixed-config
|
||||
new_scene 40 12
|
||||
$TMUX2 setw pane-scrollbars on || exit 1
|
||||
$TMUX2 setw pane-scrollbars-style "bg=black,fg=white,width=1,pad=0" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X22 -Y3 "sh -c 'printf OVERSB; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x16 -y6 -X22 -Y3 "sh -c 'printf OVERSB'" || exit 1
|
||||
compare floating-over-scrollbar -e
|
||||
|
||||
# Floating pane overlapping the client status line: the status line is not part
|
||||
@@ -211,7 +239,7 @@ new_scene 40 11
|
||||
$TMUX2 set status on || exit 1
|
||||
$TMUX2 set status-position bottom || exit 1
|
||||
$TMUX2 set status-format[0] "" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y7 "sh -c 'printf OVERST; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y7 "sh -c 'printf OVERST'" || exit 1
|
||||
compare floating-over-status
|
||||
|
||||
# A window left with only a floating pane: killing the single tiled pane removes
|
||||
@@ -219,7 +247,7 @@ compare floating-over-status
|
||||
# Use a visible fill character for the EMPTY cells around the float.
|
||||
$TMUX2 set -g fill-character '#[acs]~' || exit 1
|
||||
new_scene 40 12
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT; exec sleep 100'" || exit 1
|
||||
$TMUX2 new-pane -x20 -y6 -X8 -Y3 "sh -c 'printf FLOAT'" || exit 1
|
||||
tiled=$($TMUX2 list-panes -F '#{pane_floating_flag} #{pane_id}' | \
|
||||
awk '$1==0{print $2; exit}') || exit 1
|
||||
$TMUX2 kill-pane -t "$tiled" || exit 1
|
||||
|
||||
@@ -106,9 +106,28 @@ compare menu-over-split
|
||||
|
||||
# Menu with no border lines.
|
||||
setup 40 14
|
||||
menu -b none -x6 -y8
|
||||
$TMUX2 display-menu -T "This title must not reserve width" -C 1 \
|
||||
-b none -x6 -y8 \
|
||||
"Alpha item" a "" \
|
||||
"Beta item" b "" \
|
||||
"" "" "" \
|
||||
"Gamma item" g "" || exit 1
|
||||
sleep 1
|
||||
compare menu-noborder
|
||||
|
||||
# Menu border style follows the explicitly targeted window, not the current
|
||||
# window when a different pane is selected.
|
||||
setup 40 14
|
||||
$TMUX2 set menu-border-lines none || exit 1
|
||||
$TMUX2 neww || exit 1
|
||||
$TMUX2 display-menu -t%0 -T "Menu" -C 1 \
|
||||
"Alpha item" a "" \
|
||||
"Beta item" b "" \
|
||||
"" "" "" \
|
||||
"Gamma item" g "" || exit 1
|
||||
$TMUX2 selectw -t%0 || exit 1
|
||||
compare menu-target-window-noborder
|
||||
|
||||
# Menu with double border lines.
|
||||
setup 40 14
|
||||
menu -b double -x6 -y8
|
||||
|
||||
@@ -93,6 +93,15 @@ $TMUX2 splitw -v "$C" || exit 1
|
||||
$TMUX2 select-layout tiled || exit 1
|
||||
compare outside-both-2x2
|
||||
|
||||
# Top pane status supplies internal horizontal borders, but there is no pane
|
||||
# below the window to supply its bottom edge.
|
||||
new_scene 28 9
|
||||
$TMUX2 setw pane-border-status top || exit 1
|
||||
$TMUX2 setw pane-border-format "" || exit 1
|
||||
compare outside-both-status-top-single
|
||||
$TMUX2 splitw -v "$C" || exit 1
|
||||
compare outside-both-status-top-split
|
||||
|
||||
# Window BIGGER than the client: only part of the window is viewed and the view
|
||||
# can be panned (refresh-client). This exercises a non-zero scene offset.
|
||||
# A 2x2 grid in a 60x20 window viewed through the 40x14 client.
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Exercise drawing of popups (display-popup) over the window scene. A popup is an
|
||||
# overlay drawn on top of the redraw scene (the overlay_draw path in
|
||||
# screen-redraw.c), so this guards against regressions in how popups appear.
|
||||
#
|
||||
# A popup is modal and stays open until its command exits, so each scene fully
|
||||
# re-creates the servers and re-attaches; the popup is opened in the background
|
||||
# (display-popup blocks the client that runs it) and the outer pane is captured
|
||||
# while it is open.
|
||||
#
|
||||
# Run with GENERATE=1 to (re)create the golden files.
|
||||
|
||||
PATH=/bin:/usr/bin
|
||||
TERM=screen
|
||||
LC_ALL=C.UTF-8
|
||||
export TERM LC_ALL
|
||||
|
||||
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
|
||||
TMUX=
|
||||
TMUX2=
|
||||
SETUP=0
|
||||
RESULTS=screen-redraw-results
|
||||
|
||||
TMP=$(mktemp)
|
||||
|
||||
cleanup() {
|
||||
rm -f "$TMP"
|
||||
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
|
||||
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
|
||||
}
|
||||
trap cleanup 0 1 15
|
||||
|
||||
fail() {
|
||||
echo "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
compare() {
|
||||
sleep 1
|
||||
$TMUX capturep -p >$TMP || exit 1
|
||||
if [ -n "$GENERATE" ]; then
|
||||
cp $TMP "$RESULTS/$1.result" || exit 1
|
||||
echo "generated $1"
|
||||
else
|
||||
cmp -s $TMP "$RESULTS/$1.result" || \
|
||||
fail "scene $1 differs from $RESULTS/$1.result"
|
||||
fi
|
||||
}
|
||||
|
||||
C="sh -c 'i=0; while [ \$i -lt 13 ]; do printf \"POP%02d abcdefghij\n\" \$i; i=\$((i + 1)); done; exec sleep 100'"
|
||||
|
||||
# setup: fresh inner window attached inside a fresh outer pane, 40x14.
|
||||
setup() {
|
||||
[ -n "$TMUX" ] && $TMUX kill-server 2>/dev/null
|
||||
[ -n "$TMUX2" ] && $TMUX2 kill-server 2>/dev/null
|
||||
SETUP=$((SETUP + 1))
|
||||
TMUX="$TEST_TMUX -LtestA$$-$SETUP -f/dev/null"
|
||||
TMUX2="$TEST_TMUX -LtestB$$-$SETUP -f/dev/null"
|
||||
$TMUX2 new -d -x40 -y14 "$C" || exit 1
|
||||
$TMUX2 set -g status off || exit 1
|
||||
$TMUX2 set -g window-size manual || exit 1
|
||||
$TMUX2 resizew -x40 -y14 || exit 1
|
||||
$TMUX new -d -x40 -y14 || exit 1
|
||||
$TMUX set -g status off || exit 1
|
||||
$TMUX set -g window-size manual || exit 1
|
||||
$TMUX set -g default-terminal "tmux-256color" || exit 1
|
||||
$TMUX send -l "$TMUX2 attach" || exit 1
|
||||
$TMUX send Enter || exit 1
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# popup <args>: open a popup running a fixed command, in the background (it stays
|
||||
# open because the command sleeps; the servers are killed at the next setup).
|
||||
popup() {
|
||||
$TMUX2 display-popup "$@" -E "sh -c 'printf POPUP; exec sleep 100'" &
|
||||
sleep 1
|
||||
}
|
||||
|
||||
# Basic popup over a single pane.
|
||||
setup
|
||||
popup -w20 -h6 -x6 -y3
|
||||
compare popup-basic
|
||||
|
||||
# Popup over a split: drawn on top of the pane border.
|
||||
setup
|
||||
$TMUX2 splitw -h "$C" || exit 1
|
||||
popup -w24 -h8 -x8 -y3
|
||||
compare popup-over-split
|
||||
|
||||
# Popup with no border lines (-B).
|
||||
setup
|
||||
popup -B -w20 -h6 -x6 -y3
|
||||
compare popup-noborder
|
||||
|
||||
# Popup with double border lines.
|
||||
setup
|
||||
popup -b double -w20 -h6 -x6 -y3
|
||||
compare popup-double
|
||||
|
||||
exit 0
|
||||
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
12
regress/screen-redraw-results/floating-border-rounded.result
Normal file
@@ -0,0 +1,12 @@
|
||||
base
|
||||
╭──────────────────────────╮
|
||||
│FLOAT │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
╰──────────────────────────╯
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
MENU00 abcdefghij
|
||||
MENU01 abcdefghij
|
||||
MENU02[38;5;45m[48;5;235m Menu [39m[49m
|
||||
MENU03[38;5;45m[48;5;235m [38;5;250m Alpha item (a) [38;5;45m [39m[49m
|
||||
MENU04[38;5;45m[48;5;235m [38;5;16m[48;5;220m Beta item (b) [38;5;45m[48;5;235m [39m[49m
|
||||
MENU05[38;5;45m[48;5;235m [39m[49m
|
||||
MENU06[38;5;45m[48;5;235m [38;5;250m Gamma item (g) [38;5;45m [39m[49m
|
||||
MENU07[38;5;45m[48;5;235m [39m[49m
|
||||
MENU02 abcdefghij
|
||||
MENU03 abcdefghij
|
||||
MENU04[38;5;250m[48;5;235m Alpha item (a) [39m[49m
|
||||
MENU05[38;5;16m[48;5;220m Beta item (b) [39m[49m
|
||||
MENU06[38;5;45m[48;5;235m [39m[49m
|
||||
MENU07[38;5;250m[48;5;235m Gamma item (g) [39m[49m
|
||||
MENU08 abcdefghij
|
||||
MENU09 abcdefghij
|
||||
MENU10 abcdefghij
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
MENU00 abcdefghij
|
||||
MENU01 abcdefghij
|
||||
MENU02 abcdefghij
|
||||
MENU03 abcdefghij
|
||||
MENU04 abcd[38;5;250m[48;5;235m Alpha item (a) [39m[49m
|
||||
MENU05 abcd[38;5;16m[48;5;220m Beta item (b) [39m[49m
|
||||
MENU06 abcd[38;5;45m[48;5;235m [39m[49m
|
||||
MENU07 abcd[38;5;250m[48;5;235m Gamma item (g) [39m[49m
|
||||
MENU08 abcdefghij
|
||||
MENU09 abcdefghij
|
||||
MENU10 abcdefghij
|
||||
MENU11 abcdefghij
|
||||
MENU12 abcdefghij
|
||||
@@ -0,0 +1,14 @@
|
||||
────────────────────────────┐···········
|
||||
OUT01 abcdefghij │···········
|
||||
OUT02 abcdefghij │···········
|
||||
OUT03 abcdefghij │···········
|
||||
OUT04 abcdefghij │···········
|
||||
OUT05 abcdefghij │···········
|
||||
OUT06 abcdefghij │···········
|
||||
OUT07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┘···········
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
@@ -0,0 +1,14 @@
|
||||
────────────────────────────┐···········
|
||||
OUT06 abcdefghij │···········
|
||||
OUT07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┤···········
|
||||
PAN05 abcdefghij │···········
|
||||
PAN06 abcdefghij │···········
|
||||
PAN07 abcdefghij │···········
|
||||
│···········
|
||||
────────────────────────────┘···········
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
········································
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user