Add collapsible lines.

This commit is contained in:
Michael Grant
2026-07-20 16:26:02 +01:00
parent 7abb9af062
commit d080378464
7 changed files with 851 additions and 41 deletions

View File

@@ -30,8 +30,8 @@ const struct cmd_entry cmd_copy_mode_entry = {
.name = "copy-mode",
.alias = NULL,
.args = { "dekHMqSs:t:u", 0, 0, NULL },
.usage = "[-dekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.args = { "UcdekHMqSs:t:u", 0, 0, NULL },
.usage = "[-UcdekHMqSu] [-s src-pane] " CMD_TARGET_PANE_USAGE,
.source = { 's', CMD_FIND_PANE, 0 },
.target = { 't', CMD_FIND_PANE, 0 },

28
input.c
View File

@@ -3279,9 +3279,14 @@ input_osc_133(struct input_ctx *ictx, const char *p)
case 'A':
case 'N':
if (gl != NULL) {
memset(&gl->osc133_data, 0, sizeof gl->osc133_data);
gl->osc133_data.prompt_col = s->cx;
gl->flags |= GRID_LINE_START_PROMPT;
if (!(gl->flags & (GRID_LINE_START_PROMPT|
GRID_LINE_SECOND_PROMPT))) {
gl->osc133_data.prompt_col = s->cx;
if (strstr(p + 1, ";k=s") != NULL)
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
}
}
if (wp != NULL) {
wp->last_prompt_time = time(NULL);
@@ -3291,22 +3296,25 @@ input_osc_133(struct input_ctx *ictx, const char *p)
case 'P':
if (gl != NULL) {
cp = strstr(p, ";k=s");
if (cp != NULL && (cp[4] == ';' || cp[4] == '\0'))
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
gl->osc133_data.prompt_col = s->cx;
if (!(gl->flags & (GRID_LINE_START_PROMPT|
GRID_LINE_SECOND_PROMPT))) {
gl->osc133_data.prompt_col = s->cx;
if (cp != NULL && (cp[4] == ';' || cp[4] == '\0'))
gl->flags |= GRID_LINE_SECOND_PROMPT;
else
gl->flags |= GRID_LINE_START_PROMPT;
}
}
break;
case 'B':
case 'I':
if (gl != NULL) {
if (gl != NULL && !(gl->flags & GRID_LINE_START_COMMAND)) {
gl->flags |= GRID_LINE_START_COMMAND;
gl->osc133_data.cmd_col = s->cx;
}
break;
case 'C':
if (gl != NULL) {
if (gl != NULL && !(gl->flags & GRID_LINE_START_OUTPUT)) {
gl->flags |= GRID_LINE_START_OUTPUT;
gl->osc133_data.out_start_col = s->cx;
}

View File

@@ -577,6 +577,8 @@ key_bindings_init(void)
"bind -Tcopy-mode Escape { send -X cancel }",
"bind -Tcopy-mode C-[ { send -X cancel }",
"bind -Tcopy-mode Space { send -X page-down }",
"bind -Tcopy-mode Tab { send -X toggle-output }",
"bind -Tcopy-mode BTab { send -X toggle-output -a }",
"bind -Tcopy-mode , { send -X jump-reverse }",
"bind -Tcopy-mode \\; { send -X jump-again }",
"bind -Tcopy-mode F { command-prompt -P -1p'(jump backward)' { send -X jump-backward -- '%%' } }",
@@ -594,7 +596,7 @@ key_bindings_init(void)
"bind -Tcopy-mode t { command-prompt -P -1p'(jump to forward)' { send -X jump-to-forward -- '%%' } }",
"bind -Tcopy-mode Home { send -X start-of-line }",
"bind -Tcopy-mode End { send -X end-of-line }",
"bind -Tcopy-mode MouseDown1Pane select-pane",
"bind -Tcopy-mode MouseDown1Pane { select-pane; send -X toggle-output -m }",
"bind -Tcopy-mode MouseDrag1Pane { select-pane; send -X begin-selection }",
"bind -Tcopy-mode MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
"bind -Tcopy-mode WheelUpPane { select-pane; send -N5 -X scroll-up }",
@@ -654,6 +656,8 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi Escape { send -X clear-selection }",
"bind -Tcopy-mode-vi C-[ { send -X clear-selection }",
"bind -Tcopy-mode-vi Space { send -X begin-selection }",
"bind -Tcopy-mode-vi Tab { send -X toggle-output }",
"bind -Tcopy-mode-vi BTab { send -X toggle-output -a }",
"bind -Tcopy-mode-vi '$' { send -X end-of-line }",
"bind -Tcopy-mode-vi , { send -X jump-reverse }",
"bind -Tcopy-mode-vi / { command-prompt -P -T search -p'(search down)' { send -X search-forward -- '%%' } }",
@@ -709,7 +713,7 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi % { send -X next-matching-bracket }",
"bind -Tcopy-mode-vi Home { send -X start-of-line }",
"bind -Tcopy-mode-vi End { send -X end-of-line }",
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane }",
"bind -Tcopy-mode-vi MouseDown1Pane { select-pane; send -X toggle-output -m }",
"bind -Tcopy-mode-vi MouseDrag1Pane { select-pane; send -X begin-selection }",
"bind -Tcopy-mode-vi MouseDragEnd1Pane { send -X copy-pipe-and-cancel }",
"bind -Tcopy-mode-vi WheelUpPane { select-pane; send -N5 -X scroll-up }",

View File

@@ -114,6 +114,9 @@ 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_copy_mode_collapse_controls_list[] = {
"always", "never", "on-demand", NULL
};
/* Status line format. */
#define OPTIONS_TABLE_STATUS_FORMAT1 \
@@ -1373,6 +1376,14 @@ const struct options_table_entry options_table[] = {
.text = "Style of search matches in copy mode."
},
{ .name = "copy-mode-collapse-controls",
.type = OPTIONS_TABLE_CHOICE,
.scope = OPTIONS_TABLE_WINDOW,
.choices = options_table_copy_mode_collapse_controls_list,
.default_num = 2,
.text = "When OSC 133 output collapse controls are shown in copy mode."
},
{ .name = "copy-mode-current-match-style",
.type = OPTIONS_TABLE_STRING,
.scope = OPTIONS_TABLE_WINDOW,

47
tmux.1
View File

@@ -2167,6 +2167,27 @@ Hide line numbers in copy mode.
.Xc
Toggle line numbers in copy mode.
.It Xo
.Ic collapse\-output
.Op Fl a
.Xc
Collapse the output for the command on the cursor line.
With
.Fl a ,
collapse all command output in the copy-mode buffer.
Text before the first OSC 133 prompt is treated as an initial command output
region.
.It Xo
.Ic expand\-output
.Op Fl a
.Xc
Expand the output for the command on the cursor line.
With
.Fl a ,
expand all command output in the copy-mode buffer.
.Pp
The Tab key toggles output for the command on the cursor line and S-Tab
toggles all command output.
.It Xo
.Ic middle\-line
(vi: M)
(emacs: M\-r)
@@ -2595,12 +2616,19 @@ The synopsis for the
command is:
.Bl -tag -width Ds
.It Xo Ic copy\-mode
.Op Fl dekHMqSu
.Op Fl UcdekHMqSu
.Op Fl s Ar src\-pane
.Op Fl t Ar target\-pane
.Xc
Enter copy mode.
.Pp
.Fl c
enters copy mode with OSC 133 command output collapsed.
.Fl U
enters copy mode with OSC 133 output controls shown and all output expanded.
When output is collapsed, the prompt and entered command are shown and text
from the output start marker to the next primary prompt marker is hidden.
.Pp
.Fl u
enters copy mode and scrolls one page up and
.Fl d
@@ -5822,6 +5850,23 @@ and
.Ic hybrid ,
they use absolute line numbers.
.Pp
.It Xo Ic copy\-mode\-collapse\-controls
.Op Ic always | never | on\-demand
.Xc
Choose when OSC 133 output collapse controls are shown in copy mode.
.Ic always
shows the control column in every copy-mode view;
.Ic never
does not show it, although collapse key bindings and commands still work; and
.Ic on\-demand
shows it after entering copy mode with
.Fl U
or
.Fl c ,
or after output is changed with a copy-mode command or key binding.
The default is
.Ic on\-demand .
.Pp
.It Xo Ic mode\-keys
.Op Ic vi | emacs
.Xc

View File

@@ -34,6 +34,8 @@ static const struct tty_acs_entry tty_acs_table[] = {
{ '-', "\342\206\221" }, /* arrow pointing up */
{ '.', "\342\206\223" }, /* arrow pointing down */
{ '0', "\342\226\256" }, /* solid square block */
{ '>', "\342\206\222" }, /* arrow pointing right */
{ 'V', "\342\206\223" }, /* arrow pointing down */
{ '`', "\342\227\206" }, /* diamond */
{ 'a', "\342\226\222" }, /* checker board (stipple) */
{ 'b', "\342\220\211" },

View File

@@ -28,6 +28,14 @@
struct window_copy_mode_data;
#define WINDOW_COPY_CONTROLS_ALWAYS 0
#define WINDOW_COPY_CONTROLS_NEVER 1
#define WINDOW_COPY_CONTROLS_ON_DEMAND 2
#define WINDOW_COPY_LINE_CONTROL 0x1
#define WINDOW_COPY_LINE_MEMBER 0x2
#define WINDOW_COPY_LINE_INITIAL 0x4
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 *,
@@ -64,6 +72,8 @@ static int window_copy_line_number_is_absolute(struct window_mode_entry *);
static int window_copy_line_numbers_active(struct window_mode_entry *);
static int window_copy_cursor_line_active(struct window_mode_entry *);
static u_int window_copy_line_number_width(struct window_mode_entry *);
static u_int window_copy_left_margin(struct window_mode_entry *);
static int window_copy_controls_visible(struct window_mode_entry *);
static u_int window_copy_cursor_offset(struct window_mode_entry *, u_int,
u_int);
static u_int window_copy_cursor_unoffset(struct window_mode_entry *, u_int,
@@ -72,6 +82,16 @@ static void window_copy_write_line(struct window_mode_entry *,
struct screen_write_ctx *, u_int);
static void window_copy_write_lines(struct window_mode_entry *,
struct screen_write_ctx *, u_int, u_int);
static void window_copy_rebuild_backing(struct window_mode_entry *);
static int window_copy_set_output(struct window_mode_entry *, int, int,
u_int, int);
static int window_copy_find_output(struct window_mode_entry *, u_int,
u_int *, u_int *);
static int window_copy_restore_cursor(struct window_mode_entry *, u_int,
u_int);
static int window_copy_restore_command_end(struct window_mode_entry *,
u_int, u_int);
static int window_copy_restore_before(struct window_mode_entry *, u_int);
static char *window_copy_match_at_cursor(struct window_copy_mode_data *);
static void window_copy_scroll_to(struct window_mode_entry *, u_int, u_int,
int);
@@ -249,6 +269,12 @@ struct window_copy_cmd_state {
struct winlink *wl;
};
struct window_copy_line {
u_int source_line;
u_int output_line;
u_char flags;
};
/*
* Copy mode's visible screen (the "screen" field) is filled from one of two
* sources: the original contents of the pane (used when we actually enter via
@@ -267,6 +293,11 @@ struct window_copy_mode_data {
struct screen screen;
struct screen *backing;
struct screen *source;
u_char *outputs;
u_int output_count;
struct window_copy_line *lines;
u_int line_count;
int backing_written; /* backing display started */
struct input_ctx *ictx;
@@ -300,6 +331,9 @@ struct window_copy_mode_data {
int scroll_exit; /* exit on scroll to end? */
int hide_position; /* hide position marker */
int line_numbers; /* 0 off, 1 from option, 2 default */
int output_gutter;
int output_controls;
int top_output;
enum {
SEL_CHAR, /* select one char at a time */
@@ -447,6 +481,278 @@ window_copy_clone_screen(struct screen *src, struct screen *hint, u_int *cx,
return (dst);
}
static struct window_copy_line *
window_copy_get_line_info(struct window_copy_mode_data *data, u_int y)
{
if (data->lines == NULL || y >= data->line_count)
return (NULL);
return (&data->lines[y]);
}
static void
window_copy_set_line(struct window_copy_mode_data *data, u_int y,
u_int source_line, u_int output_line, u_char flags)
{
struct window_copy_line *line;
line = window_copy_get_line_info(data, y);
if (line == NULL)
return;
line->source_line = source_line;
line->output_line = output_line;
line->flags |= flags;
}
static void
window_copy_set_control(struct window_copy_mode_data *data, u_int y,
u_int output_line)
{
struct window_copy_line *line;
line = window_copy_get_line_info(data, y);
if (line == NULL)
return;
line->output_line = output_line;
line->flags |= WINDOW_COPY_LINE_CONTROL;
}
static int
window_copy_any_output_collapsed(struct window_copy_mode_data *data)
{
struct grid *gd = data->source->grid;
struct grid_line *gl;
u_int y;
int command = 0, output = 0;
for (y = 0; y < gd->hsize + gd->sy; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_PROMPT)
break;
}
if (y != 0 && y != gd->hsize + gd->sy && data->top_output)
return (1);
for (y = 0; y < gd->hsize + gd->sy; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_PROMPT) {
command = 0;
output = 0;
}
if (gl->flags & GRID_LINE_START_COMMAND)
command = 1;
if (gl->flags & GRID_LINE_START_OUTPUT) {
if (data->outputs[y])
return (1);
output = 1;
}
if (gl->flags & GRID_LINE_END_OUTPUT) {
if (!output && command && data->outputs[y])
return (1);
command = output = 0;
}
}
return (0);
}
static int
window_copy_set_all_output(struct window_copy_mode_data *data, int collapse)
{
struct grid *gd = data->source->grid;
struct grid_line *gl;
u_int y;
int changed = 0, command = 0, output = 0;
for (y = 0; y < gd->hsize + gd->sy; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_PROMPT)
break;
}
if (y != 0 && y != gd->hsize + gd->sy &&
data->top_output != collapse) {
data->top_output = collapse;
changed = 1;
}
for (y = 0; y < gd->hsize + gd->sy; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_PROMPT) {
command = 0;
output = 0;
}
if (gl->flags & GRID_LINE_START_COMMAND)
command = 1;
if (gl->flags & GRID_LINE_START_OUTPUT) {
if (data->outputs[y] != collapse) {
data->outputs[y] = collapse;
changed = 1;
}
output = 1;
}
if (gl->flags & GRID_LINE_END_OUTPUT) {
if (!output && command && data->outputs[y] != collapse) {
data->outputs[y] = collapse;
changed = 1;
}
command = output = 0;
}
}
return (changed);
}
static int
window_copy_controls_visible(struct window_mode_entry *wme)
{
struct window_copy_mode_data *data = wme->data;
int controls;
controls = options_get_number(wme->wp->window->options,
"copy-mode-collapse-controls");
return (controls == WINDOW_COPY_CONTROLS_ALWAYS ||
(controls == WINDOW_COPY_CONTROLS_ON_DEMAND &&
data->output_controls));
}
/* Recreate the backing screen with any collapsed OSC 133 output omitted. */
static void
window_copy_rebuild_backing(struct window_mode_entry *wme)
{
struct window_copy_mode_data *data = wme->data;
struct screen *src = data->source, *dst;
struct grid *sgd;
struct grid_line *sgl, *dgl;
struct screen_write_ctx ctx;
struct grid_cell gc;
struct osc133_data *osc133;
u_int y, x, total, dy, prompt_dy = UINT_MAX;
u_int output_line = UINT_MAX;
int collapsed = 0, command = 0, hiding = 0;
int initial = 0, was_hiding;
if (src == NULL)
return;
if (!data->output_controls) {
dst = window_copy_clone_screen(src, &data->screen, NULL, NULL, 0);
if (data->backing != NULL) {
screen_free(data->backing);
free(data->backing);
}
free(data->lines);
data->lines = NULL;
data->line_count = 0;
data->backing = dst;
data->output_gutter = 0;
return;
}
sgd = src->grid;
total = sgd->hsize + sgd->sy;
dst = xcalloc(1, sizeof *dst);
screen_init(dst, screen_size_x(&data->screen), screen_size_y(&data->screen),
screen_hlimit(src));
dst->grid->flags |= GRID_HISTORY;
free(data->lines);
data->lines = xcalloc(total, sizeof *data->lines);
data->line_count = total;
for (y = 0; y < total; y++)
data->lines[y].output_line = UINT_MAX;
for (y = 0; y < total; y++) {
sgl = grid_get_line(sgd, y);
if (sgl->flags & GRID_LINE_START_PROMPT)
break;
}
if (y != 0 && y != total)
initial = 1;
screen_write_start(&ctx, dst);
for (y = 0; y < total; y++) {
sgl = grid_get_line(sgd, y);
osc133 = &sgl->osc133_data;
for (x = 0; x <= sgl->cellused; x++) {
dy = dst->grid->hsize + dst->cy;
was_hiding = hiding;
if (!hiding)
window_copy_set_line(data, dy, y, output_line,
output_line == UINT_MAX ? 0 :
WINDOW_COPY_LINE_MEMBER);
dgl = grid_get_line(dst->grid, dy);
if (initial && y == 0 && x == 0)
window_copy_set_line(data, dy, y, UINT_MAX,
WINDOW_COPY_LINE_CONTROL | WINDOW_COPY_LINE_INITIAL);
if ((sgl->flags & GRID_LINE_START_PROMPT) &&
x == osc133->prompt_col) {
dgl->flags |= GRID_LINE_START_PROMPT;
dgl->osc133_data.prompt_col = dst->cx;
prompt_dy = dy;
output_line = UINT_MAX;
collapsed = command = hiding = 0;
}
if ((sgl->flags & GRID_LINE_SECOND_PROMPT) &&
x == osc133->prompt_col) {
dgl->flags |= GRID_LINE_SECOND_PROMPT;
dgl->osc133_data.prompt_col = dst->cx;
}
if ((sgl->flags & GRID_LINE_START_COMMAND) &&
x == osc133->cmd_col) {
dgl->flags |= GRID_LINE_START_COMMAND;
dgl->osc133_data.cmd_col = dst->cx;
command = 1;
}
if ((sgl->flags & GRID_LINE_START_OUTPUT) &&
x == osc133->out_start_col) {
dgl->flags |= GRID_LINE_START_OUTPUT;
dgl->osc133_data.out_start_col = dst->cx;
output_line = y;
collapsed = data->outputs[y];
hiding = collapsed;
if (prompt_dy != UINT_MAX)
window_copy_set_control(data, prompt_dy, output_line);
}
if ((sgl->flags & GRID_LINE_END_OUTPUT) &&
x == osc133->out_end_col) {
dgl->flags |= GRID_LINE_END_OUTPUT;
dgl->osc133_data.out_end_col = dst->cx;
dgl->osc133_data.exit_status = osc133->exit_status;
if (output_line == UINT_MAX && command) {
output_line = y;
collapsed = data->outputs[y];
if (prompt_dy != UINT_MAX)
window_copy_set_control(data, prompt_dy,
output_line);
}
if (initial && prompt_dy == UINT_MAX)
hiding = data->top_output;
else
hiding = collapsed;
command = 0;
}
if (was_hiding && !hiding)
window_copy_set_line(data, dy, y, output_line,
output_line == UINT_MAX ? 0 :
WINDOW_COPY_LINE_MEMBER);
if (x == sgl->cellused)
break;
if (hiding)
continue;
grid_get_cell(sgd, x, y, &gc);
if (!(gc.flags & GRID_FLAG_PADDING))
screen_write_cell(&ctx, &gc);
}
if (y + 1 != total && !(sgl->flags & GRID_LINE_WRAPPED) &&
(!hiding || (initial && y == 0))) {
screen_write_carriagereturn(&ctx);
screen_write_linefeed(&ctx, 0, 8);
}
if (initial && y == 0)
hiding = data->top_output;
}
screen_write_stop(&ctx);
if (data->backing != NULL) {
screen_free(data->backing);
free(data->backing);
}
data->backing = dst;
data->output_gutter = window_copy_controls_visible(wme);
}
/*
* Snapshot the source grid's monotonic scroll counters so the next incremental
* sync can tell how much history was added or collected since this point.
@@ -474,20 +780,20 @@ window_copy_sync_backing(struct window_mode_entry *wme)
struct window_copy_mode_data *data = wme->data;
struct window_pane *wp = wme->swp;
struct screen *src = &wp->base;
struct screen *dst = data->backing;
struct screen *dst = data->source;
struct grid *sg = src->grid;
struct grid *dg = dst->grid;
u_int sy = sg->sy;
u_int old_hsize = dg->hsize;
u_int new_hsize = sg->hsize;
u_int added, collected, kept;
u_int added, collected, kept, old_count, new_count;
/*
* Only a pane's own live grid is tracked incrementally. A different
* source pane (copy-mode -s) goes through clone_screen, which also
* trims trailing blank lines that this path does not.
*/
if (data->viewmode || wme->swp != wme->wp)
if (data->source == NULL || data->viewmode || wme->swp != wme->wp)
return (0);
/* Indices only line up at the same size and generation. */
@@ -509,6 +815,20 @@ window_copy_sync_backing(struct window_mode_entry *wme)
return (0);
kept = old_hsize - collected;
old_count = old_hsize + sy;
new_count = new_hsize + sy;
if (collected != 0) {
memmove(data->outputs, data->outputs + collected,
(old_count - collected) * sizeof *data->outputs);
}
data->outputs = xreallocarray(data->outputs, new_count,
sizeof *data->outputs);
if (new_count > old_count - collected) {
memset(data->outputs + old_count - collected, 0,
(new_count - (old_count - collected)) *
sizeof *data->outputs);
}
data->output_count = new_count;
if (added == 0 && collected == 0) {
/* History is unchanged; only the viewport can have mutated. */
@@ -553,6 +873,7 @@ window_copy_sync_backing(struct window_mode_entry *wme)
dst->cx = src->cx;
dst->cy = src->cy;
}
window_copy_rebuild_backing(wme);
return (1);
}
@@ -605,18 +926,35 @@ window_copy_init(struct window_mode_entry *wme,
struct window_copy_mode_data *data;
struct screen *base = &wp->base;
struct screen_write_ctx ctx;
u_int i, cx, cy;
u_int i, cx, cy, prompt, chosen;
int restored = 0;
data = window_copy_common_init(wme);
data->backing = window_copy_clone_screen(base, &data->screen, &cx, &cy,
data->source = window_copy_clone_screen(base, &data->screen, &cx, &cy,
wme->swp != wme->wp);
data->outputs = xcalloc(screen_hsize(data->source) +
screen_size_y(data->source), sizeof *data->outputs);
data->output_count = screen_hsize(data->source) +
screen_size_y(data->source);
data->output_controls = args_has(args, 'c') || args_has(args, 'U') ||
options_get_number(wp->window->options,
"copy-mode-collapse-controls") == WINDOW_COPY_CONTROLS_ALWAYS;
if (args_has(args, 'c'))
window_copy_set_all_output(data, 1);
window_copy_rebuild_backing(wme);
window_copy_sync_snapshot(data, base->grid);
data->cx = cx;
if (cy < screen_hsize(data->backing)) {
if (data->output_controls &&
window_copy_find_output(wme, cy, &prompt, &chosen)) {
restored = window_copy_restore_cursor(wme, cx, cy);
if (!restored)
restored = window_copy_restore_command_end(wme, prompt, chosen);
}
if (!restored && cy < screen_hsize(data->backing)) {
data->cy = 0;
data->oy = screen_hsize(data->backing) - cy;
} else {
} else if (!restored) {
data->cy = cy - screen_hsize(data->backing);
data->oy = 0;
}
@@ -686,8 +1024,14 @@ window_copy_free(struct window_mode_entry *wme)
if (data->ictx != NULL)
input_free(data->ictx);
if (data->source != NULL) {
screen_free(data->source);
free(data->source);
}
screen_free(data->backing);
free(data->backing);
free(data->outputs);
free(data->lines);
screen_free(&data->screen);
free(data);
@@ -3001,10 +3345,16 @@ window_copy_do_refresh(struct window_mode_entry *wme, int follow)
oy_from_top = screen_hsize(data->backing) - data->oy;
if (!window_copy_sync_backing(wme)) {
screen_free(data->backing);
free(data->backing);
data->backing = window_copy_clone_screen(&wp->base,
screen_free(data->source);
free(data->source);
data->source = window_copy_clone_screen(&wp->base,
&data->screen, NULL, NULL, wme->swp != wme->wp);
free(data->outputs);
data->outputs = xcalloc(screen_hsize(data->source) +
screen_size_y(data->source), sizeof *data->outputs);
data->output_count = screen_hsize(data->source) +
screen_size_y(data->source);
window_copy_rebuild_backing(wme);
}
if (follow) {
@@ -3193,6 +3543,328 @@ window_copy_cmd_line_numbers_toggle(struct window_copy_cmd_state *cs)
return (WINDOW_COPY_CMD_NOTHING);
}
static int
window_copy_find_output(struct window_mode_entry *wme, u_int target,
u_int *prompt, u_int *chosen)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->source->grid;
struct grid_line *gl;
u_int candidate = UINT_MAX, last_prompt = 0;
u_int output = UINT_MAX, y;
int command = 0, have_prompt = 0;
*prompt = 0;
*chosen = UINT_MAX;
for (y = 0; y < gd->hsize + gd->sy; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_PROMPT) {
if (have_prompt && target >= last_prompt && target < y &&
candidate != UINT_MAX) {
*prompt = last_prompt;
*chosen = candidate;
return (1);
}
last_prompt = y;
candidate = UINT_MAX;
output = UINT_MAX;
command = 0;
have_prompt = 1;
}
if (gl->flags & GRID_LINE_START_COMMAND)
command = 1;
if (gl->flags & GRID_LINE_START_OUTPUT) {
output = y;
candidate = y;
}
if (gl->flags & GRID_LINE_END_OUTPUT) {
if (output == UINT_MAX && command)
candidate = y;
output = UINT_MAX;
command = 0;
}
}
if (have_prompt && target >= last_prompt && candidate != UINT_MAX) {
*prompt = last_prompt;
*chosen = candidate;
return (1);
}
return (0);
}
static void
window_copy_cursor_source(struct window_mode_entry *wme, u_int *source_x,
u_int *source_y)
{
struct window_copy_mode_data *data = wme->data;
struct window_copy_line *line;
u_int y;
*source_x = data->cx;
y = screen_hsize(data->backing) + data->cy - data->oy;
line = window_copy_get_line_info(data, y);
if (line == NULL)
*source_y = y;
else
*source_y = line->source_line;
}
static int
window_copy_restore_cursor(struct window_mode_entry *wme, u_int source_x,
u_int source_y)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
struct window_copy_line *line;
u_int y;
for (y = 0; y < gd->hsize + gd->sy; y++) {
line = window_copy_get_line_info(data, y);
gl = grid_get_line(gd, y);
if (line == NULL || line->source_line != source_y ||
gl->cellused == 0)
continue;
data->cx = source_x;
if (data->cx >= gl->cellused)
data->cx = gl->cellused;
if (y < gd->hsize) {
data->cy = 0;
data->oy = gd->hsize - y;
} else {
data->cy = y - gd->hsize;
data->oy = 0;
}
return (1);
}
return (0);
}
static int
window_copy_restore_command_end(struct window_mode_entry *wme, u_int prompt,
u_int output)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
struct window_copy_line *line;
u_int command_start = UINT_MAX, marker = UINT_MAX;
u_int marker_x = 0, length, y;
int previous = 0;
for (y = 0; y < gd->hsize + gd->sy; y++) {
line = window_copy_get_line_info(data, y);
gl = grid_get_line(gd, y);
if (line == NULL)
continue;
if (line->source_line == prompt &&
line->flags & WINDOW_COPY_LINE_CONTROL)
command_start = y;
if (line->source_line != output)
continue;
if (gl->flags & GRID_LINE_START_OUTPUT) {
marker = y;
marker_x = gl->osc133_data.out_start_col;
break;
}
if (marker == UINT_MAX && gl->flags & GRID_LINE_END_OUTPUT) {
marker = y;
marker_x = gl->osc133_data.out_end_col;
}
}
if (marker == UINT_MAX)
return (0);
if (marker_x != 0) {
data->cx = marker_x;
y = marker;
} else {
for (y = marker; y != 0; y--) {
if (command_start != UINT_MAX && y - 1 < command_start)
break;
length = window_copy_find_length(wme, y - 1);
if (length != 0) {
data->cx = length;
y--;
previous = 1;
break;
}
}
if (!previous)
return (0);
}
if (y < gd->hsize) {
data->cy = 0;
data->oy = gd->hsize - y;
} else {
data->cy = y - gd->hsize;
data->oy = 0;
}
return (1);
}
static int
window_copy_restore_before(struct window_mode_entry *wme, u_int source_y)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
struct window_copy_line *line;
u_int length, y, last = UINT_MAX;
for (y = 0; y < gd->hsize + gd->sy; y++) {
line = window_copy_get_line_info(data, y);
gl = grid_get_line(gd, y);
if (line == NULL || line->source_line > source_y ||
gl->cellused == 0)
continue;
last = y;
}
if (last == UINT_MAX)
return (0);
length = window_copy_find_length(wme, last);
data->cx = length;
if (last < gd->hsize) {
data->cy = 0;
data->oy = gd->hsize - last;
} else {
data->cy = last - gd->hsize;
data->oy = 0;
}
return (1);
}
static int
window_copy_set_output(struct window_mode_entry *wme, int all, int collapse,
u_int target, int command_end)
{
struct window_copy_mode_data *data = wme->data;
u_int prompt = 0, source_x, source_y;
u_int chosen = UINT_MAX, first_prompt, y;
int changed = 0, enabled = 0, found, initial,
wanted;
if (data->source == NULL)
return (0);
window_copy_cursor_source(wme, &source_x, &source_y);
if (target == UINT_MAX)
target = source_y;
first_prompt = screen_hsize(data->source) + screen_size_y(data->source);
for (y = 0; y < first_prompt; y++) {
if (grid_get_line(data->source->grid, y)->flags &
GRID_LINE_START_PROMPT) {
first_prompt = y;
break;
}
}
initial = first_prompt != 0 &&
first_prompt != screen_hsize(data->source) +
screen_size_y(data->source) && target < first_prompt;
if (!data->output_controls) {
data->output_controls = 1;
window_copy_rebuild_backing(wme);
enabled = 1;
}
found = window_copy_find_output(wme, target, &prompt, &chosen);
if (all) {
wanted = collapse;
if (wanted == -1)
wanted = !window_copy_any_output_collapsed(data);
changed = window_copy_set_all_output(data, wanted);
} else if (initial) {
wanted = collapse;
if (wanted == -1)
wanted = !data->top_output;
if (data->top_output != wanted) {
data->top_output = wanted;
changed = 1;
}
} else if (found && chosen != UINT_MAX) {
wanted = collapse;
if (wanted == -1)
wanted = !data->outputs[chosen];
if (data->outputs[chosen] != wanted) {
data->outputs[chosen] = wanted;
changed = 1;
}
}
if (!changed) {
if (enabled) {
data->output_controls = 0;
window_copy_rebuild_backing(wme);
}
return (0);
}
window_copy_clear_selection(wme);
window_copy_clear_marks(wme);
window_copy_rebuild_backing(wme);
if (!command_end &&
window_copy_restore_cursor(wme, source_x, source_y))
return (1);
if (initial && window_copy_restore_before(wme, 0))
return (1);
if (found && window_copy_restore_command_end(wme, prompt, chosen))
return (1);
if (window_copy_restore_before(wme, source_y))
return (1);
data->cy = screen_size_y(&data->screen) - 1;
data->cx = 0;
data->oy = 0;
return (1);
}
static enum window_copy_cmd_action
window_copy_cmd_collapse_output(struct window_copy_cmd_state *cs)
{
if (window_copy_set_output(cs->wme, args_has(cs->wargs, 'a'), 1,
UINT_MAX, 0))
return (WINDOW_COPY_CMD_REDRAW);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_expand_output(struct window_copy_cmd_state *cs)
{
if (window_copy_set_output(cs->wme, args_has(cs->wargs, 'a'), 0,
UINT_MAX, 0))
return (WINDOW_COPY_CMD_REDRAW);
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_toggle_output(struct window_copy_cmd_state *cs)
{
struct window_copy_mode_data *data = cs->wme->data;
struct window_copy_line *line;
u_int current, current_prompt, target, target_prompt, chosen, x, y;
int command_end = 0, current_found, initial, target_found;
if (args_has(cs->wargs, 'm')) {
if (cs->m == NULL || cmd_mouse_at(cs->wme->wp, cs->m, &x, &y,
0) != 0 || x >= window_copy_left_margin(cs->wme))
return (WINDOW_COPY_CMD_NOTHING);
target = screen_hsize(data->backing) + y - data->oy;
line = window_copy_get_line_info(data, target);
if (line == NULL)
return (WINDOW_COPY_CMD_NOTHING);
initial = line->flags & WINDOW_COPY_LINE_INITIAL;
window_copy_cursor_source(cs->wme, &x, &current);
current_found = window_copy_find_output(cs->wme, current,
&current_prompt, &chosen);
target_found = initial || window_copy_find_output(cs->wme,
line->source_line, &target_prompt, &chosen);
if (!target_found)
return (WINDOW_COPY_CMD_NOTHING);
command_end = initial || !current_found ||
current_prompt != target_prompt;
target = line->source_line;
} else
target = UINT_MAX;
if (window_copy_set_output(cs->wme, args_has(cs->wargs, 'a'), -1,
target, command_end))
return (WINDOW_COPY_CMD_REDRAW);
return (WINDOW_COPY_CMD_NOTHING);
}
static const struct {
const char *command;
u_int minargs;
@@ -3475,6 +4147,24 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_line_numbers_toggle
},
{ .command = "collapse-output",
.args = { "a", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_collapse_output
},
{ .command = "expand-output",
.args = { "a", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_expand_output
},
{ .command = "toggle-output",
.args = { "am", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
.clear = WINDOW_COPY_CMD_CLEAR_NEVER,
.f = window_copy_cmd_toggle_output
},
{ .command = "next-prompt",
.args = { "o", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
@@ -3803,15 +4493,21 @@ window_copy_command(struct window_mode_entry *wme, struct client *c,
enum window_copy_cmd_action action;
enum window_copy_cmd_clear clear = WINDOW_COPY_CMD_CLEAR_NEVER;
const char *command;
u_int i, count = args_count(args);
int keys, flags;
u_int i, count = args_count(args), mouse_x, mouse_y;
int keys, flags, mouse_control = 0;
char *error = NULL;
if (count == 0)
return;
command = args_string(args, 0);
if (m != NULL && m->valid && !MOUSE_WHEEL(m->b))
if (m != NULL && m->valid && !MOUSE_WHEEL(m->b) &&
strcmp(command, "toggle-output") == 0 &&
cmd_mouse_at(wp, m, &mouse_x, &mouse_y, 0) == 0 &&
mouse_x < window_copy_left_margin(wme))
mouse_control = 1;
if (m != NULL && m->valid && !MOUSE_WHEEL(m->b) &&
!mouse_control)
window_copy_move_mouse(m);
cs.wme = wme;
@@ -5212,10 +5908,19 @@ window_copy_line_number_width(struct window_mode_entry *wme)
return (digits + 1);
}
static u_int
window_copy_left_margin(struct window_mode_entry *wme)
{
struct window_copy_mode_data *data = wme->data;
return (window_copy_line_number_width(wme) +
(data->output_gutter ? 3 : 0));
}
static u_int
window_copy_cursor_offset(struct window_mode_entry *wme, u_int cx, u_int sx)
{
u_int width = window_copy_line_number_width(wme);
u_int width = window_copy_left_margin(wme);
u_int content;
if (width == 0)
@@ -5232,7 +5937,7 @@ window_copy_cursor_offset(struct window_mode_entry *wme, u_int cx, u_int sx)
static u_int
window_copy_cursor_unoffset(struct window_mode_entry *wme, u_int vx, u_int sx)
{
u_int width = window_copy_line_number_width(wme);
u_int width = window_copy_left_margin(wme);
u_int content;
if (width == 0)
@@ -5312,14 +6017,16 @@ window_copy_write_line(struct window_mode_entry *wme,
struct grid_cell cur_ln_gc;
u_int sx = screen_size_x(s);
u_int hsize = screen_hsize(data->backing);
u_int width;
u_int width, line_width, gutter_width, backing_y;
u_int absolute, line_number, content_sx;
const char *value;
char *expanded;
struct format_tree *ft;
int current, mode;
width = window_copy_line_number_width(wme);
line_width = window_copy_line_number_width(wme);
gutter_width = data->output_gutter ? 3 : 0;
width = line_width + gutter_width;
if (width >= sx)
content_sx = 1;
else if (width != 0)
@@ -5348,7 +6055,10 @@ window_copy_write_line(struct window_mode_entry *wme,
"copy-mode-current-line-number-style", ft);
cur_ln_gc.flags |= GRID_FLAG_NOPALETTE;
current = (py == data->cy);
absolute = hsize - data->oy + py + 1;
backing_y = hsize - data->oy + py;
absolute = backing_y + 1;
if (data->lines != NULL && backing_y < data->line_count)
absolute = data->lines[backing_y].source_line + 1;
mode = window_copy_line_number_mode(wme);
if (mode == WINDOW_COPY_LINE_NUMBERS_DEFAULT) {
if (py < data->oy)
@@ -5363,9 +6073,39 @@ window_copy_write_line(struct window_mode_entry *wme,
line_number = py - data->cy;
else
line_number = data->cy - py;
screen_write_cursormove(ctx, 0, py, 0);
screen_write_nputs(ctx, width, current ? &cur_ln_gc : &ln_gc,
"%*u ", (int)width - 1, line_number);
if (line_width != 0) {
screen_write_cursormove(ctx, 0, py, 0);
screen_write_nputs(ctx, line_width,
current ? &cur_ln_gc : &ln_gc, "%*u ",
(int)line_width - 1, line_number);
}
if (gutter_width != 0) {
struct grid_cell control_gc;
char control = ' ';
if (data->lines != NULL && backing_y < data->line_count &&
data->lines[backing_y].flags & WINDOW_COPY_LINE_CONTROL) {
if (data->lines[backing_y].flags &
WINDOW_COPY_LINE_INITIAL)
control = data->top_output ? '>' : 'V';
else if (data->outputs[
data->lines[backing_y].output_line])
control = '>';
else
control = 'V';
}
screen_write_cursormove(ctx, line_width, py, 0);
screen_write_putc(ctx, current ? &cur_ln_gc : &ln_gc, ' ');
if (control == ' ')
screen_write_putc(ctx, current ? &cur_ln_gc : &ln_gc, ' ');
else {
memcpy(&control_gc, current ? &cur_ln_gc : &ln_gc,
sizeof control_gc);
control_gc.attr |= GRID_ATTR_CHARSET;
screen_write_putc(ctx, &control_gc, control);
}
screen_write_putc(ctx, current ? &cur_ln_gc : &ln_gc, ' ');
}
}
window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py,
@@ -5440,7 +6180,7 @@ window_copy_redraw_lines(struct window_mode_entry *wme, u_int py, u_int ny)
struct screen_write_ctx ctx;
u_int i;
if (window_copy_line_number_width(wme) != 0) {
if (window_copy_left_margin(wme) != 0) {
screen_write_start(&ctx, &data->screen);
for (i = py; i < py + ny; i++)
window_copy_write_line(wme, &ctx, i);
@@ -5597,8 +6337,8 @@ window_copy_update_cursor(struct window_mode_entry *wme, u_int cx, u_int cy)
old_cx = data->cx; old_cy = data->cy;
data->cx = cx; data->cy = cy;
if (window_copy_line_numbers_active(wme)) {
width = window_copy_line_number_width(wme);
if (window_copy_left_margin(wme) != 0) {
width = window_copy_left_margin(wme);
if (s->sel != NULL ||
data->lineflag != LINE_SEL_NONE ||
@@ -5836,10 +6576,10 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
style_apply(&gc, oo, "copy-mode-selection-style", ft);
gc.flags |= GRID_FLAG_NOPALETTE;
format_free(ft);
clipx = window_copy_line_number_width(wme);
clipx = window_copy_left_margin(wme);
if (clipx >= screen_size_x(s))
clipx = screen_size_x(s) - 1;
if (window_copy_line_numbers_active(wme)) {
if (clipx != 0) {
sx = window_copy_cursor_offset(wme, sx, screen_size_x(s));
endsx = window_copy_cursor_offset(wme, endsx, screen_size_x(s));
}