From ca80906eeef02149fd51cd56d4d8f869387948ae Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 18 Jul 2026 16:55:51 +0100 Subject: [PATCH] Implemented copy-mode-collapse, replacing collapse-output. --- options-table.c | 13 ++++------- options.c | 5 ++++ regress/collapse-output.sh | 18 ++++++++++++--- tmux.1 | 30 +++++++++++++++++------- window-copy.c | 47 ++++++++++++++++++++++++++++++-------- 5 files changed, 85 insertions(+), 28 deletions(-) diff --git a/options-table.c b/options-table.c index e22b5db35..2202ad221 100644 --- a/options-table.c +++ b/options-table.c @@ -114,9 +114,6 @@ static const char *options_table_theme_list[] = { static const char *options_table_copy_mode_line_numbers_list[] = { "off", "default", "absolute", "relative", "hybrid", NULL }; -static const char *options_table_collapse_output_list[] = { - "output-only", "output-and-prompt", NULL -}; static const char *options_table_copy_mode_collapse_controls_list[] = { "always", "never", "on-demand", NULL }; @@ -1369,12 +1366,12 @@ const struct options_table_entry options_table[] = { .text = "Time for which 'display-panes' should show pane numbers." }, - { .name = "collapse-output", - .type = OPTIONS_TABLE_CHOICE, + { .name = "copy-mode-collapse", + .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .choices = options_table_collapse_output_list, - .default_num = 0, - .text = "What is hidden when command output is collapsed in copy mode." + .default_str = "C", + .text = "Sections hidden when OSC 133 command output is collapsed in " + "copy mode." }, { .name = "copy-mode-collapse-controls", diff --git a/options.c b/options.c index b5238bc69..fefd6781f 100644 --- a/options.c +++ b/options.c @@ -1170,6 +1170,11 @@ options_from_string_check(const struct options_table_entry *oe, xasprintf(cause, "not a suitable shell: %s", value); return (-1); } + if (strcmp(oe->name, "copy-mode-collapse") == 0 && + (*value == '\0' || strspn(value, "ABCD") != strlen(value))) { + xasprintf(cause, "value is invalid: %s", value); + return (-1); + } if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { xasprintf(cause, "value is invalid: %s", value); return (-1); diff --git a/regress/collapse-output.sh b/regress/collapse-output.sh index 5041af81e..fb4e767e4 100644 --- a/regress/collapse-output.sh +++ b/regress/collapse-output.sh @@ -7,13 +7,16 @@ TERM=screen TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" $TMUX kill-server 2>/dev/null -$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo hi\\033]133;C\\007\\nhello\\n\\033]133;D;0\\007\"; exec sleep 100'" || exit 1 +$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo hi\\033]133;C\\007\\nhello\\n\\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 -c || exit 1 $TMUX send-keys -X search-backward hello || exit 1 hidden=$($TMUX display-message -p '#{copy_cursor_line}') [ "$hidden" != hello ] || exit 1 +$TMUX send-keys -X search-backward separator || exit 1 +shown=$($TMUX display-message -p '#{copy_cursor_line}') +[ "$shown" = separator ] || exit 1 $TMUX send-keys C-Tab || exit 1 $TMUX send-keys -X search-backward hello || exit 1 @@ -25,10 +28,19 @@ selected=$($TMUX show-buffer) [ "$selected" = hello ] || exit 1 $TMUX send-keys -X cancel || exit 1 -$TMUX set-window-option collapse-output output-and-prompt || exit 1 +$TMUX set-window-option copy-mode-collapse CD || exit 1 $TMUX copy-mode -c || exit 1 -$TMUX send-keys -X search-backward 'p$' || exit 1 +$TMUX send-keys -X search-backward separator || exit 1 +hidden=$($TMUX display-message -p '#{copy_cursor_line}') +[ "$hidden" != separator ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX set-window-option copy-mode-collapse CDA || exit 1 +$TMUX copy-mode -c || exit 1 +$TMUX send-keys -X search-backward 'p$ echo hi' || exit 1 hidden=$($TMUX display-message -p '#{copy_cursor_line}') [ "$hidden" != 'p$ echo hi' ] || exit 1 +$TMUX set-window-option copy-mode-collapse E >/dev/null 2>&1 && exit 1 + $TMUX kill-server diff --git a/tmux.1 b/tmux.1 index df126b7bb..5fb290f41 100644 --- a/tmux.1 +++ b/tmux.1 @@ -5838,16 +5838,30 @@ and .Ic hybrid , they use absolute line numbers. .Pp -.It Xo Ic collapse\-output -.Op Ic output\-only | output\-and\-prompt +.It Xo Ic copy\-mode\-collapse +.Op Ar sections .Xc -Choose what is hidden when OSC 133 command output is collapsed in copy mode. -.Ic output\-only -hides only the output between the C and D markers; -.Ic output\-and\-prompt -also hides the prompt between the A and B markers. +Choose which OSC 133 sections are hidden when command output is collapsed in +copy mode. +.Ar sections +is a string containing one or more of the following letters: +.Bl -column "Letter" "Hidden section" -offset indent +.It Sy Letter Ta Sy Hidden section +.It Li A Ta prompt (A to B) +.It Li B Ta entered command (B to C) +.It Li C Ta command output (C to D) +.It Li D Ta text after command output (D to the next A) +.El +.Pp +For example, +.Ic C +hides only command output, +.Ic CD +hides command output and text before the next prompt, and +.Ic CDA +hides command output, text before the next prompt, and the prompt itself. The default is -.Ic output\-only . +.Ic C . .Pp .It Xo Ic copy\-mode\-collapse\-controls .Op Ic always | never | on\-demand diff --git a/window-copy.c b/window-copy.c index e34970daa..e07119a13 100644 --- a/window-copy.c +++ b/window-copy.c @@ -32,6 +32,11 @@ struct window_copy_mode_data; #define WINDOW_COPY_CONTROLS_NEVER 1 #define WINDOW_COPY_CONTROLS_ON_DEMAND 2 +#define WINDOW_COPY_COLLAPSE_PROMPT 0x1 +#define WINDOW_COPY_COLLAPSE_COMMAND 0x2 +#define WINDOW_COPY_COLLAPSE_OUTPUT 0x4 +#define WINDOW_COPY_COLLAPSE_SEPARATOR 0x8 + static const char *window_copy_key_table(struct window_mode_entry *); static void window_copy_command(struct window_mode_entry *, struct client *, struct session *, struct winlink *, struct args *, @@ -470,6 +475,25 @@ window_copy_any_output_collapsed(struct screen *s) return (0); } +static u_int +window_copy_collapse_mask(struct window_mode_entry *wme) +{ + const char *value; + u_int mask = 0; + + value = options_get_string(wme->wp->window->options, + "copy-mode-collapse"); + if (strchr(value, 'A') != NULL) + mask |= WINDOW_COPY_COLLAPSE_PROMPT; + if (strchr(value, 'B') != NULL) + mask |= WINDOW_COPY_COLLAPSE_COMMAND; + if (strchr(value, 'C') != NULL) + mask |= WINDOW_COPY_COLLAPSE_OUTPUT; + if (strchr(value, 'D') != NULL) + mask |= WINDOW_COPY_COLLAPSE_SEPARATOR; + return (mask); +} + /* Is the output following this prompt collapsed? */ static int window_copy_prompt_collapsed(struct screen *s, u_int prompt) @@ -515,8 +539,8 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) struct grid_line *sgl, *dgl, *prompt = NULL; struct screen_write_ctx ctx; struct grid_cell gc; - u_int y, x, total, dy, output_line = UINT_MAX; - int hiding = 0, hide_prompt = 0; + u_int collapse, y, x, total, dy, output_line = UINT_MAX; + int collapsed = 0, hiding = 0; if (src == NULL) return; @@ -532,6 +556,7 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) } sgd = src->grid; total = sgd->hsize + sgd->sy; + collapse = window_copy_collapse_mask(wme); dst = xcalloc(1, sizeof *dst); screen_init(dst, screen_size_x(&data->screen), screen_size_y(&data->screen), @@ -542,9 +567,9 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) for (y = 0; y < total; y++) { sgl = grid_get_line(sgd, y); if (sgl->flags & GRID_LINE_START_PROMPT) { - hide_prompt = (options_get_number(wme->wp->window->options, - "collapse-output") != 0 && - window_copy_prompt_collapsed(src, y)); + collapsed = window_copy_prompt_collapsed(src, y); + hiding = collapsed && + (collapse & WINDOW_COPY_COLLAPSE_PROMPT); prompt = NULL; } for (x = 0; x <= sgl->cellused; x++) { @@ -560,7 +585,8 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) dgl = grid_get_line(dst->grid, dy); dgl->flags |= GRID_LINE_START_COMMAND; dgl->command = dst->cx; - hide_prompt = 0; + hiding = collapsed && + (collapse & WINDOW_COPY_COLLAPSE_COMMAND); } if ((sgl->flags & GRID_LINE_START_OUTPUT) && x == sgl->output) { dy = dst->grid->hsize + dst->cy; @@ -568,7 +594,9 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) dgl->flags |= GRID_LINE_START_OUTPUT; dgl->output = dst->cx; output_line = y; - hiding = (sgl->flags & GRID_LINE_OUTPUT_COLLAPSED) != 0; + collapsed = (sgl->flags & GRID_LINE_OUTPUT_COLLAPSED) != 0; + hiding = collapsed && + (collapse & WINDOW_COPY_COLLAPSE_OUTPUT); if (prompt != NULL) { prompt->flags |= GRID_LINE_OUTPUT_CONTROL; prompt->output_line = output_line; @@ -581,12 +609,13 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) dgl = grid_get_line(dst->grid, dy); dgl->flags |= GRID_LINE_END_OUTPUT; dgl->end_output = dst->cx; - hiding = 0; + hiding = collapsed && + (collapse & WINDOW_COPY_COLLAPSE_SEPARATOR); output_line = UINT_MAX; } if (x == sgl->cellused) break; - if (hiding || hide_prompt) + if (hiding) continue; dy = dst->grid->hsize + dst->cy; dgl = grid_get_line(dst->grid, dy);