Fix wrapped output selections and recognize single-character vi selections.

This commit is contained in:
Michael Grant
2026-10-06 14:38:17 +02:00
parent a0f7b38345
commit 11ccb77472
3 changed files with 102 additions and 24 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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)