Fix folded output selection and teardown after resize

Map joined source rows in a shared column space so select-output
works after widening. Resize the saved source when disabling fold
view and preserve the cursor across reflow.

Extend resize regressions to cover widening and fold view teardown.
This commit is contained in:
Michael Grant
2026-10-14 15:57:40 +02:00
parent 86f857d1f3
commit 2e37e61ff1
2 changed files with 123 additions and 16 deletions

View File

@@ -34,8 +34,12 @@ for keys in emacs vi; do
selection_seq="${selection_seq}\033]133;C\007${payload}" selection_seq="${selection_seq}\033]133;C\007${payload}"
selection_seq="${selection_seq}\033]133;D;7\007\r\n" selection_seq="${selection_seq}\033]133;D;7\007\r\n"
selection_seq="${selection_seq}\033]133;A\007p> \033]133;B\007" selection_seq="${selection_seq}\033]133;A\007p> \033]133;B\007"
for width in 5 20 100; do for sizes in '80 5' '80 20' '80 100' '20 5' '20 80' '20 100'; do
start_pane_history selection 80 12 "$selection_seq" set -- $sizes
initial_width=$1
width=$2
start_pane_history selection "$initial_width" 12 \
"$selection_seq"
$TMUX set -w -t selection: mode-keys "$keys" || exit 1 $TMUX set -w -t selection: mode-keys "$keys" || exit 1
$TMUX copy-mode -t selection: -U || exit 1 $TMUX copy-mode -t selection: -U || exit 1
$TMUX resize-window -t selection: -x "$width" || exit 1 $TMUX resize-window -t selection: -x "$width" || exit 1
@@ -46,11 +50,55 @@ for keys in emacs vi; do
$TMUX save-buffer "$TMP" || exit 1 $TMUX save-buffer "$TMP" || exit 1
printf '%s' "$payload" >"$EXP" printf '%s' "$payload" >"$EXP"
cmp "$TMP" "$EXP" || \ cmp "$TMP" "$EXP" || \
fail "$keys $length cells at width $width" fail "$keys $length cells, $initial_width to $width"
done done
done done
done done
# Turning folding off must use the same wrapping as ordinary copy mode.
for sizes in '20 80' '80 5'; do
set -- $sizes
start_pane_history toggle "$1" 24 "$selection_seq"
$TMUX copy-mode -t toggle: -U || exit 1
$TMUX resize-window -t toggle: -x "$2" || exit 1
$TMUX send -t toggle: -X fold-view-toggle || exit 1
$TMUX send -t toggle: -X history-top || exit 1
$TMUX capture-pane -t toggle: -Mp >"$TMP" || exit 1
$TMUX send -t toggle: -X cancel || exit 1
$TMUX copy-mode -t toggle: || exit 1
$TMUX send -t toggle: -X history-top || exit 1
$TMUX capture-pane -t toggle: -Mp >"$EXP" || exit 1
cmp "$TMP" "$EXP" || fail "fold view off from $1 to $2"
done
# Cursor mapping must work in both directions across joined source rows.
payload="$(printf '%030d' 0 | tr 0 F)Z$(printf '%029d' 0 | tr 0 F)"
seq="\033]133;A\007p> \033]133;B\007first\r\n"
seq="${seq}\033]133;C\007${payload}\033]133;D;7\007\r\n"
seq="${seq}\033]133;A\007p> \033]133;B\007"
start_pane_history cursor 20 24 "$seq"
for width in 80 10 20 80; do
$TMUX copy-mode -t cursor: -U || exit 1
$TMUX resize-window -t cursor: -x "$width" || exit 1
$TMUX send -t cursor: -X search-backward-text Z || exit 1
$TMUX set-buffer sentinel || exit 1
$TMUX send -t cursor: -X copy-output || exit 1
$TMUX save-buffer "$TMP" || exit 1
printf '%s' "$payload" >"$EXP"
cmp "$TMP" "$EXP" || fail "cursor output at width $width"
before=$($TMUX display -p -t cursor: \
'#{copy_cursor_x}:#{copy_cursor_line}')
$TMUX send -t cursor: -X fold-view-toggle || exit 1
after=$($TMUX display -p -t cursor: \
'#{copy_cursor_x}:#{copy_cursor_line}')
if [ "$before" != "$after" ]; then
printf '%s\n' "$before" >"$EXP"
printf '%s\n' "$after" >"$TMP"
fail "cursor after turning folding off at width $width"
fi
$TMUX send -t cursor: -X cancel || exit 1
done
# Refresh must keep a mixture of collapsed and expanded commands. # Refresh must keep a mixture of collapsed and expanded commands.
for refresh in refresh-now refresh-on; do for refresh in refresh-now refresh-on; do
start_cmd refresh 80 12 \ start_cmd refresh 80 12 \

View File

@@ -317,6 +317,8 @@ struct window_copy_fold_request {
struct window_copy_fold_ctx { struct window_copy_fold_ctx {
u_int source_y; /* current row in the unfolded source */ u_int source_y; /* current row in the unfolded source */
u_int source_x; /* current column in the unfolded source */ u_int source_x; /* current column in the unfolded source */
u_int wrapped_y; /* first source row of this wrapped line */
u_int wrapped_x; /* cells in preceding rows of this line */
u_int prompt_dy; /* prompt row in the backing, or UINT_MAX */ u_int prompt_dy; /* prompt row in the backing, or UINT_MAX */
u_int output_line; /* fold flag's source row, or UINT_MAX */ u_int output_line; /* fold flag's source row, or UINT_MAX */
int collapsed; /* current fold is collapsed */ int collapsed; /* current fold is collapsed */
@@ -331,7 +333,8 @@ struct window_copy_fold_ctx {
/* Source mapping and gutter metadata for one backing row. */ /* Source mapping and gutter metadata for one backing row. */
struct window_copy_line { struct window_copy_line {
u_int source_line; /* source row; UINT_MAX if unmapped */ u_int source_line; /* source row; UINT_MAX if unmapped */
u_int source_col; /* source column at mapped boundary */ u_int wrapped_line; /* wrap start row, or UINT_MAX */
u_int source_col; /* column across wrapped source rows */
u_int backing_col; /* backing column at same boundary */ u_int backing_col; /* backing column at same boundary */
u_int output_line; /* source fold flag row, or UINT_MAX */ u_int output_line; /* source fold flag row, or UINT_MAX */
u_char flags; /* WINDOW_COPY_LINE_* bits */ u_char flags; /* WINDOW_COPY_LINE_* bits */
@@ -566,6 +569,7 @@ window_copy_grow_line_info(struct window_copy_mode_data *data, u_int count)
(new_count - old_count) * sizeof *data->lines); (new_count - old_count) * sizeof *data->lines);
for (y = old_count; y < new_count; y++) { for (y = old_count; y < new_count; y++) {
data->lines[y].source_line = UINT_MAX; data->lines[y].source_line = UINT_MAX;
data->lines[y].wrapped_line = UINT_MAX;
data->lines[y].output_line = UINT_MAX; data->lines[y].output_line = UINT_MAX;
} }
data->line_count = new_count; data->line_count = new_count;
@@ -606,8 +610,11 @@ window_copy_map_line(struct window_copy_mode_data *data,
line = window_copy_get_line_info(data, y); line = window_copy_get_line_info(data, y);
if (line == NULL) if (line == NULL)
return; return;
/* Use one column space for all rows of a wrapped source line. */
line->source_line = fold->source_y; line->source_line = fold->source_y;
line->source_col = fold->source_x; line->wrapped_line = fold->wrapped_y;
line->source_col = fold->wrapped_x + fold->source_x;
line->backing_col = dst->cx; line->backing_col = dst->cx;
line->output_line = fold->output_line; line->output_line = fold->output_line;
} }
@@ -983,6 +990,10 @@ window_copy_destroy_fold_view(struct window_mode_entry *wme)
free(data->backing); free(data->backing);
data->backing = data->source; data->backing = data->source;
data->source = NULL; data->source = NULL;
/* The saved source may still be wrapped to an earlier pane size. */
screen_resize_cursor(data->backing, screen_size_x(&data->screen),
screen_size_y(&data->screen), 1, 0, 0);
free(data->source_flags); free(data->source_flags);
data->source_flags = NULL; data->source_flags = NULL;
data->source_line_count = 0; data->source_line_count = 0;
@@ -1118,6 +1129,14 @@ window_copy_rebuild_backing(struct window_mode_entry *wme)
fold.hiding = data->source_flags[0] & fold.hiding = data->source_flags[0] &
WINDOW_COPY_FOLD_COLLAPSED; WINDOW_COPY_FOLD_COLLAPSED;
} }
/* Keep joined source rows in one mapping coordinate space. */
if (sgl->flags & GRID_LINE_WRAPPED)
fold.wrapped_x += sgl->cellused;
else {
fold.wrapped_y = y + 1;
fold.wrapped_x = 0;
}
} }
screen_write_stop(&ctx); screen_write_stop(&ctx);
if (data->backing != NULL) { if (data->backing != NULL) {
@@ -4387,16 +4406,21 @@ window_copy_cmd_fold_view_toggle(struct window_copy_cmd_state *cs)
{ {
struct window_mode_entry *wme = cs->wme; struct window_mode_entry *wme = cs->wme;
struct window_copy_mode_data *data = wme->data; struct window_copy_mode_data *data = wme->data;
u_int source_x, source_y; u_int source_x, source_y, wx, wy;
if (data->viewmode) if (data->viewmode)
return (WINDOW_COPY_CMD_NOTHING); return (WINDOW_COPY_CMD_NOTHING);
window_copy_get_cursor_source(wme, &source_x, &source_y); window_copy_get_cursor_source(wme, &source_x, &source_y);
window_copy_clear_selection(wme); window_copy_clear_selection(wme);
window_copy_clear_marks(wme); window_copy_clear_marks(wme);
if (data->fold_view) if (data->fold_view) {
/* Reflowing the source also changes the cursor coordinates. */
grid_wrap_position(data->source->grid, source_x, source_y,
&wx, &wy);
window_copy_destroy_fold_view(wme); window_copy_destroy_fold_view(wme);
else grid_unwrap_position(data->backing->grid, &source_x, &source_y,
wx, wy);
} else
window_copy_create_fold_view(wme); window_copy_create_fold_view(wme);
if (window_copy_restore_cursor(wme, source_x, source_y)) if (window_copy_restore_cursor(wme, source_x, source_y))
return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_REDRAW);
@@ -4451,6 +4475,40 @@ window_copy_find_fold(struct window_mode_entry *wme, u_int target,
return (1); return (1);
} }
/* Express a source position relative to its first wrapped row. */
static void
window_copy_wrap_source_position(struct window_copy_mode_data *data,
u_int *x, u_int *y)
{
struct grid *gd = data->source->grid;
struct grid_line *gl;
while (*y > 0) {
gl = grid_get_line(gd, *y - 1);
if (~gl->flags & GRID_LINE_WRAPPED)
break;
*x += gl->cellused;
(*y)--;
}
}
/* Restore a source position to the row containing its column. */
static void
window_copy_unwrap_source_position(struct window_copy_mode_data *data,
u_int *x, u_int *y)
{
struct grid *gd = data->source->grid;
struct grid_line *gl;
while (*y + 1 < gd->hsize + gd->sy) {
gl = grid_get_line(gd, *y);
if (~gl->flags & GRID_LINE_WRAPPED || *x < gl->cellused)
break;
*x -= gl->cellused;
(*y)++;
}
}
/* Map the cursor to its position in the unfolded source. */ /* Map the cursor to its position in the unfolded source. */
static void static void
window_copy_get_cursor_source(struct window_mode_entry *wme, u_int *source_x, window_copy_get_cursor_source(struct window_mode_entry *wme, u_int *source_x,
@@ -4472,16 +4530,14 @@ window_copy_get_cursor_source(struct window_mode_entry *wme, u_int *source_x,
*source_y = data->source_line_count - 1; *source_y = data->source_line_count - 1;
return; return;
} }
*source_y = line->source_line; *source_y = line->wrapped_line;
if (data->cx >= line->backing_col) { if (data->cx >= line->backing_col) {
*source_x = line->source_col + data->cx - line->backing_col; *source_x = line->source_col + data->cx - line->backing_col;
return; } else if (line->backing_col - data->cx <= line->source_col) {
}
if (line->backing_col - data->cx <= line->source_col) {
*source_x = line->source_col - (line->backing_col - data->cx); *source_x = line->source_col - (line->backing_col - data->cx);
return; } else
} *source_x = 0;
*source_x = 0; window_copy_unwrap_source_position(data, source_x, source_y);
} }
/* Find the backing position corresponding to a source position. */ /* Find the backing position corresponding to a source position. */
@@ -4499,10 +4555,13 @@ window_copy_find_backing_position(struct window_mode_entry *wme, u_int source_x,
*backing_y = source_y; *backing_y = source_y;
return (1); return (1);
} }
/* Joined source rows share a row and column space in the mappings. */
window_copy_wrap_source_position(data, &source_x, &source_y);
total = screen_hsize(data->backing) + screen_size_y(data->backing); total = screen_hsize(data->backing) + screen_size_y(data->backing);
for (y = 0; y < total; y++) { for (y = 0; y < total; y++) {
line = window_copy_get_line_info(data, y); line = window_copy_get_line_info(data, y);
if (line == NULL || line->source_line != source_y) if (line == NULL || line->wrapped_line != source_y)
continue; continue;
if (source_x >= line->source_col) { if (source_x >= line->source_col) {
x = line->backing_col + source_x - line->source_col; x = line->backing_col + source_x - line->source_col;