Clean up image support and add regression tests

This commit is contained in:
Michael Grant
2026-10-04 11:44:00 +01:00
parent 91441d6ef2
commit 90ee6a89cc
31 changed files with 465 additions and 633 deletions

View File

@@ -1,29 +1,7 @@
#!/bin/sh
# Regression test for a border-status caching bug: window_make_pane_status()
# (window-border.c) gates the physical redraw of a pane's border-status
# title on a logical content diff (grid_compare against a cached copy), not
# on whether the physical screen cells were disturbed by something else in
# the meantime - such as a floating pane's own border, drawn on top of a
# tiled pane's border-status row, sliding across it and then away again.
# See tmux-image-redraw-known-bugs.md ("border-status text cache ignores
# physical damage") for the full write-up.
#
# Reproduction: a tiled pane with pane-border-status on has a floating pane
# dragged, by mouse, from directly over its border-status row to somewhere
# else. The tiled pane's title should reappear once the floating pane has
# moved off it; without the fix it stays blank.
#
# The drag starts and ends away from row 0 rather than grabbing the
# floating pane's border while it is already sitting on row 0: when a
# floating pane's own border-status row exactly coincides with the tiled
# pane's row 0, mouse hit-testing on that row attributes clicks to the
# tiled pane, not the floating one on top of it (an unrelated tmux quirk,
# not what this test is about). That only matters for the initial press,
# though - once a drag is under way, further motion events go straight to
# the already-bound per-pane callback without re-resolving which pane owns
# the coordinates, so starting the grab on an unambiguous row and dragging
# through row 0 works fine.
# A tiled pane's border title must reappear after a floating pane moves away.
# Start the drag below row zero so its initial press hits the floating pane.
PATH=/bin:/usr/bin
TERM=screen

View File

@@ -1,22 +1,7 @@
#!/bin/sh
# Regression test for a fixed bug: on a client detected as SIXEL-capable, a
# pane-content redraw (PANE_REDRAW) erased the image backend's rectangle
# using the redrawing pane's raw nominal geometry (wp->xoff/yoff/sx/sy)
# instead of the cells it actually owns in the current scene. When a
# floating pane occluded part of that rectangle, the erase blanked the
# floating pane's on-screen area, and since the subsequent text redraw
# correctly only refills cells the redrawing pane owns, nothing repainted
# it back in - the floating pane's content stayed blank.
#
# Fixed in screen-redraw.c:redraw_draw_pane_lines() by erasing only the
# cell ranges the pane actually owns per line (its REDRAW_SPAN_PANE spans),
# not its raw rectangle. See tmux-sixel-erase-ignores-occlusion.md and
# IMAGE-REDRAW-DISCUSSION.md for the full write-up.
#
# Confirmed by direct A/B test against this exact sequence: reliably wipes
# the floating pane's content on the unfixed code, reliably leaves it
# intact on the fixed code.
# A SIXEL pane redraw must erase only cells owned by that pane, preserving
# content in an overlapping floating pane when leaving copy mode.
PATH=/bin:/usr/bin
TERM=screen

View File

@@ -1,21 +1,6 @@
#!/bin/sh
# Regression test: Alt-dragging a floating pane by its body (move-pane -M,
# bound by default to M-MouseDrag1Pane/M-MouseDrag1Border) must not
# retransmit images in other panes of the same window.
#
# cmd_join_pane_mouse_move() (cmd-join-pane.c) is a second, separate
# implementation of "drag to move a floating pane" - parallel to the one in
# cmd-resize-pane.c already fixed for plain (non-Alt) border drags - and
# used to call server_redraw_window() unconditionally on every motion
# event, wiping and retransmitting every image in the window on each step
# of the drag even though only the floating pane's own rectangle actually
# moved. See tmux-image-redraw-known-bugs.md for the full write-up.
#
# This is checked by counting DCS (\033P) sequences in the client's raw
# output while a stationary tiled pane's image is present and an unrelated
# floating pane is Alt-dragged elsewhere in the window: with the fix, none
# should appear.
# Alt-dragging a floating pane must not retransmit images in other panes.
. ./image-noflash-common.inc

View File

@@ -0,0 +1,39 @@
#!/bin/sh
# Kitty placeholder rows stay cell-aligned when a client becomes narrower.
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 -Limage-placeholder-outer$$ -f/dev/null"
TMUX2="$TEST_TMUX -u -Limage-placeholder-inner$$ -f/dev/null"
TMP=$(mktemp)
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
$TMUX2 new-session -d -x 10 -y 4 "
i=0
while [ \$i -lt 10 ]; do
printf '\\364\\216\\273\\256\\314\\205'
i=\$((i + 1))
done
sleep 30" || exit 1
$TMUX2 set -g status off || exit 1
$TMUX new-session -d -x 10 -y 4 "$TMUX2 attach-session" || exit 1
$TMUX set -g status off || exit 1
sleep 1
$TMUX resize-window -x 5 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -n "$(sed -n 1p $TMP)" ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
$TMUX resize-window -x 10 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
exit 0

View File

@@ -0,0 +1,38 @@
#!/bin/sh
# Widening a pane restores clipped columns for every image placement.
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Limage-resize-outer$$ -f/dev/null"
TMUX2="env -u TMUX LC_ALL=C $TEST_TMUX -Limage-resize-inner$$ -f/dev/null"
TMP=$(mktemp)
trap "$TMUX kill-server 2>/dev/null; $TMUX2 kill-server 2>/dev/null; rm -f $TMP" 0 1 15
# White placements stay behind the final red placement.
$TMUX2 new-session -d -x 8 -y 4 "
i=0
while [ \$i -lt 64 ]; do
printf '\\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=16,r=1;/////w==\\033\\\\'
i=\$((i + 1))
done
printf '\\033_Ga=T,q=2,C=1,f=32,s=1,v=1,c=16,r=1;/wAA/w==\\033\\\\'
sleep 30" || exit 1
[ "$($TMUX2 display-message -p '#{image_support}')" = 0 ] && exit 0
$TMUX2 set -g status off || exit 1
$TMUX new-session -d -x 8 -y 4 "$TMUX2 attach-session" || exit 1
$TMUX set -g status off || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(cat $TMP)" = '........' ] || exit 1
$TMUX resize-window -x 16 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E0 >$TMP || exit 1
[ "$(cat $TMP)" = '................' ] || exit 1
exit 0

View File

@@ -1,29 +1,8 @@
#!/bin/sh
# Regression test: an image wider than the pane at the time it was
# displayed must show more of itself once the pane grows wide enough,
# instead of staying clipped to its original width forever.
#
# image_write() (image.c) clips an image's width to whatever fit in the
# pane when it was first displayed, and never revisits that decision -
# unlike height, which recovers naturally through ordinary scrollback,
# there is no "scroll right", so the clipped columns were permanently
# discarded. window_pane_resize() (window.c) now calls
# image_grid_resize_width() after a pane grows wider, which extends each
# existing placement's spans - using the image's own retained, immutable
# pixel data - up to whichever is smaller: the image's full width or the
# new pane width. See tmux-image-redraw-known-bugs.md for the full
# write-up.
#
# This is checked via the SIXEL raster widths reported in the client's raw
# output before and after widening the window. The redraw triggered by the
# resize is damage-based (only the newly-uncovered columns are dirtied), so
# it does not redraw the whole row as one wider raster - it sends the
# already-correct clipped portion's width again untouched, plus a *second*,
# separate raster covering just the newly-added columns. So rather than
# looking for a single wider raster, this checks that the widths seen
# across both redraws, added together, account for the fixture's full
# pixel width - i.e. the previously-clipped remainder actually appeared.
# Widening a pane must recover image columns clipped at placement time.
# A damage redraw can send the newly visible columns as a separate raster,
# so compare the combined raster widths with the original image width.
PATH=/bin:/usr/bin
TERM=screen

View File

@@ -1,29 +1,6 @@
#!/bin/sh
# Regression test: an image taller than the pane, at the moment it is first
# displayed, must remain visible when scrolling back up through history -
# not show blank space for the part that was scrolled off screen before the
# user ever got to see it.
#
# image_write() (image.c) handles this case by scrolling the screen up
# (via screen_write_scrollup()) to make room, then only ever wrote spans
# for the rows that ended up on screen afterwards - the rows that were
# immediately scrolled off (origin_y of them) were pushed into history as
# blank, spanless rows and their image data was discarded. Unlike width,
# which has no "scroll right" to recover a permanent clip, height already
# has ordinary scrollback, so this was pure waste. image_write() now also
# calls image_extend_row() for those origin_y history rows (capped to
# gd->hsize, since screen_write_scrollup() may not have pushed a real
# history row for every one of them - see tmux-uint-subtraction-underflow-
# care.md), so scrolling back up recovers the image instead of showing
# empty space. See tmux-image-redraw-known-bugs.md for the full write-up.
#
# This is checked by comparing how many separate SIXEL rasters the client
# receives right after entering copy mode (a baseline - copy mode itself
# triggers a redraw of the still-visible rows) against how many it
# receives after scrolling to the very top of history: with the fix, more
# rasters appear (covering the newly-revealed, previously-scrolled-off
# rows); without it, scrolling reveals nothing new.
# Images scrolled off-screen must reappear when copy mode scrolls back up.
PATH=/bin:/usr/bin
TERM=screen

View File

@@ -1,22 +1,6 @@
#!/bin/sh
# Regression test: extending a copy-mode selection by cursor movement, with
# the view otherwise unmoved (no scrolling), must not retransmit an image
# whose row the cursor passes through.
#
# window_copy_write_one() (window-copy.c) used to write text/highlight
# styling directly over image-covered cells, which - since a character
# write typically clears whatever pixel content a terminal was showing
# there - erased the image with nothing to redraw it back in. Separately,
# window_copy_write_line()'s call to image_redraw_area() used to fire
# unconditionally on every redraw, so even after fixing the erasure, the
# image would still be needlessly recomposited (and briefly flash) on
# every single cursor step even though nothing about it had changed. See
# tmux-image-redraw-known-bugs.md for the full write-up.
#
# This is checked by counting DCS (\033P) sequences in the client's raw
# output during the cursor movement: with the fix, extending a selection
# without scrolling never touches the image, so none should appear.
# Moving a copy-mode selection cursor must not retransmit unchanged images.
. ./image-noflash-common.inc

View File

@@ -1,23 +1,7 @@
#!/bin/sh
# Regression test: interactively sizing a new floating pane by dragging
# (new-pane -M, bound by default to C-MouseDrag1Pane/C-MouseDrag1Empty)
# must not retransmit images in other panes of the same window on ongoing
# drag steps.
#
# cmd_split_window_mouse_resize() (cmd-split-window.c) used to call
# server_redraw_window() unconditionally on every motion event while
# sizing the new floating pane, wiping and retransmitting every image in
# the window on each step even though only the new pane's own rectangle
# actually changed. See tmux-image-redraw-known-bugs.md for the full
# write-up.
#
# Creating the floating pane itself causes one legitimate, unrelated
# redraw (pane creation always redraws the session - see
# server_redraw_session() in cmd-split-window.c), so this checks DCS
# (\033P) counts only for the *ongoing* drag-motion steps after that
# initial creation, once c->tty.mouse_drag_update is already bound to the
# resize callback: with the fix, none should appear there.
# Sizing a new floating pane must not retransmit images in other panes.
# Wait for the initial creation redraw before checking subsequent drag steps.
. ./image-noflash-common.inc

View File

@@ -4,10 +4,12 @@
PATH=/bin:/usr/bin
TERM=screen
LC_ALL=C
export TERM LC_ALL
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Limage$$ -f/dev/null"
TMUX2="$TEST_TMUX -Limage-client$$ -f/dev/null"
TMUX2="env -u TMUX LC_ALL=C $TEST_TMUX -Limage-client$$ -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX2 kill-server 2>/dev/null
@@ -24,6 +26,7 @@ $TMUX new-session -d -x 20 -y 8 "
sleep 1
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
[ "$($TMUX display-message -p '#{sixel_support}')" = 1 ] || exit 1
# Images scroll as grid cells, while capture output contains ordinary spaces.
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
@@ -187,27 +190,9 @@ $TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 2p $TMP)" = "@@@@@" ] || exit 1
[ -z "$(sed -n 3p $TMP)" ] || exit 1
# Kitty virtual placements use U+10EEEE placeholder cells. Keep their rows at
# fixed coordinates when narrowing the terminal, clipping instead of reflowing
# the second half onto the following row.
# Restore the wider client for the remaining placement checks.
$TMUX resize-window -x 10 -y 4 || exit 1
sleep 1
$TMUX2 new-window -d "
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
printf '\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205\\364\\216\\273\\256\\314\\205'
sleep 10" || exit 1
$TMUX2 select-window -t:4 || exit 1
sleep 1
$TMUX resize-window -x 5 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ -n "$(sed -n 1p $TMP)" ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
$TMUX resize-window -x 10 -y 4 || exit 1
sleep 1
$TMUX capture-pane -pS0 -E3 >$TMP || exit 1
[ "$(sed -n 1p $TMP | wc -c)" = 61 ] || exit 1
[ -z "$(sed -n 2p $TMP)" ] || exit 1
# A placement ID supplied with transmit-and-place is reused by a later place.
# This is the sequence used by chawan: the newline moves the cursor down before