From 11ccb774725940d1ba6720e52fae6b9171d1c3d1 Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Tue, 6 Oct 2026 14:38:17 +0200 Subject: [PATCH] Fix wrapped output selections and recognize single-character vi selections. --- regress/output-editor.sh | 45 +++++++++++++++++++++ regress/output-osc133-reflow.sh | 12 ++++++ window-copy.c | 69 +++++++++++++++++++++------------ 3 files changed, 102 insertions(+), 24 deletions(-) diff --git a/regress/output-editor.sh b/regress/output-editor.sh index 224516c58..a1fe108b0 100644 --- a/regress/output-editor.sh +++ b/regress/output-editor.sh @@ -72,4 +72,49 @@ wait_editor selection [ "$($TMUX display-message -p -t edit:0 '#{selection_present}')" = 1 ] || exit 1 $TMUX send-keys -t edit:0 -X cancel || exit 1 + +# Both active and stopped single-character selections use the editor binding. +printf 'o' >"$DIR/expected" || exit 1 +for keys in emacs vi; do + $TMUX set-option -w -t edit:0 mode-keys "$keys" || exit 1 + case "$keys" in + emacs) binding=e ;; + vi) binding=M-e ;; + esac + for state in active stopped; do + name="$keys-$state" + $TMUX set-option -g editor "sh $DIR/editor.sh $name" || exit 1 + $TMUX copy-mode -t edit:0 || exit 1 + $TMUX send-keys -t edit:0 -X search-backward one || exit 1 + $TMUX send-keys -t edit:0 -X begin-selection || exit 1 + if [ "$keys" = emacs ]; then + $TMUX send-keys -t edit:0 -X cursor-right || exit 1 + fi + if [ "$state" = stopped ]; then + $TMUX send-keys -t edit:0 -X stop-selection || exit 1 + fi + [ "$($TMUX display-message -p -t edit:0 \ + '#{selection_present}')" = 1 ] || exit 1 + $TMUX send-keys -t edit:0 "$binding" || exit 1 + wait_editor "$name" + cmp "$DIR/expected" "$DIR/$name" || exit 1 + [ "$($TMUX show-buffer)" = sentinel ] || exit 1 + $TMUX send-keys -t edit:0 -X cancel || exit 1 + done +done + +# An empty emacs selection still opens the output. +printf 'one\ntwo\n' >"$DIR/expected" || exit 1 +$TMUX set-option -w -t edit:0 mode-keys emacs || exit 1 +$TMUX set-option -g editor "sh $DIR/editor.sh empty" || exit 1 +$TMUX copy-mode -t edit:0 || exit 1 +$TMUX send-keys -t edit:0 -X search-backward one || exit 1 +$TMUX send-keys -t edit:0 -X begin-selection || exit 1 +[ "$($TMUX display-message -p -t edit:0 '#{selection_present}')" = 0 ] || + exit 1 +$TMUX send-keys -t edit:0 e || exit 1 +wait_editor empty +cmp "$DIR/expected" "$DIR/empty" || exit 1 +$TMUX send-keys -t edit:0 -X cancel || exit 1 + exit 0 diff --git a/regress/output-osc133-reflow.sh b/regress/output-osc133-reflow.sh index bbfc2f6b4..3357662c5 100644 --- a/regress/output-osc133-reflow.sh +++ b/regress/output-osc133-reflow.sh @@ -157,4 +157,16 @@ expected=$(printf 'one\n_') check_output newline "${expected%_}" check_selection newline "${expected%_}" +# Wrapped cells keep spaces, even when D splits a wrapped line. +for payload in 'one' 'one ' 'one ' ' ' 'long-output '; do + seq="${prompt}\033]133;C\007${payload}\033]133;D;0\007" + start_pane_history wrapped 8 20 "${seq} separator" + check_output wrapped "$payload" + check_selection wrapped "$payload" + $TMUX resize-window -t wrapped: -x 80 || exit 1 + $TMUX resize-window -t wrapped: -x 8 || exit 1 + check_output wrapped "$payload" + check_selection wrapped "$payload" +done + exit $exit_status diff --git a/window-copy.c b/window-copy.c index cac2fdfea..65c4f4985 100644 --- a/window-copy.c +++ b/window-copy.c @@ -141,6 +141,8 @@ static void window_copy_copy_selection(struct window_mode_entry *, const char *, int, int); static void window_copy_append_selection(struct window_mode_entry *); static void window_copy_clear_selection(struct window_mode_entry *); +static u_int window_copy_copy_line_length(struct window_mode_entry *, u_int, + int *); static void window_copy_copy_line(struct window_mode_entry *, char **, size_t *, u_int, u_int, u_int); static int window_copy_in_set(struct window_mode_entry *, u_int, u_int, @@ -1123,6 +1125,7 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) u_int position, limit; struct grid_line *gl; time_t t; + int keys; gl = grid_get_line(data->backing->grid, hsize - data->oy); t = grid_line_time(gl); @@ -1156,7 +1159,9 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft) format_add(ft, "selection_active", "1"); else format_add(ft, "selection_active", "0"); - if (data->endselx != data->selx || data->endsely != data->sely) + keys = options_get_number(wme->wp->window->options, "mode-keys"); + if (keys == MODEKEY_VI || data->endselx != data->selx || + data->endsely != data->sely) format_add(ft, "selection_present", "1"); else format_add(ft, "selection_present", "0"); @@ -1610,6 +1615,7 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) size_t len; u_int sx, sy, ex, ey, total, last; int all = args_has(cs->wargs, 'a'); + int wrapped; if (all) { sx = sy = 0; @@ -1631,12 +1637,19 @@ 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); + last = window_copy_copy_line_length(wme, ey, NULL); 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); + if (ex == 0 && ey > 0) { + ey--; + ex = window_copy_copy_line_length(wme, ey, &wrapped); + if (wrapped && ex > 0) + ex--; + } else { + grid_reader_start(&gr, data->backing->grid, ex, ey); + grid_reader_cursor_left(&gr, 1); + grid_reader_get_cursor(&gr, &ex, &ey); + } } window_copy_scroll_to(wme, ex, ey, 1); return (WINDOW_COPY_CMD_REDRAW); @@ -6467,7 +6480,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) size_t off; u_int i, xx, yy, sx, sy, ex, ey, ey_last; u_int firstsx, lastex, restex, restsx, selx; - int keys; + int keys, wrapped; if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) { buf = window_copy_match_at_cursor(data); @@ -6500,7 +6513,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) } /* Trim ex to end of line. */ - ey_last = window_copy_find_length(wme, ey); + ey_last = window_copy_copy_line_length(wme, ey, &wrapped); if (ex > ey_last) ex = ey_last; @@ -6571,8 +6584,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) } /* Remove final \n (unless at end in vi mode). */ if (keys == MODEKEY_EMACS || lastex <= ey_last) { - if (~grid_get_line(data->backing->grid, ey)->flags & - GRID_LINE_WRAPPED || lastex != ey_last) + if (!wrapped || lastex != ey_last) off -= 1; } *len = off; @@ -6709,6 +6721,27 @@ window_copy_append_selection(struct window_mode_entry *wme) free(bufname); } +/* Get the length for copying, preserving spaces on wrapped lines. */ +static u_int +window_copy_copy_line_length(struct window_mode_entry *wme, u_int sy, + int *wrapped) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + struct grid_line *gl = grid_get_line(gd, sy); + u_int length; + int line_wrapped; + + line_wrapped = (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx); + if (wrapped != NULL) + *wrapped = line_wrapped; + if (line_wrapped) + length = gl->cellsize; + else + length = window_copy_find_length(wme, sy); + return (length); +} + static void window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, u_int sy, u_int sx, u_int ex) @@ -6716,27 +6749,15 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, struct window_copy_mode_data *data = wme->data; struct grid *gd = data->backing->grid; struct grid_cell gc; - struct grid_line *gl; struct utf8_data ud; - u_int i, xx, wrapped = 0; + u_int i, xx; const char *s; + int wrapped; if (sx > ex) return; - /* - * Work out if the line was wrapped at the screen edge and all of it is - * on screen. - */ - gl = grid_get_line(gd, sy); - if (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx) - wrapped = 1; - - /* If the line was wrapped, don't strip spaces (use the full length). */ - if (wrapped) - xx = gl->cellsize; - else - xx = window_copy_find_length(wme, sy); + xx = window_copy_copy_line_length(wme, sy, &wrapped); if (ex > xx) ex = xx; if (sx > xx)