From dd3839ead180beed56a32849f0f70a6b1b37023f Mon Sep 17 00:00:00 2001 From: Michael Grant Date: Sat, 3 Oct 2026 11:44:19 +0100 Subject: [PATCH] More cleanup. --- input.c | 34 ++- key-bindings.c | 4 +- options-table.c | 1 + regress/copy-mode-exit-status.sh | 2 +- regress/input-modes.sh | 11 + regress/output-commands.sh | 14 + tmux.1 | 16 +- window-copy.c | 501 ++++++++++++++----------------- 8 files changed, 281 insertions(+), 302 deletions(-) diff --git a/input.c b/input.c index 72628ef65..b1be4e447 100644 --- a/input.c +++ b/input.c @@ -3199,7 +3199,8 @@ input_osc_133_save_marker(struct window_pane *wp, struct screen *s) { struct grid *gd = s->grid; struct grid_line *gl; - struct input_osc_133_ctx ctx = {0}; + struct osc133_data *od; + struct input_osc_133_ctx ctx = {0}; u_int y; if (wp == NULL || s != &wp->base || SCREEN_IS_ALTERNATE(s) || @@ -3210,17 +3211,22 @@ input_osc_133_save_marker(struct window_pane *wp, struct screen *s) ctx.collected = gd->scroll_collected; for (y = gd->hsize + gd->sy; y > 0; y--) { gl = grid_get_line(gd, y - 1); - if ((gl->flags & GRID_LINE_START_OUTPUT) && - (~gl->flags & GRID_LINE_END_OUTPUT || - gl->osc133_data.out_end_col < gl->osc133_data.out_start_col) && - (~gl->flags & GRID_LINE_START_PROMPT || - gl->osc133_data.prompt_col <= gl->osc133_data.out_start_col)) { - ctx.start = y - 1; - ctx.col = gl->osc133_data.out_start_col; - break; + if (~gl->flags & GRID_LINE_START_OUTPUT) { + if (gl->flags & + (GRID_LINE_START_PROMPT|GRID_LINE_END_OUTPUT)) + break; + continue; } - if (gl->flags & (GRID_LINE_START_PROMPT|GRID_LINE_END_OUTPUT)) + od = &gl->osc133_data; + if ((gl->flags & GRID_LINE_END_OUTPUT) && + od->out_end_col >= od->out_start_col) break; + if ((gl->flags & GRID_LINE_START_PROMPT) && + od->prompt_col > od->out_start_col) + break; + ctx.start = y - 1; + ctx.col = od->out_start_col; + break; } return (ctx); } @@ -3252,9 +3258,8 @@ input_osc_133_restore_marker(struct screen *s, static int input_osc_133_exit_status(const char *p, int *present) { - const char *end; + const char *end, *errstr; char *copy; - char *endptr; long long status; *present = 0; @@ -3272,10 +3277,9 @@ input_osc_133_exit_status(const char *p, int *present) return (0); } *present = 1; - errno = 0; - status = (int)strtol(copy, &endptr, 10); + status = strtonum(copy, 0, 255, &errstr); free(copy); - if (errno != 0 || endptr == copy || status < 0 || status > 255) + if (errstr != NULL) return (255); return (status); } diff --git a/key-bindings.c b/key-bindings.c index f2bb7fc6a..8ac8a3223 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -587,7 +587,7 @@ key_bindings_init(void) "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 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 -- '%%' } }", @@ -668,7 +668,7 @@ key_bindings_init(void) "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 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 -- '%%' } }", diff --git a/options-table.c b/options-table.c index eed829452..9184c1286 100644 --- a/options-table.c +++ b/options-table.c @@ -115,6 +115,7 @@ static const char *options_table_theme_list[] = { static const char *options_table_copy_mode_line_numbers_list[] = { "off", "default", "absolute", "relative", "hybrid", NULL }; + /* Status line format. */ #define OPTIONS_TABLE_STATUS_FORMAT1 \ "#[align=left range=left #{E:status-left-style}]" \ diff --git a/regress/copy-mode-exit-status.sh b/regress/copy-mode-exit-status.sh index 76bb0ec6c..025a10d09 100644 --- a/regress/copy-mode-exit-status.sh +++ b/regress/copy-mode-exit-status.sh @@ -84,7 +84,7 @@ check_grep " 3 !- p\$ two" # A format wider than the standard three-column gutter is not truncated. $TMUX2 send -X cancel || exit 1 -$TMUX2 set -g copy-mode-exit-status-format \ +$TMUX2 set -p copy-mode-exit-status-format \ '#[align=right]#{?exit_status,!#{exit_status}, }' || exit 1 $TMUX2 copy-mode -c || exit 1 $TMUX2 send -X history-top || exit 1 diff --git a/regress/input-modes.sh b/regress/input-modes.sh index cf0e5f875..72a140a81 100644 --- a/regress/input-modes.sh +++ b/regress/input-modes.sh @@ -26,5 +26,16 @@ check_raw_matches osc133 \ 'L 7 \(7\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,2,5,255' \ 'L 8 \(8\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,3,5,0' +# Reject malformed statuses and distinguish zero from a missing status. +start_pane osc133-status 20 8 \ + '\033]133;C\007a\033]133;D;1junk\007\n\033]133;C\007b\033]133;D;4294967296\007\n\033]133;C\007c\033]133;D;255\007\n\033]133;C\007d\033]133;D;0;foo=bar\007\n\033]133;C\007e\033]133;D;foo=bar\007\n\033]133;C\007f\033]133;D;\007\n' +check_raw_matches osc133-status \ + 'L 0 \(0\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,0,1,255' \ + 'L 1 \(1\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,0,1,255' \ + 'L 2 \(2\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,0,1,255' \ + 'L 3 \(3\) flags=START_OUTPUT,END_OUTPUT,END_OUTPUT_STATUS\[[0-9a-f]+\].* osc133=0,0,0,1,0' \ + 'L 4 \(4\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,0,1,0' \ + 'L 5 \(5\) flags=START_OUTPUT,END_OUTPUT\[[0-9a-f]+\].* osc133=0,0,0,1,0' + $TMUX kill-server 2>/dev/null exit $exit_status diff --git a/regress/output-commands.sh b/regress/output-commands.sh index 4676e8ea3..9bc9f266d 100755 --- a/regress/output-commands.sh +++ b/regress/output-commands.sh @@ -288,4 +288,18 @@ $TMUX show-buffer >$OUT grep -q seq "$OUT" && exit 1 $TMUX send-keys -t :hist.0 -X cancel || exit 1 +# View mode has no pane source snapshot. +$TMUX new-window -d -n view 'exec sleep 100' || exit 1 +$TMUX run-shell -t :view.0 "printf 'view one\nview two\n'" || exit 1 +[ "$($TMUX display-message -p -t :view.0 '#{pane_mode}')" = view-mode ] || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :view.0 -X copy-output -a || exit 1 +[ "$($TMUX show-buffer)" = "$(printf 'view one\nview two')" ] || exit 1 +$TMUX send-keys -t :view.0 -X select-output -a || exit 1 +[ "$($TMUX display-message -p -t :view.0 '#{selection_present}')" = 1 ] || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :view.0 -X copy-selection || exit 1 +[ "$($TMUX show-buffer)" = "$(printf 'view one\nview two')" ] || exit 1 +$TMUX send-keys -t :view.0 -X cancel || exit 1 + exit 0 diff --git a/tmux.1 b/tmux.1 index decd2cabf..c5d758fe3 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2213,10 +2213,23 @@ Expand the output for the command on the cursor line. With .Fl a , expand all command output in the copy-mode buffer. -.Pp +.It Xo +.Ic toggle\-output +.Op Fl a +.Xc +Toggle whether output for the command on the cursor line is collapsed. +With +.Fl a , +toggle all command output in the copy-mode buffer. The Tab key toggles output for the command on the cursor line and S-Tab toggles all command output. .It Xo +.Ic fold\-view\-toggle +(vi: O) +(emacs: O) +.Xc +Toggle OSC 133 output controls in copy mode and expand all command output. +.It Xo .Ic middle\-line (vi: M) (emacs: M\-r) @@ -7434,6 +7447,7 @@ The following variables are available, where appropriate: .It Li "copy_cursor_word" Ta "" Ta "Word under cursor in copy mode" .It Li "copy_cursor_x" Ta "" Ta "Cursor X position in copy mode" .It Li "copy_cursor_y" Ta "" Ta "Cursor Y position in copy mode" +.It Li "copy_fold_view" Ta "" Ta "1 if OSC 133 output controls are shown" .It Li "copy_line_numbers" Ta "" Ta "1 if line numbers are shown in copy mode" .It Li "copy_position" Ta "" Ta "Position shown in the copy mode indicator" .It Li "copy_position_limit" Ta "" Ta "Limit shown in the copy mode indicator" diff --git a/window-copy.c b/window-copy.c index 5422fc8b3..4b9d957b0 100644 --- a/window-copy.c +++ b/window-copy.c @@ -82,6 +82,9 @@ 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_write_gutter(struct window_mode_entry *, + struct screen_write_ctx *, u_int, u_int, + const struct grid_cell *); static void window_copy_rebuild_backing(struct window_mode_entry *); static int window_copy_set_fold(struct window_mode_entry *, const struct window_copy_fold_request *); @@ -293,10 +296,10 @@ enum window_copy_fold_action { }; struct window_copy_fold_request { - enum window_copy_fold_action action; - int all; - u_int target; - int command_end; + enum window_copy_fold_action action; + int all; + u_int target; + int command_end; }; /* State carried across source lines while rebuilding the folded screen. */ @@ -335,12 +338,12 @@ struct window_copy_line { * structure (which is deallocated when the mode ends). */ struct window_copy_mode_data { - struct screen screen; + struct screen screen; - struct screen *backing; - struct screen *source; - u_char *source_flags; /* Flags per source line */ - u_int source_line_count; + struct screen *backing; + struct screen *source; + u_char *source_flags; /* flags per source line */ + u_int source_line_count; struct window_copy_line *lines; u_int line_count; int backing_written; /* backing display started */ @@ -560,6 +563,8 @@ window_copy_set_control(struct window_copy_mode_data *data, u_int y, line->flags |= WINDOW_COPY_LINE_CONTROL; if (expandable) line->flags |= WINDOW_COPY_LINE_EXPANDABLE; + else + line->flags &= ~WINDOW_COPY_LINE_EXPANDABLE; } static void @@ -583,10 +588,7 @@ window_copy_expand_exit_status(struct window_mode_entry *wme, const char *value; char *expanded; - /* - * This is called while rebuilding the backing grid, before the copy - * cursor and viewport have been restored for the new grid. - */ + /* The copy cursor and viewport have not been restored yet. */ ft = format_create(NULL, NULL, 0, 0); format_add(ft, "exit_status", "0"); format_add(ft, "exit_status_present", "0"); @@ -594,7 +596,7 @@ window_copy_expand_exit_status(struct window_mode_entry *wme, format_add(ft, "exit_status", "%hhu", line->exit_status); format_add(ft, "exit_status_present", "1"); } - value = options_get_string(wme->wp->window->options, + value = options_get_string(wme->wp->options, "copy-mode-exit-status-format"); expanded = format_expand(ft, value); format_free(ft); @@ -607,7 +609,7 @@ window_copy_update_exit_status_width(struct window_mode_entry *wme) struct window_copy_mode_data *data = wme->data; struct window_copy_line *line; char *expanded; - u_int width, y; + u_int width, length, y; data->output_status_width = 0; if (!data->fold_view || data->lines == NULL) @@ -618,8 +620,9 @@ window_copy_update_exit_status_width(struct window_mode_entry *wme) if (~line->flags & WINDOW_COPY_LINE_CONTROL) continue; expanded = window_copy_expand_exit_status(wme, line); - if (format_width(expanded) > width) - width = format_width(expanded); + length = format_width(expanded); + if (length > width) + width = length; free(expanded); } data->output_status_width = width; @@ -638,6 +641,19 @@ window_copy_set_source_folded(struct window_copy_mode_data *data, u_int y, return (1); } +/* Find the first primary prompt, if any. */ +static u_int +window_copy_first_prompt(struct grid *gd) +{ + u_int y, total = gd->hsize + gd->sy; + + for (y = 0; y < total; y++) { + if (grid_get_line(gd, y)->flags & GRID_LINE_START_PROMPT) + return (y); + } + return (UINT_MAX); +} + static int window_copy_any_fold_collapsed(struct window_copy_mode_data *data) { @@ -646,14 +662,10 @@ window_copy_any_fold_collapsed(struct window_copy_mode_data *data) 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 && + y = window_copy_first_prompt(gd); + if (y != 0 && y != UINT_MAX && (data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED)) - /* Output which has scrolled off top of buffer is collapsed. */ + /* The initial output region is collapsed. */ return (1); for (y = 0; y < gd->hsize + gd->sy; y++) { @@ -671,7 +683,8 @@ window_copy_any_fold_collapsed(struct window_copy_mode_data *data) } if (gl->flags & GRID_LINE_END_OUTPUT) { if (!output && command && - (data->source_flags[y] & WINDOW_COPY_FOLD_COLLAPSED)) + (data->source_flags[y] & + WINDOW_COPY_FOLD_COLLAPSED)) return (1); command = output = 0; } @@ -687,15 +700,10 @@ window_copy_set_all_folds(struct window_copy_mode_data *data, int collapse) 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 && - window_copy_set_source_folded(data, 0, collapse)) { + y = window_copy_first_prompt(gd); + if (y != 0 && y != UINT_MAX && + window_copy_set_source_folded(data, 0, collapse)) changed = 1; - } for (y = 0; y < gd->hsize + gd->sy; y++) { gl = grid_get_line(gd, y); @@ -706,16 +714,14 @@ window_copy_set_all_folds(struct window_copy_mode_data *data, int collapse) if (gl->flags & GRID_LINE_START_COMMAND) command = 1; if (gl->flags & GRID_LINE_START_OUTPUT) { - if (window_copy_set_source_folded(data, y, collapse)) { + if (window_copy_set_source_folded(data, y, collapse)) changed = 1; - } output = 1; } if (gl->flags & GRID_LINE_END_OUTPUT) { if (!output && command && - window_copy_set_source_folded(data, y, collapse)) { + window_copy_set_source_folded(data, y, collapse)) changed = 1; - } command = output = 0; } } @@ -730,7 +736,6 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) struct screen *src = data->source, *dst; struct grid *sgd; struct grid_line *sgl, *dgl; - struct window_copy_line *line; struct screen_write_ctx ctx; struct grid_cell gc; struct osc133_data *osc133; @@ -744,7 +749,8 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) if (src == NULL) return; if (!data->fold_view) { - dst = window_copy_clone_screen(src, &data->screen, NULL, NULL, 0); + dst = window_copy_clone_screen(src, &data->screen, NULL, NULL, + 0); if (data->backing != NULL) { screen_free(data->backing); free(data->backing); @@ -759,8 +765,8 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) 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)); + 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); @@ -769,24 +775,12 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) data->lines[y].source_line = UINT_MAX; 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) - /* - * The source begins in an existing command or its output. Treat its - * first line as a synthetic command for the region before the first A. - */ - fold.initial = 1; + y = window_copy_first_prompt(sgd); + /* Keep the first line as the control for any initial output. */ + fold.initial = (y != 0 && y != UINT_MAX); screen_write_start(&ctx, dst); - /* - * Markers may occur at any column, so copy the source one cell boundary at - * a time. The final boundary (cellused) is included to handle a marker - * immediately after the last cell on a line. - */ + /* Include the final cell boundary for markers after the last cell. */ for (y = 0; y < total; y++) { sgl = grid_get_line(sgd, y); osc133 = &sgl->osc133_data; @@ -794,124 +788,81 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) dy = dst->grid->hsize + dst->cy; was_hiding = fold.hiding; if (!fold.hiding) - /* - * Row is visible. Records the source - * row and current output-block identity - * for cursor restoration, selection, - * gutter drawing, and line numbers. - */ + /* Map visible rows to their source. */ window_copy_set_line(data, dy, y, fold.output_line, 0); - /* - * Continue even if row is folded because - * there may be markers to process. - */ + /* Process markers on hidden rows too. */ dgl = grid_get_line(dst->grid, dy); if (fold.initial && y == 0 && x == 0) - /* Top line of buffer, no osc133 marker yet. - * Attach controls to top line, make it a - * "synthetic command" so top lines can fold. - */ + /* Attach initial output controls. */ window_copy_set_line(data, dy, y, UINT_MAX, - WINDOW_COPY_LINE_CONTROL | WINDOW_COPY_LINE_INITIAL); + WINDOW_COPY_LINE_CONTROL| + WINDOW_COPY_LINE_INITIAL); if ((sgl->flags & GRID_LINE_START_OUTPUT) && x == osc133->out_start_col) { - /* - * osc133 C starts the collapsible region - * controlled by this source line. - */ + /* C starts the collapsible output region. */ dgl->flags |= GRID_LINE_START_OUTPUT; dgl->osc133_data.out_start_col = dst->cx; fold.output_line = y; - /* used to draw the | or L in gutter */ fold.tree = 1; fold.in_output = 1; fold.output_empty = 1; fold.collapsed = data->source_flags[y] & WINDOW_COPY_FOLD_COLLAPSED; - /* Start/stop hiding cells */ fold.hiding = fold.collapsed; if (fold.prompt_dy != UINT_MAX) /* Attach +/- controls to the prompt. */ window_copy_set_control(data, - fold.prompt_dy, fold.output_line, 1); + fold.prompt_dy, + fold.output_line, 1); } if ((sgl->flags & GRID_LINE_END_OUTPUT) && x == osc133->out_end_col) { - /* - * Command output ends at the osc133 D - * marker. There may be more lines - * (like pre-prompt output) before next A - * marker. Fold these with previous output - * until the following A marker. - */ + /* Fold text after D until the next A. */ dgl->flags |= GRID_LINE_END_OUTPUT; if (sgl->flags & GRID_LINE_END_OUTPUT_STATUS) - dgl->flags |= GRID_LINE_END_OUTPUT_STATUS; + dgl->flags |= + GRID_LINE_END_OUTPUT_STATUS; dgl->osc133_data.out_end_col = dst->cx; - dgl->osc133_data.exit_status = osc133->exit_status; + dgl->osc133_data.exit_status = + osc133->exit_status; if (fold.in_output && fold.output_empty && - fold.prompt_dy != UINT_MAX) { - line = window_copy_get_line_info(data, - fold.prompt_dy); - if (line != NULL) - /* No output to hide, no +/-. */ - line->flags &= ~WINDOW_COPY_LINE_EXPANDABLE; - } + fold.prompt_dy != UINT_MAX) + /* No output to hide, no +/-. */ + window_copy_set_control(data, + fold.prompt_dy, + fold.output_line, 0); fold.in_output = 0; if (fold.output_line == UINT_MAX && fold.command) { - /* - * Saw osc133 B but not C marker. - * This could be user pressed enter - * or command like 'true' with no - * output. - */ + /* B without C has no output. */ fold.output_line = y; fold.tree = 1; - /* Block is collapsed? */ fold.collapsed = data->source_flags[y] & WINDOW_COPY_FOLD_COLLAPSED; if (fold.prompt_dy != UINT_MAX) - /* - * Make this a +/- control row - * but without the +/- for now - * since we don't know what - * comes below. - */ + /* Show output controls. */ window_copy_set_control(data, fold.prompt_dy, fold.output_line, 0); } if (fold.prompt_dy != UINT_MAX && sgl->flags & GRID_LINE_END_OUTPUT_STATUS) - /* - * osc133 D, record exit status on the - * command's prompt row. - */ window_copy_set_exit_status(data, - fold.prompt_dy, osc133->exit_status); + fold.prompt_dy, + osc133->exit_status); if (fold.initial && fold.prompt_dy == UINT_MAX) - /* At top of buffer before first osc A - * marker use first line's fold state. - */ + /* Keep the initial fold state. */ fold.hiding = data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED; else - /* - * Otherwise, use the current block's - * fold state. - */ fold.hiding = fold.collapsed; fold.command = 0; } - /* - * Process osc133 C and D before a following prompt - * at the same column. - */ + /* Process C and D before A at the same column. */ if ((sgl->flags & GRID_LINE_START_PROMPT) && x == osc133->prompt_col) { - /* osc133 A ends region, starts new prompt. */ + /* A starts a new prompt. */ dgl->flags |= GRID_LINE_START_PROMPT; dgl->osc133_data.prompt_col = dst->cx; fold.prompt_dy = dy; @@ -922,21 +873,16 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) } if ((sgl->flags & GRID_LINE_SECOND_PROMPT) && x == osc133->prompt_col) { - /* osc133 A;k=s, marks secondary prompt. */ dgl->flags |= GRID_LINE_SECOND_PROMPT; dgl->osc133_data.prompt_col = dst->cx; } if ((sgl->flags & GRID_LINE_START_COMMAND) && x == osc133->cmd_col) { - /* osc133 B marks start/continue of command. */ dgl->flags |= GRID_LINE_START_COMMAND; dgl->osc133_data.cmd_col = dst->cx; fold.command = 1; } - /* - * An osc133 A may expose the remainder of this source - * line after a folded region. - */ + /* A may expose cells after a folded region. */ if (was_hiding && !fold.hiding) window_copy_set_line(data, dy, y, fold.output_line, 0); @@ -952,40 +898,31 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) fold.prompt_dy != UINT_MAX) window_copy_set_control(data, fold.prompt_dy, fold.output_line, 1); - /* - * osc133 marker processing continues while folded, but - * cell output does not. - */ + /* Skip hidden cells after processing their markers. */ if (fold.hiding) continue; if (!(gc.flags & GRID_FLAG_PADDING)) { screen_write_cell(&ctx, &gc); if (fold.tree) window_copy_set_line(data, dy, y, - fold.output_line, WINDOW_COPY_LINE_MEMBER); + fold.output_line, + WINDOW_COPY_LINE_MEMBER); } } if (!fold.hiding && fold.tree && sgl->cellused == 0 && ~(sgl->flags) & (GRID_LINE_START_PROMPT | GRID_LINE_START_OUTPUT | GRID_LINE_END_OUTPUT)) - /* Mark empty visible lines so gutter tree continues */ + /* Continue the gutter tree on empty lines. */ window_copy_set_line(data, dy, y, fold.output_line, WINDOW_COPY_LINE_MEMBER); if (y + 1 != total && !(sgl->flags & GRID_LINE_WRAPPED) && (!fold.hiding || (fold.initial && y == 0))) { - /* - * Hide folded line breaks, except after the initial - * synthetic command line (top line of copy buffer). - */ + /* Keep the initial region's control line visible. */ screen_write_carriagereturn(&ctx); screen_write_linefeed(&ctx, 0, 8); } if (fold.initial && y == 0) { - /* - * Top line of buffer (which may not be a command) - * becomes our "synthisied" commmand to anchor lines - * folded below it up to first osc133 A. - */ + /* Fold initial output below its control line. */ fold.tree = 1; fold.hiding = data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED; @@ -1029,11 +966,10 @@ window_copy_sync_backing(struct window_mode_entry *wme) struct screen *src = &wp->base; 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, old_count, new_count; + struct grid *dg; + u_int sy, old_hsize, new_hsize; + u_int added, collected, kept; + u_int old_count, new_count; /* * Only a pane's own live grid is tracked incrementally. A different @@ -1042,6 +978,10 @@ window_copy_sync_backing(struct window_mode_entry *wme) */ if (data->source == NULL || data->viewmode || wme->swp != wme->wp) return (0); + dg = dst->grid; + sy = sg->sy; + old_hsize = dg->hsize; + new_hsize = sg->hsize; /* Indices only line up at the same size and generation. */ if (sg->sx != dg->sx || sg->sy != dg->sy || @@ -1064,17 +1004,15 @@ window_copy_sync_backing(struct window_mode_entry *wme) kept = old_hsize - collected; old_count = old_hsize + sy; new_count = new_hsize + sy; - if (collected != 0) { + if (collected != 0) memmove(data->source_flags, data->source_flags + collected, (old_count - collected) * sizeof *data->source_flags); - } data->source_flags = xreallocarray(data->source_flags, new_count, sizeof *data->source_flags); - if (new_count > old_count - collected) { + if (new_count > old_count - collected) memset(data->source_flags + old_count - collected, 0, (new_count - (old_count - collected)) * sizeof *data->source_flags); - } data->source_line_count = new_count; if (added == 0 && collected == 0) { @@ -1185,7 +1123,7 @@ window_copy_init(struct window_mode_entry *wme, screen_size_y(data->source); data->fold_view = args_has(args, 'c') || args_has(args, 'U'); if (args_has(args, 'c')) - window_copy_set_all_folds(data, 1); + window_copy_set_all_folds(data, 1); window_copy_rebuild_backing(wme); window_copy_sync_snapshot(data, base->grid); @@ -1194,7 +1132,8 @@ window_copy_init(struct window_mode_entry *wme, window_copy_find_fold(wme, cy, &prompt, &chosen)) { restored = window_copy_restore_cursor(wme, cx, cy); if (!restored) - restored = window_copy_restore_command_end(wme, prompt, chosen); + restored = window_copy_restore_command_end(wme, prompt, + chosen); } else if (data->fold_view) restored = window_copy_restore_cursor(wme, cx, cy); if (!restored && cy < screen_hsize(data->backing)) { @@ -2183,10 +2122,11 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) { struct window_mode_entry *wme = cs->wme; struct window_copy_mode_data *data = wme->data; - struct window_copy_fold_request request = { + struct window_copy_fold_request request = { .action = WINDOW_COPY_FOLD_EXPAND }; struct grid_reader gr; + struct grid *gd; void *buf; size_t len; u_int sx, sy, ex, ey, bsx, bsy, bex, bey; @@ -2194,10 +2134,13 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) int all = args_has(cs->wargs, 'a'); if (all) { + gd = data->backing->grid; + if (data->source != NULL) + gd = data->source->grid; sx = sy = 0; - total = data->source->grid->hsize + data->source->grid->sy; + total = gd->hsize + gd->sy; ey = total - 1; - ex = grid_get_line(data->source->grid, ey)->cellused; + ex = grid_get_line(gd, ey)->cellused; } else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) return (WINDOW_COPY_CMD_NOTHING); buf = window_copy_get_output(wme, &len, all); @@ -4145,8 +4088,8 @@ window_copy_find_fold(struct window_mode_entry *wme, u_int target, 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) { + if (have_prompt && candidate != UINT_MAX && + target >= last_prompt && target < y) { *prompt = last_prompt; *chosen = candidate; return (1); @@ -4194,8 +4137,7 @@ window_copy_cursor_source(struct window_mode_entry *wme, u_int *source_x, else if (line->source_line == UINT_MAX) { *source_x = UINT_MAX; *source_y = data->source_line_count - 1; - } - else + } else *source_y = line->source_line; } @@ -4263,7 +4205,8 @@ window_copy_restore_command_end(struct window_mode_entry *wme, u_int prompt, 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 command_start = UINT_MAX; + u_int marker = UINT_MAX; u_int marker_x = 0, length, y; int previous = 0; @@ -4354,9 +4297,10 @@ window_copy_set_fold(struct window_mode_entry *wme, { struct window_copy_mode_data *data = wme->data; u_int prompt = 0, source_x, source_y; - u_int chosen = UINT_MAX, first_prompt, y; + u_int chosen = UINT_MAX, first_prompt; u_int target = request->target; - int changed = 0, enabled = 0, found, initial; + int changed = 0, enabled = 0; + int found, initial; int wanted; if (data->source == NULL) @@ -4364,44 +4308,30 @@ window_copy_set_fold(struct window_mode_entry *wme, 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; + first_prompt = window_copy_first_prompt(data->source->grid); + initial = (first_prompt != 0 && first_prompt != UINT_MAX && + target < first_prompt); if (!data->fold_view) { data->fold_view = 1; window_copy_rebuild_backing(wme); enabled = 1; } found = window_copy_find_fold(wme, target, &prompt, &chosen); + wanted = (request->action == WINDOW_COPY_FOLD_COLLAPSE); if (request->all) { - wanted = (request->action == WINDOW_COPY_FOLD_COLLAPSE); if (request->action == WINDOW_COPY_FOLD_TOGGLE) wanted = !window_copy_any_fold_collapsed(data); changed = window_copy_set_all_folds(data, wanted); } else if (initial) { - wanted = (request->action == WINDOW_COPY_FOLD_COLLAPSE); if (request->action == WINDOW_COPY_FOLD_TOGGLE) wanted = !(data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED); - if (window_copy_set_source_folded(data, 0, wanted)) { - changed = 1; - } + changed = window_copy_set_source_folded(data, 0, wanted); } else if (found && chosen != UINT_MAX) { - wanted = (request->action == WINDOW_COPY_FOLD_COLLAPSE); if (request->action == WINDOW_COPY_FOLD_TOGGLE) wanted = !(data->source_flags[chosen] & WINDOW_COPY_FOLD_COLLAPSED); - if (window_copy_set_source_folded(data, chosen, wanted)) { - changed = 1; - } + changed = window_copy_set_source_folded(data, chosen, wanted); } if (!changed) { if (enabled) { @@ -4431,7 +4361,7 @@ window_copy_set_fold(struct window_mode_entry *wme, static enum window_copy_cmd_action window_copy_cmd_collapse_output(struct window_copy_cmd_state *cs) { - struct window_copy_fold_request request = { + struct window_copy_fold_request request = { .action = WINDOW_COPY_FOLD_COLLAPSE, .all = args_has(cs->wargs, 'a'), .target = UINT_MAX @@ -4445,7 +4375,7 @@ window_copy_cmd_collapse_output(struct window_copy_cmd_state *cs) static enum window_copy_cmd_action window_copy_cmd_expand_output(struct window_copy_cmd_state *cs) { - struct window_copy_fold_request request = { + struct window_copy_fold_request request = { .action = WINDOW_COPY_FOLD_EXPAND, .all = args_has(cs->wargs, 'a'), .target = UINT_MAX @@ -4461,7 +4391,7 @@ 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; - struct window_copy_fold_request request = { + struct window_copy_fold_request request = { .action = WINDOW_COPY_FOLD_TOGGLE, .all = args_has(cs->wargs, 'a'), .target = UINT_MAX @@ -4469,26 +4399,31 @@ window_copy_cmd_toggle_output(struct window_copy_cmd_state *cs) u_int current, current_prompt, target; u_int target_prompt, chosen, x, y; int current_found; - int initial, target_found; + int initial; 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)) + if (cs->m == NULL) + return (WINDOW_COPY_CMD_NOTHING); + if (cmd_mouse_at(cs->wme->wp, cs->m, &x, &y, 0) != 0) + return (WINDOW_COPY_CMD_NOTHING); + if (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 || line->source_line == UINT_MAX) return (WINDOW_COPY_CMD_NOTHING); initial = line->flags & WINDOW_COPY_LINE_INITIAL; - window_copy_cursor_source(cs->wme, &x, ¤t); - current_found = window_copy_find_fold(cs->wme, current, - ¤t_prompt, &chosen); - target_found = initial || window_copy_find_fold(cs->wme, - line->source_line, &target_prompt, &chosen); - if (!target_found) - return (WINDOW_COPY_CMD_NOTHING); - request.command_end = initial || !current_found || - current_prompt != target_prompt; + request.command_end = 1; + if (!initial) { + if (!window_copy_find_fold(cs->wme, line->source_line, + &target_prompt, &chosen)) + return (WINDOW_COPY_CMD_NOTHING); + window_copy_cursor_source(cs->wme, &x, ¤t); + current_found = window_copy_find_fold(cs->wme, current, + ¤t_prompt, &chosen); + request.command_end = !current_found || + current_prompt != target_prompt; + } request.target = line->source_line; } if (window_copy_set_fold(cs->wme, &request)) @@ -5178,7 +5113,8 @@ 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), mouse_x, mouse_y; + u_int i, count = args_count(args); + u_int mouse_x, mouse_y; int keys, flags, mouse_control = 0; char *error = NULL; @@ -6606,12 +6542,12 @@ static u_int window_copy_left_margin(struct window_mode_entry *wme) { struct window_copy_mode_data *data = wme->data; - u_int width = 0; + u_int width; + width = window_copy_line_number_width(wme); if (data->fold_view) - width = data->output_status_width + 2; - - return (window_copy_line_number_width(wme) + width); + width += data->output_status_width + 2; + return (width); } static u_int @@ -6702,6 +6638,61 @@ window_copy_get_current_offset(struct window_pane *wp, u_int *offset, return (1); } +static void +window_copy_write_gutter(struct window_mode_entry *wme, + struct screen_write_ctx *ctx, u_int py, u_int line_width, + const struct grid_cell *gc) +{ + struct window_copy_mode_data *data = wme->data; + struct window_copy_line *line, *next; + struct grid_cell control_gc; + u_int backing_y, output_line = UINT_MAX, i; + u_int width = data->output_status_width + 2; + char control = ' ', *status; + + backing_y = screen_hsize(data->backing) - data->oy + py; + line = window_copy_get_line_info(data, backing_y); + if (line != NULL && line->flags & WINDOW_COPY_LINE_CONTROL) { + if (line->flags & WINDOW_COPY_LINE_INITIAL) + output_line = 0; + else if (line->flags & WINDOW_COPY_LINE_EXPANDABLE && + line->output_line < data->source_line_count) + output_line = line->output_line; + if (output_line != UINT_MAX) { + if (data->source_flags[output_line] & + WINDOW_COPY_FOLD_COLLAPSED) + control = '+'; + else + control = '-'; + } + } else if (line != NULL && line->flags & WINDOW_COPY_LINE_MEMBER) { + next = window_copy_get_line_info(data, backing_y + 1); + if (next == NULL || ~next->flags & WINDOW_COPY_LINE_MEMBER || + next->output_line != line->output_line) + control = 'm'; /* ACS L character */ + else + control = 'x'; /* ACS | character */ + } + screen_write_cursormove(ctx, line_width, py, 0); + for (i = 0; i < width; i++) + screen_write_putc(ctx, gc, ' '); + if (control != ' ') { + screen_write_cursormove(ctx, + line_width + data->output_status_width, py, 0); + memcpy(&control_gc, gc, sizeof control_gc); + if (control == 'x' || control == 'm') + control_gc.attr |= GRID_ATTR_CHARSET; + screen_write_putc(ctx, &control_gc, control); + } + if (line != NULL && line->flags & WINDOW_COPY_LINE_CONTROL) { + status = window_copy_expand_exit_status(wme, line); + screen_write_cursormove(ctx, line_width, py, 0); + format_draw(ctx, gc, data->output_status_width, status, NULL, + 0); + free(status); + } +} + static void window_copy_write_line(struct window_mode_entry *wme, struct screen_write_ctx *ctx, u_int py) @@ -6712,22 +6703,18 @@ window_copy_write_line(struct window_mode_entry *wme, struct options *oo = wp->window->options; struct grid_cell gc, mgc, cgc, mkgc, clgc, ln_gc; struct grid_cell cur_ln_gc; - struct grid_cell control_gc; - struct window_copy_line *line, *next; + const struct grid_cell *line_gc; u_int sx = screen_size_x(s); u_int hsize = screen_hsize(data->backing); - u_int width, line_width, gutter_width, backing_y; + u_int width, line_width, backing_y; u_int absolute, line_number, content_sx; - u_int i; const char *value; - char *expanded, *status; + char *expanded; struct format_tree *ft; - char control; int current, mode; line_width = window_copy_line_number_width(wme); - gutter_width = data->fold_view ? data->output_status_width + 2 : 0; - width = line_width + gutter_width; + width = window_copy_left_margin(wme); if (width >= sx) content_sx = 1; else if (width != 0) @@ -6755,6 +6742,7 @@ 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); + line_gc = current ? &cur_ln_gc : &ln_gc; backing_y = hsize - data->oy + py; absolute = backing_y + 1; if (data->lines != NULL && backing_y < data->line_count && @@ -6780,62 +6768,12 @@ window_copy_write_line(struct window_mode_entry *wme, line_number = data->cy - py; if (line_width != 0) { screen_write_cursormove(ctx, 0, py, 0); - screen_write_nputs(ctx, line_width, - current ? &cur_ln_gc : &ln_gc, "%*u ", + screen_write_nputs(ctx, line_width, line_gc, "%*u ", (int)line_width - 1, line_number); } - if (gutter_width != 0) { - line = NULL; - control = ' '; - if (data->lines != NULL && backing_y < data->line_count) - line = &data->lines[backing_y]; - if (line != NULL && - line->flags & WINDOW_COPY_LINE_CONTROL) { - if (line->flags & - WINDOW_COPY_LINE_INITIAL) { - control = data->source_flags[0] & - WINDOW_COPY_FOLD_COLLAPSED ? '+' : '-'; - } else if (line->flags & WINDOW_COPY_LINE_EXPANDABLE && - line->output_line < data->source_line_count) { - if (data->source_flags[line->output_line] & - WINDOW_COPY_FOLD_COLLAPSED) - control = '+'; - else - control = '-'; - } - } else if (line != NULL && - line->flags & WINDOW_COPY_LINE_MEMBER) { - next = NULL; - if (backing_y + 1 < data->line_count) - next = &data->lines[backing_y + 1]; - if (next == NULL || - ~(next->flags) & WINDOW_COPY_LINE_MEMBER || - next->output_line != line->output_line) - control = 'm'; /* ACS L character */ - else - control = 'x'; /* ACS | character */ - } - screen_write_cursormove(ctx, line_width, py, 0); - for (i = 0; i < gutter_width; i++) - screen_write_putc(ctx, current ? &cur_ln_gc : &ln_gc, ' '); - if (control != ' ') { - screen_write_cursormove(ctx, - line_width + data->output_status_width, py, 0); - memcpy(&control_gc, current ? &cur_ln_gc : &ln_gc, - sizeof control_gc); - if (control == 'x' || control == 'm') - control_gc.attr |= GRID_ATTR_CHARSET; - screen_write_putc(ctx, &control_gc, control); - } - if (line != NULL && - line->flags & WINDOW_COPY_LINE_CONTROL) { - status = window_copy_expand_exit_status(wme, line); - screen_write_cursormove(ctx, line_width, py, 0); - format_draw(ctx, current ? &cur_ln_gc : &ln_gc, - data->output_status_width, status, NULL, 0); - free(status); - } - } + if (data->fold_view) + window_copy_write_gutter(wme, ctx, py, line_width, + line_gc); } window_copy_write_one(wme, ctx, width, py, hsize - data->oy + py, @@ -6847,7 +6785,8 @@ window_copy_write_line(struct window_mode_entry *wme, expanded = format_expand(ft, value); if (*expanded != '\0') { screen_write_cursormove(ctx, width, 0, 0); - format_draw(ctx, &gc, content_sx, expanded, NULL, 0); + format_draw(ctx, &gc, content_sx, expanded, + NULL, 0); } free(expanded); } @@ -7456,7 +7395,8 @@ window_copy_find_previous_output_range(struct screen *s, u_int cursor_x, struct osc133_data *od; u_int start_x, start_y, end_x, end_y; u_int y, total; - int found = 0, found_empty = 0, have_prompt = 0; + int found = 0, found_empty = 0; + int have_prompt = 0; int pending = 0, started_here; int has_start, has_end, end_first; @@ -7491,12 +7431,7 @@ window_copy_find_previous_output_range(struct screen *s, u_int cursor_x, end_x = od->out_end_col; end_y = y; - /* - * Prefer a previously found non-empty output over a - * later one that turns out to be empty (for example a - * command with no output immediately followed by the - * cursor's prompt). - */ + /* Prefer earlier output to a later empty command. */ if (!found || found_empty || start_x != end_x || start_y != end_y) { *sx = start_x; @@ -7538,13 +7473,12 @@ window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all) { struct window_copy_mode_data *data = wme->data; struct grid *gd; + void *buf; u_int sx, sy, ex, ey, total; - if (data->source == NULL) { - *len = 0; - return (NULL); - } - gd = data->source->grid; + gd = data->backing->grid; + if (data->source != NULL) + gd = data->source->grid; total = gd->hsize + gd->sy; if (all) { sx = sy = 0; @@ -7554,7 +7488,8 @@ window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all) *len = 0; return (NULL); } - return (window_copy_get_grid_range(gd, sx, sy, ex, ey, len)); + buf = window_copy_get_grid_range(gd, sx, sy, ex, ey, len); + return (buf); } static void *