From 1eedc4aa24ef332f8bb1c6a8086fc094db13a256 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 29 Sep 2026 10:03:12 +0100 Subject: [PATCH] Fix OSC 133 output range lookup for markers sharing a line Handle C and D on the same line, output ending on the next prompt's line, and output starting on a prompt line. Keep the output columns when A resets a line's prompt data, and fall back to the previous output if the range is empty. Extend the regress test to cover these and reset the paste buffer before each check. --- input.c | 2 +- regress/output-commands.sh | 47 +++++++++++++++++++++++- window-copy.c | 75 ++++++++++++++++++++++++++++---------- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/input.c b/input.c index 2732b616e..df53c947d 100644 --- a/input.c +++ b/input.c @@ -3278,8 +3278,8 @@ input_osc_133(struct input_ctx *ictx, const char *p) case 'A': case 'N': if (gl != NULL) { - memset(&gl->osc133_data, 0, sizeof gl->osc133_data); gl->osc133_data.prompt_col = s->cx; + gl->osc133_data.cmd_col = 0; gl->flags |= GRID_LINE_START_PROMPT; } if (wp != NULL) { diff --git a/regress/output-commands.sh b/regress/output-commands.sh index 87833427f..d686d0607 100755 --- a/regress/output-commands.sh +++ b/regress/output-commands.sh @@ -20,6 +20,7 @@ $TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B sleep 1 $TMUX copy-mode || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -X copy-output || exit 1 [ "$($TMUX show-buffer)" = unfinished ] || exit 1 @@ -33,6 +34,7 @@ sleep 1 [ "$($TMUX show-buffer)" = unfinished ] || exit 1 $TMUX send-keys -X select-output || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -X copy-selection || exit 1 [ "$($TMUX show-buffer)" = unfinished ] || exit 1 $TMUX send-keys -X cancel || exit 1 @@ -46,12 +48,14 @@ $TMUX send-keys -X cancel || exit 1 $TMUX copy-mode || exit 1 $TMUX send-keys -X search-backward separator || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -X copy-output || exit 1 [ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1 $TMUX send-keys -X cancel || exit 1 $TMUX copy-mode || exit 1 $TMUX send-keys -X search-backward one || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -X copy-output || exit 1 [ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1 $TMUX send-keys -X cancel || exit 1 @@ -59,6 +63,7 @@ $TMUX send-keys -X cancel || exit 1 $TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1 sleep 1 $TMUX copy-mode -t :plain || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :plain.0 -X copy-output -a || exit 1 all=$($TMUX show-buffer) case "$all" in @@ -69,13 +74,15 @@ $TMUX send-keys -t :plain.0 -X cancel || exit 1 $TMUX set-buffer -b keep unchanged || exit 1 $TMUX copy-mode -t :plain || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :plain.0 -X copy-output || exit 1 [ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1 $TMUX send-keys -t :plain.0 -X cancel || exit 1 -$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1 +$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\n\\033]133;C\\007\\033]133;D;0\\007'; exec sleep 100" || exit 1 sleep 1 $TMUX copy-mode -t :empty || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :empty.0 -X copy-output || exit 1 [ "$($TMUX show-buffer)" = one ] || exit 1 $TMUX send-keys -t :empty.0 -X cancel || exit 1 @@ -83,6 +90,7 @@ $TMUX send-keys -t :empty.0 -X cancel || exit 1 $TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 sleep 1 $TMUX copy-mode -t :prompt || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :prompt.0 -X copy-output || exit 1 [ "$($TMUX show-buffer)" = one ] || exit 1 $TMUX send-keys -t :prompt.0 -X cancel || exit 1 @@ -91,6 +99,7 @@ $TMUX copy-mode -t :prompt || exit 1 $TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1 $TMUX send-keys -t :prompt.0 C-o || exit 1 [ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :prompt.0 -X copy-selection || exit 1 [ "$($TMUX show-buffer)" = one ] || exit 1 $TMUX send-keys -t :prompt.0 -X cancel || exit 1 @@ -100,6 +109,7 @@ sleep 1 $TMUX copy-mode -t :same || exit 1 $TMUX send-keys -t :same.0 -X search-backward one || exit 1 $TMUX send-keys -t :same.0 -X select-output || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :same.0 -X copy-selection || exit 1 [ "$($TMUX show-buffer)" = one ] || exit 1 $TMUX send-keys -t :same.0 -X cancel || exit 1 @@ -108,8 +118,41 @@ $TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n sleep 1 $TMUX copy-mode -t :clear || exit 1 $TMUX send-keys -t :clear.0 C-o || exit 1 +$TMUX set-buffer sentinel || exit 1 $TMUX send-keys -t :clear.0 -X copy-selection || exit 1 -[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\n1 pts/0')" ] || exit 1 +[ "$($TMUX show-buffer)" = "$(printf '1\nPID TTY\n1 pts/0')" ] || exit 1 $TMUX send-keys -t :clear.0 -X cancel || exit 1 +$TMUX new-window -d -n oneline "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\nseparator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :oneline || exit 1 +$TMUX send-keys -t :oneline.0 -X search-backward echo || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :oneline.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = one ] || exit 1 +$TMUX send-keys -t :oneline.0 -X cancel || exit 1 + +$TMUX new-window -d -n nextprompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :nextprompt || exit 1 +$TMUX send-keys -t :nextprompt.0 -X search-backward echo || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :nextprompt.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1 +$TMUX send-keys -t :nextprompt.0 -X cancel || exit 1 + +$TMUX new-window -d -n hist "printf '\\033]133;A\\007p\\$ \\033]133;B\\007seq\\n\\033]133;C\\007'; seq 1 40; sleep 3; seq 100 103; printf '\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX clear-history -t :hist.0 || exit 1 +sleep 4 +$TMUX copy-mode -t :hist || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :hist.0 -X copy-output || exit 1 +$TMUX show-buffer >$OUT +[ "$(head -n1 "$OUT")" != sentinel ] || exit 1 +[ "$(head -n1 "$OUT")" != 1 ] || exit 1 +[ "$(tail -n1 "$OUT")" = 103 ] || exit 1 +grep -q seq "$OUT" && exit 1 +$TMUX send-keys -t :hist.0 -X cancel || exit 1 + exit 0 diff --git a/window-copy.c b/window-copy.c index c0243450b..9388211e1 100644 --- a/window-copy.c +++ b/window-copy.c @@ -6242,6 +6242,9 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, u_int cursor_x, cursor_y, prompt_x = 0; u_int prompt_y = UINT_MAX, y, total; int found_start = 0, found_end = 0; + int next_prompt, in_range, found; + void *buf; + size_t len; cursor_x = data->cx; cursor_y = screen_hsize(data->backing) + data->cy - data->oy; @@ -6266,27 +6269,38 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, for (y = prompt_y; y < total; y++) { gl = grid_get_line(gd, y); - /* An output may end on the next prompt's line. */ + next_prompt = (y != prompt_y && + gl->flags & GRID_LINE_START_PROMPT); + /* Output before the next prompt on its line is ours. */ + if (y == prompt_y) + in_range = (gl->osc133_data.out_start_col >= prompt_x); + else if (next_prompt) + in_range = (gl->osc133_data.out_start_col < + gl->osc133_data.prompt_col); + else + in_range = 1; + if (gl->flags & GRID_LINE_START_OUTPUT && in_range) { + *sx = gl->osc133_data.out_start_col; + *sy = y; + 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 || gl->osc133_data.out_end_col >= prompt_x)) { + (y != prompt_y || gl->osc133_data.out_end_col >= prompt_x) && + (y != *sy || gl->osc133_data.out_end_col >= *sx)) { *ex = gl->osc133_data.out_end_col; *ey = y; found_end = 1; break; } - if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT) + if (next_prompt) break; - if (gl->flags & GRID_LINE_START_OUTPUT && - (y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) { - *sx = gl->osc133_data.out_start_col; - *sy = y; - found_start = 1; - } } if (!found_start) { log_debug("%s: no output after prompt", __func__); - return (window_copy_find_previous_output_range(wme, cursor_x, - cursor_y, sx, sy, ex, ey)); + found = window_copy_find_previous_output_range(wme, cursor_x, + cursor_y, sx, sy, ex, ey); + return (found); } if (!found_end) { if (y != total) { @@ -6295,6 +6309,14 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, } window_copy_output_end(data->backing, ex, ey); } + buf = window_copy_get_grid_range(wme, *sx, *sy, *ex, *ey, &len); + if (buf == NULL) { + log_debug("%s: empty output", __func__); + found = window_copy_find_previous_output_range(wme, cursor_x, + cursor_y, sx, sy, ex, ey); + return (found); + } + free(buf); log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy, *ex, *ey); return (1); @@ -6309,19 +6331,28 @@ window_copy_find_previous_output_range(struct window_mode_entry *wme, struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; struct grid_line *gl; + struct osc133_data *od; void *buf; u_int start_x, start_y, end_x, end_y; u_int y, total; size_t len; int found = 0, have_prompt = 0; int pending = 0; + int has_start, has_end, end_first; total = gd->hsize + gd->sy; for (y = 0; y < total && y <= cursor_y; y++) { gl = grid_get_line(gd, y); - if (gl->flags & GRID_LINE_START_OUTPUT && - (y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) { - start_x = gl->osc133_data.out_start_col; + od = &gl->osc133_data; + has_start = (gl->flags & GRID_LINE_START_OUTPUT && + (y != cursor_y || od->out_start_col <= cursor_x)); + has_end = (gl->flags & GRID_LINE_END_OUTPUT && + (y != cursor_y || od->out_end_col <= cursor_x)); + /* An end before the start ends the previous output. */ + end_first = (has_start && has_end && + od->out_end_col < od->out_start_col); + if (has_start && !end_first) { + start_x = od->out_start_col; start_y = y; pending = 1; } @@ -6329,13 +6360,12 @@ window_copy_find_previous_output_range(struct window_mode_entry *wme, if (!pending && !have_prompt && gl->flags & GRID_LINE_END_OUTPUT && (~gl->flags & GRID_LINE_START_PROMPT || - gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) { + od->out_end_col <= od->prompt_col)) { start_x = start_y = 0; pending = 1; } - if (pending && gl->flags & GRID_LINE_END_OUTPUT && - (y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) { - end_x = gl->osc133_data.out_end_col; + if (pending && has_end) { + end_x = od->out_end_col; end_y = y; buf = window_copy_get_grid_range(wme, start_x, start_y, end_x, end_y, &len); @@ -6349,8 +6379,15 @@ window_copy_find_previous_output_range(struct window_mode_entry *wme, } pending = 0; } + if (has_start && end_first) { + start_x = od->out_start_col; + start_y = y; + pending = 1; + } if (gl->flags & GRID_LINE_START_PROMPT) { - pending = 0; + /* Output may start after the prompt. */ + if (!has_start || od->out_start_col < od->prompt_col) + pending = 0; have_prompt = 1; } }