Implemented copy-mode-collapse, replacing collapse-output.

This commit is contained in:
Michael Grant
2026-07-18 16:55:51 +01:00
parent 2743f14eba
commit ca80906eee
5 changed files with 85 additions and 28 deletions

View File

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

View File

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

View File

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

30
tmux.1
View File

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

View File

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