diff --git a/key-bindings.c b/key-bindings.c index 2555b9fcb..d0460dc88 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -53,6 +53,7 @@ " '#{?#{m/r:(copy|view)-mode,#{pane_mode}},Go To Bottom,}' '>' {send -X history-bottom}" \ " ''" \ " '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Hide Line Numbers,Show Line Numbers},}' 'L' {send -X line-numbers-toggle}" \ + " '#{?#{==:#{pane_mode},copy-mode},Edit,}' 'e' {if -F '#{selection_present}' {send -X open-selection} {send -X open-output}}" \ " '#{?#{==:#{pane_mode},copy-mode},#{?refresh_active,Refresh Off,Refresh On},}' 'r' {send -X refresh-toggle}" \ " ''" \ " '#{?#{&&:#{buffer_size},#{!:#{pane_in_mode}}},Paste #[underscore]#{=/9/...:buffer_sample},}' 'p' {paste-buffer}" \ @@ -575,6 +576,7 @@ key_bindings_init(void) "bind -Tcopy-mode C-l { send -X recentre-top-bottom }", "bind -Tcopy-mode M-l { send -X cursor-centre-horizontal }", "bind -Tcopy-mode C-n { send -X cursor-down }", + "bind -Tcopy-mode C-o { send -X select-output }", "bind -Tcopy-mode C-p { send -X cursor-up }", "bind -Tcopy-mode C-r { command-prompt -P -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental -- '%%' } }", "bind -Tcopy-mode C-s { command-prompt -P -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental -- '%%' } }", @@ -592,6 +594,7 @@ key_bindings_init(void) "bind -Tcopy-mode R { send -X rectangle-toggle }", "bind -Tcopy-mode T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", "bind -Tcopy-mode X { send -X set-mark }", + "bind -Tcopy-mode e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }", "bind -Tcopy-mode f { command-prompt -P -1p'(jump forward)' { send -X jump-forward -- '%%' } }", "bind -Tcopy-mode g { command-prompt -P -p'(goto line)' { send -X goto-line -- '%%' } }", "bind -Tcopy-mode n { send -X search-again }", @@ -687,6 +690,8 @@ key_bindings_init(void) "bind -Tcopy-mode-vi K { send -X scroll-up }", "bind -Tcopy-mode-vi L { send -X bottom-line }", "bind -Tcopy-mode-vi M { send -X middle-line }", + "bind -Tcopy-mode-vi M-e { if -F '#{selection_present}' { send -X open-selection } { send -X open-output } }", + "bind -Tcopy-mode-vi M-o { send -X select-output }", "bind -Tcopy-mode-vi N { send -X search-reverse }", "bind -Tcopy-mode-vi P { send -X toggle-position }", "bind -Tcopy-mode-vi T { command-prompt -P -1p'(jump to backward)' { send -X jump-to-backward -- '%%' } }", diff --git a/regress/output-commands.sh b/regress/output-commands.sh new file mode 100755 index 000000000..5b67efb6a --- /dev/null +++ b/regress/output-commands.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +PATH=/bin:/usr/bin +TERM=screen + +[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) +TMUX="$TEST_TMUX -L${TEST_SOCKET:-testO$$} -f/dev/null" +OUT=/tmp/tmux-output-commands-$$ +EXPECTED=$(printf 'one\ntwo') + +cleanup() +{ + $TMUX kill-server 2>/dev/null + rm -f "$OUT" +} +trap cleanup EXIT HUP INT TERM + +$TMUX kill-server 2>/dev/null +$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1 +sleep 1 + +$TMUX copy-mode || exit 1 +$TMUX send-keys -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || exit 1 + +$TMUX send-keys -X pipe-output "wc -c >$OUT" || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 10 ] || exit 1 + +$TMUX send-keys -X copy-pipe-output "wc -c >$OUT" output || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 10 ] || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || exit 1 + +$TMUX send-keys -X select-output || exit 1 +$TMUX send-keys -X copy-selection || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX copy-mode || exit 1 +$TMUX send-keys -X search-backward separator || exit 1 +$TMUX send-keys -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX copy-mode || exit 1 +$TMUX send-keys -X search-backward one || exit 1 +$TMUX send-keys -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :plain || exit 1 +$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1 +all=$($TMUX show-buffer) +case "$all" in +*alpha*beta*) ;; +*) exit 1 ;; +esac +$TMUX send-keys -t :plain.0 -X cancel || exit 1 + +$TMUX set-buffer -b keep unchanged || exit 1 +$TMUX copy-mode -t :plain || exit 1 +$TMUX send-keys -t :plain.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1 +$TMUX send-keys -t :plain.0 -X cancel || exit 1 + +$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :empty || exit 1 +$TMUX send-keys -t :empty.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = one ] || exit 1 +$TMUX send-keys -t :empty.0 -X cancel || exit 1 + +$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\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 :prompt || exit 1 +$TMUX send-keys -t :prompt.0 -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = one ] || exit 1 +$TMUX send-keys -t :prompt.0 -X cancel || exit 1 + +$TMUX copy-mode -t :prompt || exit 1 +$TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1 +$TMUX send-keys -t :prompt.0 C-o || exit 1 +[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1 +$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1 +[ "$($TMUX show-buffer)" = one ] || exit 1 +$TMUX send-keys -t :prompt.0 -X cancel || exit 1 + +$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :same || exit 1 +$TMUX send-keys -t :same.0 -X search-backward one || exit 1 +$TMUX send-keys -t :same.0 -X select-output || exit 1 +$TMUX send-keys -t :same.0 -X copy-selection || exit 1 +[ "$($TMUX show-buffer)" = one ] || exit 1 +$TMUX send-keys -t :same.0 -X cancel || exit 1 + +$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1 +sleep 1 +$TMUX copy-mode -t :clear || exit 1 +$TMUX send-keys -t :clear.0 C-o || 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 + +exit 0 diff --git a/tmux.1 b/tmux.1 index 56790f9f0..8e10a34e6 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2006,6 +2006,25 @@ Same as .Ic copy\-pipe but also exit copy mode. .It Xo +.Ic copy\-pipe\-output +.Op Fl aCP +.Op Ar command +.Op Ar prefix +.Xc +Copy the current OSC 133 command output, pipe it to +.Ar command , +and clear the selection. +.Ar prefix +is used to name the new paste buffer. +.It Xo +.Ic copy\-output +.Op Fl aCP +.Op Ar prefix +.Xc +Copy the current OSC 133 command output and clear the selection. +.Ar prefix +is used to name the new paste buffer. +.It Xo .Ic copy\-selection .Op Fl CP .Op Ar prefix @@ -2224,6 +2243,32 @@ but treat all non-whitespace characters as part of a word. .Xc Switch at which end of the selection the cursor sits. .It Xo +.Ic open\-output +.Op Fl a +.Op Ar command +.Xc +Open the current OSC 133 command output in an editor. +If +.Ar command +is given, it is used as the editor command; otherwise, the +.Ic editor +option is used. +.It Xo +.Ic open\-selection +.Op Ar command +.Xc +Open the current selection in an editor. +If +.Ar command +is given, it is used as the editor command; otherwise, the +.Ic editor +option is used. +In emacs mode, +.Ql e , +and in vi mode, +.Ql M\-e , +opens the selection if one is present, otherwise the command output. +.It Xo .Ic page\-down (vi: C\-f) (emacs: PageDown) @@ -2264,6 +2309,14 @@ Same as .Ic pipe but also exit copy mode. .It Xo +.Ic pipe\-output +.Op Fl a +.Op Ar command +.Xc +Pipe the current OSC 133 command output to +.Ar command +and clear the selection. +.It Xo .Ic previous\-matching\-bracket (emacs: M\-C\-b) .Xc @@ -2453,6 +2506,15 @@ Search forward for the specified plain text. Repeat the last search in the reverse direction (forward becomes backward and backward becomes forward). .It Xo +.Ic select\-output +.Op Fl a +.Xc +Select the current OSC 133 command output. +The default emacs binding is +.Ql C\-o +and the default vi binding is +.Ql M\-o . +.It Xo .Ic select\-line (vi: V) .Xc @@ -2496,6 +2558,20 @@ Toggle the visibility of the position indicator in the top right. Move to the top line. .El .Pp +The output commands use OSC 133 output text from the +.Ql C +marker up to its matching +.Ql D +marker. +If there is no +.Ql D +marker, the output ends at the end of the copy-mode buffer. +At the following prompt or below it, they use the most recent nonempty output. +With +.Fl a , +they use the entire copy-mode buffer instead. +If no output range can be found, they do nothing. +.Pp The search commands come in several varieties: .Ql search\-forward and diff --git a/window-copy.c b/window-copy.c index c5529a3ff..15d288be6 100644 --- a/window-copy.c +++ b/window-copy.c @@ -117,9 +117,20 @@ static int window_copy_set_selection(struct window_mode_entry *, int, int); static int window_copy_update_selection(struct window_mode_entry *, int, int); static void window_copy_synchronize_cursor(struct window_mode_entry *, int); +static int window_copy_find_output_range(struct window_mode_entry *, + u_int *, u_int *, u_int *, u_int *); +static int window_copy_find_previous_output_range(struct window_mode_entry *, + u_int, u_int, u_int *, u_int *, u_int *, u_int *); +static void window_copy_output_end(struct screen *, u_int *, u_int *); static void *window_copy_get_selection(struct window_mode_entry *, size_t *); +static void *window_copy_get_output(struct window_mode_entry *, size_t *, + int); +static void *window_copy_get_grid_range(struct window_mode_entry *, u_int, + u_int, u_int, u_int, size_t *); static void window_copy_copy_buffer(struct window_mode_entry *, const char *, void *, size_t, int, int); +static void window_copy_pipe_buffer(struct session *, const char *, void *, + size_t); static void window_copy_pipe(struct window_mode_entry *, struct session *, const char *); static void window_copy_copy_pipe(struct window_mode_entry *, @@ -1587,6 +1598,80 @@ window_copy_cmd_copy_selection_and_cancel(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); } +static enum window_copy_cmd_action +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 grid_reader gr; + void *buf; + size_t len; + u_int sx, sy, ex, ey, total; + int all = args_has(cs->wargs, 'a'); + + if (all) { + sx = sy = 0; + total = data->backing->grid->hsize + data->backing->grid->sy; + ey = total - 1; + ex = grid_get_line(data->backing->grid, 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); + if (buf == NULL) + return (WINDOW_COPY_CMD_NOTHING); + free(buf); + + window_copy_clear_selection(wme); + data->lineflag = LINE_SEL_NONE; + data->rectflag = 0; + data->selflag = SEL_CHAR; + window_copy_scroll_to(wme, sx, sy, 1); + window_copy_start_selection(wme); + if (options_get_number(wme->wp->window->options, "mode-keys") == + MODEKEY_VI) { + grid_reader_start(&gr, data->backing->grid, ex, ey); + grid_reader_cursor_left(&gr, 1); + grid_reader_get_cursor(&gr, &ex, &ey); + } + window_copy_scroll_to(wme, ex, ey, 1); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_output_no_clear(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + void *buf; + char *prefix = NULL; + const char *arg0 = args_string(cs->wargs, 0); + size_t len; + int set_paste = !args_has(cs->wargs, 'P'); + int set_clip = !args_has(cs->wargs, 'C'); + + if (arg0 != NULL) + prefix = format_single(NULL, arg0, c, s, wl, wp); + if (s != NULL) { + buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a')); + if (buf != NULL) + window_copy_copy_buffer(wme, prefix, buf, len, set_paste, + set_clip); + } + free(prefix); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_output(struct window_copy_cmd_state *cs) +{ + window_copy_cmd_copy_output_no_clear(cs); + window_copy_clear_selection(cs->wme); + return (WINDOW_COPY_CMD_REDRAW); +} + static enum window_copy_cmd_action window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs) { @@ -2742,6 +2827,133 @@ window_copy_cmd_pipe_and_cancel(struct window_copy_cmd_state *cs) return (WINDOW_COPY_CMD_CANCEL); } +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_output_no_clear(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + void *buf; + char *command = NULL, *prefix = NULL; + const char *arg0 = args_string(cs->wargs, 0); + const char *arg1 = args_string(cs->wargs, 1); + size_t len; + int set_paste = !args_has(cs->wargs, 'P'); + int set_clip = !args_has(cs->wargs, 'C'); + + if (arg1 != NULL) + prefix = format_single(NULL, arg1, c, s, wl, wp); + if (s != NULL && arg0 != NULL && *arg0 != '\0') + command = format_single(NULL, arg0, c, s, wl, wp); + if (s != NULL) { + buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a')); + if (buf != NULL) { + window_copy_pipe_buffer(s, command, buf, len); + window_copy_copy_buffer(wme, prefix, buf, len, set_paste, + set_clip); + } + } + free(command); + free(prefix); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_copy_pipe_output(struct window_copy_cmd_state *cs) +{ + window_copy_cmd_copy_pipe_output_no_clear(cs); + window_copy_clear_selection(cs->wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe_output_no_clear(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct client *c = cs->c; + struct session *s = cs->s; + struct winlink *wl = cs->wl; + struct window_pane *wp = wme->wp; + void *buf; + char *command = NULL; + const char *arg0 = args_string(cs->wargs, 0); + size_t len; + + if (s != NULL && arg0 != NULL && *arg0 != '\0') + command = format_single(NULL, arg0, c, s, wl, wp); + if (s != NULL) { + buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a')); + if (buf != NULL) { + window_copy_pipe_buffer(s, command, buf, len); + free(buf); + } + } + free(command); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_pipe_output(struct window_copy_cmd_state *cs) +{ + window_copy_cmd_pipe_output_no_clear(cs); + window_copy_clear_selection(cs->wme); + return (WINDOW_COPY_CMD_REDRAW); +} + +static void +window_copy_open_done(char *buf, __unused size_t len, __unused void *arg) +{ + free(buf); +} + +static enum window_copy_cmd_action +window_copy_cmd_open_selection(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + struct window_copy_mode_data *data = wme->data; + void *buf; + char *editor = NULL; + const char *arg0 = args_string(cs->wargs, 0); + size_t len; + + if (cs->c == NULL || + (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE)) + return (WINDOW_COPY_CMD_NOTHING); + if (arg0 != NULL && *arg0 != '\0') + editor = format_single(NULL, arg0, cs->c, cs->s, cs->wl, wme->wp); + buf = window_copy_get_selection(wme, &len); + if (buf != NULL) { + spawn_editor(cs->c, buf, len, editor, window_copy_open_done, NULL); + free(buf); + } + free(editor); + return (WINDOW_COPY_CMD_NOTHING); +} + +static enum window_copy_cmd_action +window_copy_cmd_open_output(struct window_copy_cmd_state *cs) +{ + struct window_mode_entry *wme = cs->wme; + void *buf; + char *editor = NULL; + const char *arg0 = args_string(cs->wargs, 0); + size_t len; + + if (cs->c == NULL) + return (WINDOW_COPY_CMD_NOTHING); + if (arg0 != NULL && *arg0 != '\0') + editor = format_single(NULL, arg0, cs->c, cs->s, cs->wl, wme->wp); + buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a')); + if (buf != NULL) { + spawn_editor(cs->c, buf, len, editor, window_copy_open_done, NULL); + free(buf); + } + free(editor); + return (WINDOW_COPY_CMD_NOTHING); +} + static enum window_copy_cmd_action window_copy_cmd_goto_line(struct window_copy_cmd_state *cs) { @@ -3404,6 +3616,18 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_copy_pipe_and_cancel }, + { .command = "copy-pipe-output", + .args = { "aCP", 0, 2, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_pipe_output + }, + { .command = "copy-output", + .args = { "aCP", 0, 1, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_copy_output + }, { .command = "copy-selection-no-clear", .args = { "CP", 0, 1, NULL }, .flags = 0, @@ -3626,6 +3850,18 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_EMACS_ONLY, .f = window_copy_cmd_other_end }, + { .command = "open-output", + .args = { "a", 0, 1, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_open_output + }, + { .command = "open-selection", + .args = { "", 0, 1, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_NEVER, + .f = window_copy_cmd_open_selection + }, { .command = "page-down", .args = { "", 0, 0, NULL }, .flags = WINDOW_COPY_CMD_FLAG_READONLY, @@ -3662,6 +3898,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_pipe_and_cancel }, + { .command = "pipe-output", + .args = { "a", 0, 1, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_pipe_output + }, { .command = "previous-matching-bracket", .args = { "", 0, 0, NULL }, .flags = WINDOW_COPY_CMD_FLAG_READONLY, @@ -3839,6 +4081,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_search_reverse }, + { .command = "select-output", + .args = { "a", 0, 0, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_select_output + }, { .command = "select-line", .args = { "", 0, 0, NULL }, .flags = 0, @@ -5970,6 +6218,185 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw, return (1); } +/* Find the output range for the command at the cursor. */ +static int +window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx, + u_int *sy, u_int *ex, u_int *ey) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + struct grid_line *gl; + u_int cursor_x, cursor_y, prompt_x = 0; + u_int prompt_y = UINT_MAX, y, total; + int found_start = 0, found_end = 0; + + cursor_x = data->cx; + cursor_y = screen_hsize(data->backing) + data->cy - data->oy; + log_debug("%s: cursor at %u,%u", __func__, cursor_x, cursor_y); + + total = gd->hsize + gd->sy; + for (y = 0; y < total; y++) { + gl = grid_get_line(gd, y); + if (~gl->flags & GRID_LINE_START_PROMPT) + continue; + if (y > cursor_y || + (y == cursor_y && gl->osc133_data.prompt_col > cursor_x)) + break; + prompt_y = y; + prompt_x = gl->osc133_data.prompt_col; + } + if (prompt_y == UINT_MAX) { + log_debug("%s: no osc133 prompt before cursor", __func__); + return (0); + } + log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y); + + for (y = prompt_y; y < total; y++) { + gl = grid_get_line(gd, y); + /* An output may end on the next prompt's line. */ + if (found_start && gl->flags & GRID_LINE_END_OUTPUT && + (y != prompt_y || gl->osc133_data.out_end_col >= prompt_x)) { + *ex = gl->osc133_data.out_end_col; + *ey = y; + found_end = 1; + break; + } + if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT) + break; + if (gl->flags & GRID_LINE_START_OUTPUT && + (y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) { + *sx = gl->osc133_data.out_start_col; + *sy = y; + found_start = 1; + } + } + if (!found_start) { + log_debug("%s: no output after prompt", __func__); + return (window_copy_find_previous_output_range(wme, cursor_x, + cursor_y, sx, sy, ex, ey)); + } + if (!found_end) { + if (y != total) { + log_debug("%s: output interrupted by next prompt", __func__); + return (0); + } + window_copy_output_end(data->backing, ex, ey); + } + log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy, + *ex, *ey); + return (1); +} + +/* Find the most recent complete output at or before a position. */ +static int +window_copy_find_previous_output_range(struct window_mode_entry *wme, + u_int cursor_x, u_int cursor_y, u_int *sx, u_int *sy, u_int *ex, + u_int *ey) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + struct grid_line *gl; + void *buf; + u_int start_x, start_y, end_x, end_y; + u_int y, total; + size_t len; + int found = 0, have_prompt = 0; + int pending = 0; + + total = gd->hsize + gd->sy; + for (y = 0; y < total && y <= cursor_y; y++) { + gl = grid_get_line(gd, y); + if (gl->flags & GRID_LINE_START_OUTPUT && + (y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) { + start_x = gl->osc133_data.out_start_col; + start_y = y; + pending = 1; + } + /* The output may have cleared its C marker from the screen. */ + if (!pending && !have_prompt && + gl->flags & GRID_LINE_END_OUTPUT && + (~gl->flags & GRID_LINE_START_PROMPT || + gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) { + start_x = start_y = 0; + pending = 1; + } + if (pending && gl->flags & GRID_LINE_END_OUTPUT && + (y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) { + end_x = gl->osc133_data.out_end_col; + end_y = y; + buf = window_copy_get_grid_range(wme, start_x, start_y, + end_x, end_y, &len); + if (buf != NULL) { + free(buf); + *sx = start_x; + *sy = start_y; + *ex = end_x; + *ey = end_y; + found = 1; + } + pending = 0; + } + if (gl->flags & GRID_LINE_START_PROMPT) { + pending = 0; + have_prompt = 1; + } + } + return (found); +} + +static void +window_copy_output_end(struct screen *s, u_int *x, u_int *y) +{ + *x = s->cx; + *y = screen_hsize(s) + s->cy; +} + +static void * +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 = data->backing->grid; + u_int sx, sy, ex, ey, total; + + total = gd->hsize + gd->sy; + if (all) { + sx = sy = 0; + ey = total - 1; + ex = grid_get_line(gd, ey)->cellused; + } else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) { + *len = 0; + return (NULL); + } + return (window_copy_get_grid_range(wme, sx, sy, ex, ey, len)); +} + +static void * +window_copy_get_grid_range(struct window_mode_entry *wme, u_int sx, u_int sy, + u_int ex, u_int ey, size_t *len) +{ + struct window_copy_mode_data *data = wme->data; + struct grid *gd = data->backing->grid; + char *buf; + size_t off; + u_int i; + + buf = xmalloc(1); + off = 0; + for (i = sy; i <= ey; i++) { + window_copy_copy_line(wme, &buf, &off, i, i == sy ? sx : 0, + i == ey ? ex : gd->sx); + } + if (off != 0 && buf[off - 1] == '\n') + off--; + if (off == 0) { + free(buf); + *len = 0; + return (NULL); + } + *len = off; + return (buf); +} + static void * window_copy_get_selection(struct window_mode_entry *wme, size_t *len) { @@ -6102,8 +6529,7 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix, if (set_clip && options_get_number(global_options, "set-clipboard") != 0) { - if (window_copy_line_numbers_active(wme) && - (wp->flags & PANE_REDRAW)) { + if (wp->flags & PANE_REDRAW) { /* Clear PANE_REDRAW so clipboard write not skipped. */ redraw = PANE_REDRAW; wp->flags &= ~PANE_REDRAW; @@ -6126,18 +6552,28 @@ window_copy_pipe_run(struct window_mode_entry *wme, struct session *s, const char *cmd, size_t *len) { void *buf; - struct job *job; buf = window_copy_get_selection(wme, len); + window_copy_pipe_buffer(s, cmd, buf, *len); + return (buf); +} + +static void +window_copy_pipe_buffer(struct session *s, const char *cmd, void *buf, + size_t len) +{ + struct job *job; + + if (buf == NULL) + return; if (cmd == NULL || *cmd == '\0') cmd = options_get_string(global_options, "copy-command"); if (cmd != NULL && *cmd != '\0') { job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL, NULL, JOB_NOWAIT, -1, -1); if (job != NULL) - bufferevent_write(job_get_event(job), buf, *len); + bufferevent_write(job_get_event(job), buf, len); } - return (buf); } static void