diff --git a/input.c b/input.c index c3707c59c..57eb967bb 100644 --- a/input.c +++ b/input.c @@ -95,6 +95,14 @@ struct input_param { }; }; +/* Saved OSC 133 output marker. */ +struct input_osc_133_ctx { + int running; + u_int start; + u_int col; + u_int collected; +}; + /* Input parser context. */ struct input_ctx { struct window_pane *wp; @@ -173,6 +181,9 @@ static void input_osc_110(struct input_ctx *, const char *); static void input_osc_111(struct input_ctx *, const char *); static void input_osc_112(struct input_ctx *, const char *); static void input_osc_133(struct input_ctx *, const char *); +static struct input_osc_133_ctx input_osc_133_save_marker(struct input_ctx *); +static void input_osc_133_restore_marker(struct input_ctx *, + const struct input_osc_133_ctx *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -1466,6 +1477,7 @@ input_csi_dispatch(struct input_ctx *ictx) struct screen *s = sctx->s; const struct input_table_entry *entry; struct options *oo; + struct input_osc_133_ctx osc133 = {0}; int i, n, m, ek, set, p; u_int cx, bg = ictx->cell.cell.bg; @@ -1739,13 +1751,19 @@ input_csi_dispatch(struct input_ctx *ictx) case -1: break; case 0: + /* Erasing to the end clears the whole screen at 0,0. */ + if (s->cx == 0 && s->cy == 0) + osc133 = input_osc_133_save_marker(ictx); screen_write_clearendofscreen(sctx, bg); + input_osc_133_restore_marker(ictx, &osc133); break; case 1: screen_write_clearstartofscreen(sctx, bg); break; case 2: + osc133 = input_osc_133_save_marker(ictx); screen_write_clearscreen(sctx, bg); + input_osc_133_restore_marker(ictx, &osc133); break; case 3: if (input_get(ictx, 1, 0, 0) == 0) { @@ -3278,6 +3296,77 @@ input_osc_133_secondary_prompt(const char *p) return (0); } +/* Record the first primary or secondary prompt marker on a line. */ +static void +input_osc_133_mark_prompt(struct grid_line *gl, u_int col, const char *p) +{ + if (gl == NULL || gl->flags & + (GRID_LINE_START_PROMPT|GRID_LINE_SECOND_PROMPT)) + return; + gl->osc133_data.prompt_col = col; + if (input_osc_133_secondary_prompt(p)) + gl->flags |= GRID_LINE_SECOND_PROMPT; + else + gl->flags |= GRID_LINE_START_PROMPT; +} + +/* Save the running command's output start before clearing the screen. */ +static struct input_osc_133_ctx +input_osc_133_save_marker(struct input_ctx *ictx) +{ + struct window_pane *wp = ictx->wp; + struct screen *s = ictx->ctx.s; + struct grid *gd = s->grid; + struct grid_line *gl; + struct input_osc_133_ctx ctx = {0}; + u_int y; + + if (wp == NULL || s != &wp->base || SCREEN_IS_ALTERNATE(s) || + (~wp->flags & PANE_CMDRUNNING)) + return (ctx); + ctx.running = 1; + ctx.start = UINT_MAX; + 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_PROMPT|GRID_LINE_END_OUTPUT)) + break; + } + return (ctx); +} + +/* Restore the running command's output start if a screen clear removed it. */ +static void +input_osc_133_restore_marker(struct input_ctx *ictx, + const struct input_osc_133_ctx *ctx) +{ + struct grid *gd = ictx->ctx.s->grid; + struct grid_line *gl; + u_int collected; + + if (!ctx->running) + return; + collected = gd->scroll_collected - ctx->collected; + if (ctx->start != UINT_MAX && ctx->start >= collected) { + gl = grid_get_line(gd, ctx->start - collected); + if ((gl->flags & GRID_LINE_START_OUTPUT) && + gl->osc133_data.out_start_col == ctx->col) + return; + } + gl = grid_get_line(gd, gd->hsize); + gl->flags |= GRID_LINE_START_OUTPUT; + gl->osc133_data.out_start_col = 0; +} + /* Handle the OSC 133 sequence. */ static void input_osc_133(struct input_ctx *ictx, const char *p) @@ -3295,32 +3384,14 @@ input_osc_133(struct input_ctx *ictx, const char *p) switch (*p) { case 'A': case 'N': - if (gl != NULL) { - if (!(gl->flags & (GRID_LINE_START_PROMPT| - GRID_LINE_SECOND_PROMPT))) { - gl->osc133_data.prompt_col = s->cx; - if (input_osc_133_secondary_prompt(p)) - gl->flags |= GRID_LINE_SECOND_PROMPT; - else - gl->flags |= GRID_LINE_START_PROMPT; - } - } + input_osc_133_mark_prompt(gl, s->cx, p); if (wp != NULL) { wp->last_prompt_time = time(NULL); events_fire_pane("pane-shell-prompt", wp); } break; case 'P': - if (gl != NULL) { - if (!(gl->flags & (GRID_LINE_START_PROMPT| - GRID_LINE_SECOND_PROMPT))) { - gl->osc133_data.prompt_col = s->cx; - if (input_osc_133_secondary_prompt(p)) - gl->flags |= GRID_LINE_SECOND_PROMPT; - else - gl->flags |= GRID_LINE_START_PROMPT; - } - } + input_osc_133_mark_prompt(gl, s->cx, p); break; case 'B': case 'I': diff --git a/regress/output-commands.sh b/regress/output-commands.sh index 323660b05..ab305eed2 100755 --- a/regress/output-commands.sh +++ b/regress/output-commands.sh @@ -140,6 +140,103 @@ $TMUX send-keys -t :clear.0 -X copy-selection || exit 1 [ "$($TMUX show-buffer)" = "$(printf 'PID TTY\n1 pts/0')" ] || exit 1 $TMUX send-keys -t :clear.0 -X cancel || exit 1 +# Select from either output line after clearing the screen. +for view in normal unfolded; do + for steps in 2 3; do + if [ "$view" = unfolded ]; then + $TMUX copy-mode -U -t :clear || exit 1 + else + $TMUX copy-mode -t :clear || exit 1 + fi + $TMUX send-keys -t :clear.0 -X -N "$steps" cursor-up || exit 1 + $TMUX send-keys -t :clear.0 C-o || exit 1 + [ "$($TMUX display-message -p -t :clear.0 '#{selection_present}')" = 1 ] || exit 1 + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t :clear.0 -X copy-selection || exit 1 + [ "$($TMUX show-buffer)" = "$(printf 'PID TTY\n1 pts/0')" ] || exit 1 + $TMUX send-keys -t :clear.0 -X cancel || exit 1 + done +done + +$TMUX set-option -g scroll-on-clear on || exit 1 +$TMUX new-window -d -n clearprompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007clear; echo hello; echo world\\n\\033]133;C\\007\\033[H\\033[2J\\033[3Jhello\\nworld\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +for steps in 1 2; do + $TMUX copy-mode -t :clearprompt || exit 1 + $TMUX send-keys -t :clearprompt.0 -X -N "$steps" cursor-up || exit 1 + $TMUX send-keys -t :clearprompt.0 C-o || exit 1 + [ "$($TMUX display-message -p -t :clearprompt.0 '#{selection_present}')" = 1 ] || exit 1 + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t :clearprompt.0 -X copy-selection || exit 1 + [ "$($TMUX show-buffer)" = "$(printf 'hello\nworld')" ] || exit 1 + $TMUX send-keys -t :clearprompt.0 -X cancel || exit 1 +done + +# A screen clear must not pull earlier commands or plain scrollback into output. +for integration in plain marked; do + for erase in J 2J; do + $TMUX set-option -g scroll-on-clear off || exit 1 + if [ "$integration" = marked ]; then + prefix='\033]133;A\007old>\033]133;B\007seq\n\033]133;C\007' + ending='\033]133;D;0\007' + else + prefix= + ending= + fi + $TMUX new-window -d -n retained "printf '$prefix'; seq 1 40; printf '$ending\\033]133;A\\007p\\$ \\033]133;B\\007clear; echo hello; echo world\\n\\033]133;C\\007\\033[H\\033[${erase}hello\\nworld\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 + sleep 1 + [ "$($TMUX display-message -p -t :retained.0 '#{history_size}')" -gt 0 ] || exit 1 + for steps in 0 1 2; do + $TMUX copy-mode -t :retained || exit 1 + if [ "$steps" -gt 0 ]; then + $TMUX send-keys -t :retained.0 -X -N "$steps" cursor-up || exit 1 + fi + $TMUX send-keys -t :retained.0 C-o || exit 1 + [ "$($TMUX display-message -p -t :retained.0 '#{selection_present}')" = 1 ] || exit 1 + $TMUX set-buffer sentinel || exit 1 + $TMUX send-keys -t :retained.0 -X copy-selection || exit 1 + [ "$($TMUX show-buffer)" = "$(printf 'hello\nworld')" ] || exit 1 + $TMUX send-keys -t :retained.0 -X cancel || exit 1 + done + $TMUX kill-window -t :retained || exit 1 + done +done + +# Preserve C when scroll-on-clear keeps the command's earlier output. +$TMUX set-option -g scroll-on-clear on || exit 1 +$TMUX new-window -d -n preserve "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo before; clear; echo hello; echo world\\n\\033]133;C\\007before\\n\\033[H\\033[2Jhello\\nworld\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :preserve || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :preserve.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = "$(printf 'before\nhello\nworld')" ] || exit 1 +$TMUX send-keys -t :preserve.0 -X cancel || exit 1 + +# An unfinished command can be selected using its restored C without D. +$TMUX set-option -g scroll-on-clear off || exit 1 +$TMUX new-window -d -n running "seq 1 40; printf '\\033]133;A\\007p\\$ \\033]133;B\\007clear; echo hello; echo world\\n\\033]133;C\\007\\033[H\\033[2Jhello\\nworld\\n'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :running || exit 1 +$TMUX send-keys -t :running.0 -X cursor-up || exit 1 +$TMUX send-keys -t :running.0 C-o || exit 1 +[ "$($TMUX display-message -p -t :running.0 '#{selection_present}')" = 1 ] || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :running.0 -X copy-selection || exit 1 +[ "$($TMUX show-buffer)" = "$(printf 'hello\nworld')" ] || exit 1 +$TMUX send-keys -t :running.0 -X cancel || exit 1 + +# Do not infer output from a D which belongs to the first surviving prompt. +$TMUX new-window -d -n prefix "printf 'prefix\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\033]133;D;0\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :prefix || exit 1 +$TMUX send-keys -t :prefix.0 -X cursor-up || exit 1 +$TMUX send-keys -t :prefix.0 C-o || exit 1 +[ "$($TMUX display-message -p -t :prefix.0 '#{selection_present}')" = 0 ] || exit 1 +$TMUX set-buffer sentinel || exit 1 +$TMUX send-keys -t :prefix.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = sentinel ] || exit 1 +$TMUX send-keys -t :prefix.0 -X cancel || exit 1 + $TMUX new-window -d -n oneline "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\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 -t :oneline || exit 1 diff --git a/window-copy.c b/window-copy.c index 0e1b6408f..5422fc8b3 100644 --- a/window-copy.c +++ b/window-copy.c @@ -27,6 +27,7 @@ #include "tmux.h" struct window_copy_mode_data; +struct window_copy_fold_request; #define WINDOW_COPY_LINE_CONTROL 0x1 #define WINDOW_COPY_LINE_MEMBER 0x2 @@ -82,8 +83,8 @@ static void window_copy_write_line(struct window_mode_entry *, 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_fold(struct window_mode_entry *, int, int, - u_int, int); +static int window_copy_set_fold(struct window_mode_entry *, + const struct window_copy_fold_request *); static int window_copy_find_fold(struct window_mode_entry *, u_int, u_int *, u_int *); static void window_copy_cursor_source(struct window_mode_entry *, u_int *, @@ -285,6 +286,32 @@ struct window_copy_cmd_state { struct winlink *wl; }; +enum window_copy_fold_action { + WINDOW_COPY_FOLD_COLLAPSE, + WINDOW_COPY_FOLD_EXPAND, + WINDOW_COPY_FOLD_TOGGLE, +}; + +struct window_copy_fold_request { + enum window_copy_fold_action action; + int all; + u_int target; + int command_end; +}; + +/* State carried across source lines while rebuilding the folded screen. */ +struct window_copy_fold_ctx { + u_int prompt_dy; + u_int output_line; + int collapsed; + int command; + int hiding; + int tree; + int in_output; + int output_empty; + int initial; +}; + struct window_copy_line { u_int source_line; u_int output_line; @@ -707,11 +734,12 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) 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, tree = 0; - int in_output = 0, output_empty = 0; - int initial = 0, was_hiding; + struct window_copy_fold_ctx fold = { + .prompt_dy = UINT_MAX, + .output_line = UINT_MAX + }; + u_int y, x, total, dy; + int was_hiding; if (src == NULL) return; @@ -751,7 +779,7 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) * 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. */ - initial = 1; + fold.initial = 1; screen_write_start(&ctx, dst); /* @@ -764,21 +792,22 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) osc133 = &sgl->osc133_data; for (x = 0; x <= sgl->cellused; x++) { dy = dst->grid->hsize + dst->cy; - was_hiding = hiding; - if (!hiding) + 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. */ - window_copy_set_line(data, dy, y, output_line, 0); + window_copy_set_line(data, dy, y, + fold.output_line, 0); /* * Continue even if row is folded because * there may be markers to process. */ dgl = grid_get_line(dst->grid, dy); - if (initial && y == 0 && x == 0) + 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. @@ -793,17 +822,19 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) */ dgl->flags |= GRID_LINE_START_OUTPUT; dgl->osc133_data.out_start_col = dst->cx; - output_line = y; - tree = 1; /* used to draw the | or L in gutter */ - in_output = 1; - output_empty = 1; - collapsed = data->source_flags[y] & + 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; - hiding = collapsed; /* Start/stop hiding cells */ - if (prompt_dy != UINT_MAX) + /* Start/stop hiding cells */ + fold.hiding = fold.collapsed; + if (fold.prompt_dy != UINT_MAX) /* Attach +/- controls to the prompt. */ - window_copy_set_control(data, prompt_dy, - output_line, 1); + window_copy_set_control(data, + fold.prompt_dy, fold.output_line, 1); } if ((sgl->flags & GRID_LINE_END_OUTPUT) && x == osc133->out_end_col) { @@ -819,56 +850,60 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) dgl->flags |= GRID_LINE_END_OUTPUT_STATUS; dgl->osc133_data.out_end_col = dst->cx; dgl->osc133_data.exit_status = osc133->exit_status; - if (in_output && output_empty && prompt_dy != UINT_MAX) { - line = window_copy_get_line_info(data, prompt_dy); + 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; } - in_output = 0; - if (output_line == UINT_MAX && command) { + 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. */ - output_line = y; - tree = 1; + fold.output_line = y; + fold.tree = 1; /* Block is collapsed? */ - collapsed = data->source_flags[y] & + fold.collapsed = data->source_flags[y] & WINDOW_COPY_FOLD_COLLAPSED; - if (prompt_dy != UINT_MAX) + if (fold.prompt_dy != UINT_MAX) /* * Make this a +/- control row * but without the +/- for now * since we don't know what * comes below. */ - window_copy_set_control(data, prompt_dy, - output_line, 0); + window_copy_set_control(data, + fold.prompt_dy, + fold.output_line, 0); } - if (prompt_dy != UINT_MAX && + 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, prompt_dy, - osc133->exit_status); - if (initial && prompt_dy == UINT_MAX) + window_copy_set_exit_status(data, + 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. */ - hiding = data->source_flags[0] & + fold.hiding = data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED; else /* * Otherwise, use the current block's * fold state. */ - hiding = collapsed; - command = 0; + fold.hiding = fold.collapsed; + fold.command = 0; } /* * Process osc133 C and D before a following prompt @@ -879,10 +914,11 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) /* osc133 A ends region, starts new prompt. */ dgl->flags |= GRID_LINE_START_PROMPT; dgl->osc133_data.prompt_col = dst->cx; - prompt_dy = dy; - output_line = UINT_MAX; - collapsed = command = hiding = tree = 0; - in_output = output_empty = 0; + fold.prompt_dy = dy; + fold.output_line = UINT_MAX; + fold.collapsed = fold.command = 0; + fold.hiding = fold.tree = 0; + fold.in_output = fold.output_empty = 0; } if ((sgl->flags & GRID_LINE_SECOND_PROMPT) && x == osc133->prompt_col) { @@ -895,44 +931,48 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) /* osc133 B marks start/continue of command. */ dgl->flags |= GRID_LINE_START_COMMAND; dgl->osc133_data.cmd_col = dst->cx; - command = 1; + fold.command = 1; } /* * An osc133 A may expose the remainder of this source * line after a folded region. */ - if (was_hiding && !hiding) - window_copy_set_line(data, dy, y, output_line, 0); + if (was_hiding && !fold.hiding) + window_copy_set_line(data, dy, y, + fold.output_line, 0); if (x == sgl->cellused) break; grid_get_cell(sgd, x, y, &gc); - if (in_output && !(gc.flags & GRID_FLAG_PADDING)) - output_empty = 0; + if (fold.in_output && !(gc.flags & GRID_FLAG_PADDING)) + fold.output_empty = 0; /* Separator text after D is also expandable. */ - if (!in_output && tree && output_line != UINT_MAX && - !(gc.flags & GRID_FLAG_PADDING) && prompt_dy != UINT_MAX) - window_copy_set_control(data, prompt_dy, output_line, 1); + if (!fold.in_output && fold.tree && + fold.output_line != UINT_MAX && + !(gc.flags & GRID_FLAG_PADDING) && + 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. */ - if (hiding) + if (fold.hiding) continue; if (!(gc.flags & GRID_FLAG_PADDING)) { screen_write_cell(&ctx, &gc); - if (tree) - window_copy_set_line(data, dy, y, output_line, - WINDOW_COPY_LINE_MEMBER); + if (fold.tree) + window_copy_set_line(data, dy, y, + fold.output_line, WINDOW_COPY_LINE_MEMBER); } } - if (!hiding && tree && sgl->cellused == 0 && + 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 */ - window_copy_set_line(data, dy, y, output_line, + window_copy_set_line(data, dy, y, fold.output_line, WINDOW_COPY_LINE_MEMBER); if (y + 1 != total && !(sgl->flags & GRID_LINE_WRAPPED) && - (!hiding || (initial && y == 0))) { + (!fold.hiding || (fold.initial && y == 0))) { /* * Hide folded line breaks, except after the initial * synthetic command line (top line of copy buffer). @@ -940,14 +980,14 @@ window_copy_rebuild_backing(struct window_mode_entry *wme) screen_write_carriagereturn(&ctx); screen_write_linefeed(&ctx, 0, 8); } - if (initial && y == 0) { + 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. */ - tree = 1; - hiding = data->source_flags[0] & + fold.tree = 1; + fold.hiding = data->source_flags[0] & WINDOW_COPY_FOLD_COLLAPSED; } } @@ -2143,6 +2183,9 @@ 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 = { + .action = WINDOW_COPY_FOLD_EXPAND + }; struct grid_reader gr; void *buf; size_t len; @@ -2166,8 +2209,10 @@ window_copy_cmd_select_output(struct window_copy_cmd_state *cs) window_copy_set_all_folds(data, 0); window_copy_rebuild_backing(wme); } else if (data->fold_view && - data->source_flags[sy] & WINDOW_COPY_FOLD_COLLAPSED) - window_copy_set_fold(wme, 0, 0, sy, 0); + data->source_flags[sy] & WINDOW_COPY_FOLD_COLLAPSED) { + request.target = sy; + window_copy_set_fold(wme, &request); + } if (!window_copy_source_position(wme, sx, sy, &bsx, &bsy) || !window_copy_source_position(wme, ex, ey, &bex, &bey)) return (WINDOW_COPY_CMD_NOTHING); @@ -4304,12 +4349,13 @@ window_copy_restore_before(struct window_mode_entry *wme, u_int source_y) } static int -window_copy_set_fold(struct window_mode_entry *wme, int all, int collapse, - u_int target, int command_end) +window_copy_set_fold(struct window_mode_entry *wme, + const struct window_copy_fold_request *request) { 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 target = request->target; int changed = 0, enabled = 0, found, initial; int wanted; @@ -4335,22 +4381,22 @@ window_copy_set_fold(struct window_mode_entry *wme, int all, int collapse, enabled = 1; } found = window_copy_find_fold(wme, target, &prompt, &chosen); - if (all) { - wanted = collapse; - if (wanted == -1) + 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 = collapse; - if (wanted == -1) + 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; } } else if (found && chosen != UINT_MAX) { - wanted = collapse; - if (wanted == -1) + 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)) { @@ -4367,7 +4413,7 @@ window_copy_set_fold(struct window_mode_entry *wme, int all, int collapse, window_copy_clear_selection(wme); window_copy_clear_marks(wme); window_copy_rebuild_backing(wme); - if (!command_end && + if (!request->command_end && window_copy_restore_cursor(wme, source_x, source_y)) return (1); if (initial && window_copy_restore_before(wme, 0)) @@ -4385,8 +4431,13 @@ window_copy_set_fold(struct window_mode_entry *wme, int all, int collapse, static enum window_copy_cmd_action window_copy_cmd_collapse_output(struct window_copy_cmd_state *cs) { - if (window_copy_set_fold(cs->wme, args_has(cs->wargs, 'a'), 1, - UINT_MAX, 0)) + struct window_copy_fold_request request = { + .action = WINDOW_COPY_FOLD_COLLAPSE, + .all = args_has(cs->wargs, 'a'), + .target = UINT_MAX + }; + + if (window_copy_set_fold(cs->wme, &request)) return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_NOTHING); } @@ -4394,8 +4445,13 @@ 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) { - if (window_copy_set_fold(cs->wme, args_has(cs->wargs, 'a'), 0, - UINT_MAX, 0)) + struct window_copy_fold_request request = { + .action = WINDOW_COPY_FOLD_EXPAND, + .all = args_has(cs->wargs, 'a'), + .target = UINT_MAX + }; + + if (window_copy_set_fold(cs->wme, &request)) return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_NOTHING); } @@ -4405,9 +4461,14 @@ 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 = { + .action = WINDOW_COPY_FOLD_TOGGLE, + .all = args_has(cs->wargs, 'a'), + .target = UINT_MAX + }; u_int current, current_prompt, target; u_int target_prompt, chosen, x, y; - int command_end = 0, current_found; + int current_found; int initial, target_found; if (args_has(cs->wargs, 'm')) { @@ -4426,13 +4487,11 @@ window_copy_cmd_toggle_output(struct window_copy_cmd_state *cs) line->source_line, &target_prompt, &chosen); if (!target_found) return (WINDOW_COPY_CMD_NOTHING); - command_end = initial || !current_found || + request.command_end = initial || !current_found || current_prompt != target_prompt; - target = line->source_line; - } else - target = UINT_MAX; - if (window_copy_set_fold(cs->wme, args_has(cs->wargs, 'a'), -1, - target, command_end)) + request.target = line->source_line; + } + if (window_copy_set_fold(cs->wme, &request)) return (WINDOW_COPY_CMD_REDRAW); return (WINDOW_COPY_CMD_NOTHING); } @@ -7291,6 +7350,7 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, struct grid_line *gl; u_int cursor_x, cursor_y, prompt_x = 0; u_int prompt_y = UINT_MAX, y, total; + u_int output_x = 0, output_y = UINT_MAX; int found_start = 0, found_end = 0; int next_prompt, in_range, found; @@ -7303,6 +7363,12 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, total = gd->hsize + gd->sy; for (y = 0; y < total; y++) { gl = grid_get_line(gd, y); + if ((gl->flags & GRID_LINE_START_OUTPUT) && + (y < cursor_y || (y == cursor_y && + gl->osc133_data.out_start_col <= cursor_x))) { + output_x = gl->osc133_data.out_start_col; + output_y = y; + } if (~gl->flags & GRID_LINE_START_PROMPT) continue; if (y > cursor_y || @@ -7311,11 +7377,18 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, prompt_y = y; prompt_x = gl->osc133_data.prompt_col; } + /* C may survive without A, or have been restored after a clear. */ + if (output_y != UINT_MAX && (prompt_y == UINT_MAX || + output_y > prompt_y || + (output_y == prompt_y && output_x >= prompt_x))) { + prompt_x = output_x; + prompt_y = output_y; + } if (prompt_y == UINT_MAX) { - log_debug("%s: no osc133 prompt before cursor", __func__); + log_debug("%s: no osc133 marker before cursor", __func__); return (0); } - log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y); + log_debug("%s: start at %u,%u", __func__, prompt_x, prompt_y); for (y = prompt_y; y < total; y++) { gl = grid_get_line(gd, y); @@ -7336,6 +7409,8 @@ window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, } /* An output may end on the same line or the next prompt's. */ if (found_start && gl->flags & GRID_LINE_END_OUTPUT && + (!next_prompt || gl->osc133_data.out_end_col <= + gl->osc133_data.prompt_col) && (y != prompt_y || gl->osc133_data.out_end_col >= prompt_x) && (y != *sy || gl->osc133_data.out_end_col >= *sx)) { *ex = gl->osc133_data.out_end_col;