From a0f7b38345b4ef5639c0a3fee7f7bb6f828a538d Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 6 Oct 2026 14:21:59 +0200 Subject: [PATCH] Fix OSC 133 output ranges after history loss and trim vi selection endpoints. --- regress/output-history-loss.sh | 118 +++++++++++++++++++++----------- regress/output-osc133-reflow.sh | 15 ++++ window-copy.c | 12 +++- 3 files changed, 103 insertions(+), 42 deletions(-) diff --git a/regress/output-history-loss.sh b/regress/output-history-loss.sh index 6bbd2404f..bd33c4224 100644 --- a/regress/output-history-loss.sh +++ b/regress/output-history-loss.sh @@ -2,6 +2,43 @@ . ./input-common.inc +check_output() +{ + name=$1 + search=$2 + + for position in inside after; do + for keys in emacs vi; do + $TMUX set-option -w -t "$name:" mode-keys "$keys" || + exit 1 + $TMUX copy-mode -t "$name:" || exit 1 + if [ "$position" = inside ]; then + $TMUX send-keys -t "$name:" -X search-backward \ + "$search" || exit 1 + fi + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t "$name:" -X copy-output || exit 1 + $TMUX save-buffer "$TMP" || exit 1 + cmp "$TMP" "$EXP" || + fail "$name $position $keys output" + + $TMUX send-keys -t "$name:" -X select-output || exit 1 + present=$($TMUX display-message -p -t "$name:" \ + '#{selection_present}') + if [ "$present" != 1 ]; then + echo "FAIL: $name $position $keys no selection" + exit_status=1 + fi + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t "$name:" -X copy-selection || exit 1 + $TMUX save-buffer "$TMP" || exit 1 + cmp "$TMP" "$EXP" || + fail "$name $position $keys selection" + $TMUX send-keys -t "$name:" -X cancel || exit 1 + done + done +} + # A multiline command evicts its prompt before producing output. command='\033]133;A\007p>\033]133;B\007echo\n' i=0 @@ -30,50 +67,49 @@ for state in complete unfinished next-prompt; do check_raw_matches lost 'END_OUTPUT.*osc133=0,0,0,3,0' fi - for position in inside after; do - for keys in emacs vi; do - $TMUX set-option -w -t lost: mode-keys "$keys" || - exit 1 - $TMUX copy-mode -t lost: || exit 1 - if [ "$position" = inside ]; then - $TMUX send-keys -t lost: -X search-backward one || - exit 1 - fi - $TMUX set-buffer sentinel || exit 1 - $TMUX send-keys -t lost: -X copy-output || exit 1 - $TMUX save-buffer "$TMP" || exit 1 - printf '%s' "$OUTPUT" >"$EXP" - cmp "$TMP" "$EXP" || - fail "$state $position $keys output" + printf '%s' "$OUTPUT" >"$EXP" + check_output lost one +done - $TMUX send-keys -t lost: -X select-output || exit 1 - present=$($TMUX display-message -p -t lost: \ - '#{selection_present}') - if [ "$present" != 1 ]; then - echo "FAIL: $state $position $keys no selection" - exit_status=1 - fi - $TMUX set-buffer sentinel || exit 1 - $TMUX send-keys -t lost: -X copy-selection || exit 1 - $TMUX save-buffer "$TMP" || exit 1 - cmp "$TMP" "$EXP" || - fail "$state $position $keys selection" - $TMUX send-keys -t lost: -X cancel || exit 1 - done - done +# A long output evicts both A and C but leaves D. +output='\033]133;A\007p>\033]133;B\007echo\n\033]133;C\007' +i=0 +while [ "$i" -lt 30 ]; do + output="${output}$(printf 'row%02d' "$i")\n" + i=$((i + 1)) +done +for state in complete next-prompt same-line-prompt; do + seq="${output}\033]133;D;0\007separator" + if [ "$state" != complete ]; then + if [ "$state" = next-prompt ]; then + seq="${seq}\n" + fi + seq="${seq}\033]133;A\007p>\033]133;B\007" + fi + start_pane_hlimit clipped 40 6 "$seq" 3 + check_raw_no_matches clipped 'START_OUTPUT' + if [ "$state" = complete ]; then + check_raw_no_matches clipped 'START_PROMPT' + fi + check_raw_matches clipped 'END_OUTPUT' + $TMUX capture-pane -p -t clipped: -S - | + sed '/^separator/,$d' >"$EXP" + check_output clipped row29 done # Retained command text does not belong to a later prompt's output. -seq="${command}\033]133;A\007p>\033]133;B\007echo\n" -seq="${seq}\033]133;C\007later\033]133;D;0\007" -start_pane_hlimit later 40 5 "$seq" 3 -$TMUX copy-mode -t later: || exit 1 -$TMUX send-keys -t later: -X history-top || exit 1 -$TMUX set-buffer sentinel || exit 1 -$TMUX send-keys -t later: -X copy-output || exit 1 -[ "$($TMUX show-buffer)" = sentinel ] || exit 1 -$TMUX send-keys -t later: -X select-output || exit 1 -[ "$($TMUX display-message -p -t later: '#{selection_present}')" = 0 ] || - exit 1 +for newline in '' '\n'; do + seq="${command}\033]133;A\007p>\033]133;B\007echo${newline}" + seq="${seq}\033]133;C\007later\033]133;D;0\007" + start_pane_hlimit later 40 5 "$seq" 3 + $TMUX copy-mode -t later: || exit 1 + $TMUX send-keys -t later: -X history-top || exit 1 + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t later: -X copy-output || exit 1 + [ "$($TMUX show-buffer)" = sentinel ] || exit 1 + $TMUX send-keys -t later: -X select-output || exit 1 + [ "$($TMUX display-message -p -t later: '#{selection_present}')" = 0 ] || + exit 1 +done exit $exit_status diff --git a/regress/output-osc133-reflow.sh b/regress/output-osc133-reflow.sh index 20bd211c8..bbfc2f6b4 100644 --- a/regress/output-osc133-reflow.sh +++ b/regress/output-osc133-reflow.sh @@ -142,4 +142,19 @@ check_output below "$expected" check_selection below "$expected" check_pipe below "$expected" +# Selection trims trailing cells without adding a newline in vi mode. +for suffix in ' \033]133;D;0\007' '\033[5C\033]133;D;0\007' ' '; do + start_pane_history trailing 80 20 \ + "${prompt}\033]133;C\007one${suffix}" + check_output trailing one + check_selection trailing one +done + +# A newline before D is part of the output, including after spaces. +start_pane_history newline 80 20 \ + "${prompt}\033]133;C\007one \n\033]133;D;0\007" +expected=$(printf 'one\n_') +check_output newline "${expected%_}" +check_selection newline "${expected%_}" + exit $exit_status diff --git a/window-copy.c b/window-copy.c index f40557c6a..cac2fdfea 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1608,7 +1608,7 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) struct grid_reader gr; void *buf; size_t len; - u_int sx, sy, ex, ey, total; + u_int sx, sy, ex, ey, total, last; int all = args_has(cs->wargs, 'a'); if (all) { @@ -1631,6 +1631,9 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) window_copy_start_selection(wme); if (options_get_number(wme->wp->window->options, "mode-keys") == MODEKEY_VI) { + last = window_copy_find_length(wme, ey); + if (ex > last) + ex = last; grid_reader_start(&gr, data->backing->grid, ex, ey); grid_reader_cursor_left(&gr, 1); grid_reader_get_cursor(&gr, &ex, &ey); @@ -6276,6 +6279,13 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, *sy = y; found_start = 1; } + /* Both A and C may have left history while D remains. */ + if (!found_start && prompt_y == UINT_MAX && + gl->flags & GRID_LINE_END_OUTPUT && + (!next_prompt || od->out_end_col <= od->prompt_col)) { + *sx = *sy = 0; + found_start = 1; + } /* An output may end on the same line or the next prompt's. */ if (found_start && gl->flags & GRID_LINE_END_OUTPUT && (y != prompt_y || od->out_end_col >= prompt_x) &&