diff --git a/Makefile.am b/Makefile.am index cbdb4e863..acc9675f0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -98,6 +98,7 @@ dist_tmux_SOURCES = \ cmd-command-prompt.c \ cmd-confirm-before.c \ cmd-copy-mode.c \ + cmd-output.c \ cmd-detach-client.c \ cmd-display-menu.c \ cmd-display-message.c \ diff --git a/cmd-output.c b/cmd-output.c new file mode 100644 index 000000000..4f2338604 --- /dev/null +++ b/cmd-output.c @@ -0,0 +1,113 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2007 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include "tmux.h" + +static enum cmd_retval cmd_output_exec(struct cmd *, struct cmdq_item *); + +const struct cmd_entry cmd_select_output_entry = { + .name = "select-output", + .alias = NULL, + + .args = { "at:", 0, 0, NULL }, + .usage = "[-a] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +const struct cmd_entry cmd_copy_output_entry = { + .name = "copy-output", + .alias = NULL, + + .args = { "aCPt:", 0, 1, NULL }, + .usage = "[-aCP] " CMD_TARGET_PANE_USAGE " [prefix]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +const struct cmd_entry cmd_copy_pipe_output_entry = { + .name = "copy-pipe-output", + .alias = NULL, + + .args = { "aCPt:", 0, 2, NULL }, + .usage = "[-aCP] " CMD_TARGET_PANE_USAGE " [command] [prefix]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +const struct cmd_entry cmd_pipe_output_entry = { + .name = "pipe-output", + .alias = NULL, + + .args = { "at:", 0, 1, NULL }, + .usage = "[-a] " CMD_TARGET_PANE_USAGE " [command]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +const struct cmd_entry cmd_open_output_entry = { + .name = "open-output", + .alias = NULL, + + .args = { "at:", 0, 1, NULL }, + .usage = "[-a] " CMD_TARGET_PANE_USAGE " [command]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +const struct cmd_entry cmd_open_selection_entry = { + .name = "open-selection", + .alias = NULL, + + .args = { "t:", 0, 1, NULL }, + .usage = CMD_TARGET_PANE_USAGE " [command]", + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = CMD_AFTERHOOK, + .exec = cmd_output_exec +}; + +static enum cmd_retval +cmd_output_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct client *c = cmdq_get_client(item); + + window_copy_output(target->wp, c, target->s, target->wl, item, + cmd_get_entry(self)->name, args); + return (CMD_RETURN_NORMAL); +} diff --git a/cmd.c b/cmd.c index 64fc0bbc5..0530d2e0c 100644 --- a/cmd.c +++ b/cmd.c @@ -39,6 +39,8 @@ extern const struct cmd_entry cmd_clear_prompt_history_entry; extern const struct cmd_entry cmd_clock_mode_entry; extern const struct cmd_entry cmd_command_prompt_entry; extern const struct cmd_entry cmd_confirm_before_entry; +extern const struct cmd_entry cmd_copy_output_entry; +extern const struct cmd_entry cmd_copy_pipe_output_entry; extern const struct cmd_entry cmd_copy_mode_entry; extern const struct cmd_entry cmd_customize_mode_entry; extern const struct cmd_entry cmd_delete_buffer_entry; @@ -76,8 +78,11 @@ extern const struct cmd_entry cmd_new_session_entry; extern const struct cmd_entry cmd_new_window_entry; extern const struct cmd_entry cmd_next_layout_entry; extern const struct cmd_entry cmd_next_window_entry; +extern const struct cmd_entry cmd_open_output_entry; +extern const struct cmd_entry cmd_open_selection_entry; extern const struct cmd_entry cmd_paste_buffer_entry; extern const struct cmd_entry cmd_pipe_pane_entry; +extern const struct cmd_entry cmd_pipe_output_entry; extern const struct cmd_entry cmd_previous_layout_entry; extern const struct cmd_entry cmd_previous_window_entry; extern const struct cmd_entry cmd_refresh_client_entry; @@ -91,6 +96,7 @@ extern const struct cmd_entry cmd_rotate_window_entry; extern const struct cmd_entry cmd_run_shell_entry; extern const struct cmd_entry cmd_save_buffer_entry; extern const struct cmd_entry cmd_select_layout_entry; +extern const struct cmd_entry cmd_select_output_entry; extern const struct cmd_entry cmd_select_pane_entry; extern const struct cmd_entry cmd_select_window_entry; extern const struct cmd_entry cmd_send_keys_entry; @@ -134,6 +140,8 @@ const struct cmd_entry *cmd_table[] = { &cmd_command_prompt_entry, &cmd_confirm_before_entry, &cmd_copy_mode_entry, + &cmd_copy_output_entry, + &cmd_copy_pipe_output_entry, &cmd_customize_mode_entry, &cmd_delete_buffer_entry, &cmd_detach_client_entry, @@ -170,8 +178,11 @@ const struct cmd_entry *cmd_table[] = { &cmd_new_window_entry, &cmd_next_layout_entry, &cmd_next_window_entry, + &cmd_open_output_entry, + &cmd_open_selection_entry, &cmd_paste_buffer_entry, &cmd_pipe_pane_entry, + &cmd_pipe_output_entry, &cmd_previous_layout_entry, &cmd_previous_window_entry, &cmd_refresh_client_entry, @@ -185,6 +196,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_run_shell_entry, &cmd_save_buffer_entry, &cmd_select_layout_entry, + &cmd_select_output_entry, &cmd_select_pane_entry, &cmd_select_window_entry, &cmd_send_keys_entry, diff --git a/key-bindings.c b/key-bindings.c index 4b40e3517..fad8e0612 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -54,6 +54,7 @@ " ''" \ " '#{?#{==:#{pane_mode},copy-mode},#{?copy_line_numbers,Line Numbers Off,Line Numbers On},}' 'L' {send -X line-numbers-toggle}" \ " '#{?#{==:#{pane_mode},copy-mode},#{?copy_fold_view,Fold View Off,Fold View On},}' 'O' {send -X fold-view-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}" \ @@ -576,6 +577,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 -- '%%' } }", @@ -596,6 +598,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 }", @@ -693,6 +696,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 O { send -X fold-view-toggle }", "bind -Tcopy-mode-vi P { send -X toggle-position }", diff --git a/regress/output-commands.sh b/regress/output-commands.sh new file mode 100644 index 000000000..1a351c158 --- /dev/null +++ b/regress/output-commands.sh @@ -0,0 +1,106 @@ +#!/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-output || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || exit 1 + +$TMUX pipe-output "wc -c >$OUT" || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 10 ] || exit 1 + +$TMUX copy-pipe-output "wc -c >$OUT" output || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 10 ] || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || exit 1 + +$TMUX select-output || exit 1 +[ "$($TMUX display-message -p '#{pane_in_mode}')" = 1 ] || 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 -U || exit 1 +$TMUX send-keys -X search-backward one || exit 1 +$TMUX send-keys -X copy-output || exit 1 +copied=$($TMUX show-buffer) +[ "$copied" = "$EXPECTED" ] || exit 1 + +$TMUX send-keys -X pipe-output "wc -l >$OUT" || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 2 ] || exit 1 + +$TMUX send-keys -X copy-pipe-output "wc -l >$OUT" output || exit 1 +sleep 1 +[ "$(cat "$OUT")" = 2 ] || exit 1 +copied=$($TMUX show-buffer) +[ "$copied" = "$EXPECTED" ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX copy-mode -c || exit 1 +$TMUX send-keys -X search-backward echo || exit 1 +$TMUX send-keys -X select-output || exit 1 +$TMUX send-keys -X copy-selection || exit 1 +selected=$($TMUX show-buffer) +[ "$selected" = "$EXPECTED" ] || exit 1 +$TMUX send-keys -X cancel || exit 1 + +$TMUX set-buffer -b keep unchanged || exit 1 +$TMUX copy-mode -U || exit 1 +$TMUX send-keys -X search-backward unfinished || exit 1 +$TMUX send-keys -X copy-output || exit 1 +[ "$($TMUX show-buffer)" = unfinished ] || 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-output -a -t :plain || exit 1 +all=$($TMUX show-buffer) +case "$all" in +*alpha*beta*) ;; +*) exit 1 ;; +esac + +$TMUX set-buffer -b keep unchanged || exit 1 +$TMUX copy-output -t :plain || exit 1 +[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1 + +$TMUX select-output -t :plain || exit 1 +[ "$($TMUX display-message -p -t :plain.0 '#{pane_in_mode}')" = 0 ] || exit 1 +$TMUX select-output -a -t :plain || exit 1 +[ "$($TMUX display-message -p -t :plain.0 '#{pane_in_mode}')" = 1 ] || exit 1 +$TMUX send-keys -t :plain.0 -X copy-selection || 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 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 + +exit 0 diff --git a/spawn.c b/spawn.c index 361d207e5..8df5e63a6 100644 --- a/spawn.c +++ b/spawn.c @@ -701,7 +701,7 @@ spawn_editor_finish(struct window_pane *wp) struct spawn_editor_state * spawn_editor(struct client *c, const char *buf, size_t len, - spawn_finish_edit_cb cb, void *arg) + const char *editor, spawn_finish_edit_cb cb, void *arg) { struct spawn_editor_state *es; struct spawn_context sc = { 0 }; @@ -715,13 +715,13 @@ spawn_editor(struct client *c, const char *buf, size_t len, FILE *f; char *cmd, *cause = NULL; char path[] = _PATH_TMP "tmux.XXXXXXXX"; - const char *editor; int fd; if (w->modal != NULL) return (NULL); - editor = options_get_string(global_options, "editor"); + if (editor == NULL) + editor = options_get_string(global_options, "editor"); fd = mkstemp(path); if (fd == -1) return (NULL); diff --git a/tmux.1 b/tmux.1 index df1ca6e44..d29e407e5 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2004,6 +2004,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 @@ -2243,6 +2262,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) @@ -2283,6 +2328,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 @@ -2477,6 +2530,16 @@ backward becomes forward). .Xc Select the current line. .It Xo +.Ic select\-output +.Op Fl a +.Xc +Select the current OSC 133 command output. +If it is collapsed, it is expanded first. +The default emacs binding is +.Ql C\-o +and the default vi binding is +.Ql M\-o . +.It Xo .Ic select\-word .Xc Select the current word. @@ -2515,6 +2578,12 @@ 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 C marker to the D marker. +If there is no D marker, the output ends at the end of the copy-mode buffer. +With +.Fl a , +they use the entire copy-mode buffer instead. +.Pp The search commands come in several varieties: .Ql search\-forward and @@ -2560,6 +2629,15 @@ Finding the beginning of command output requires the shell to emit an escape sequence (\e033]133;C\e033\e\e) to tell tmux where the output begins. If the shell does not send these escape sequences, these commands do nothing. .Pp +The output commands operate on text from an OSC 133 +.Ql C +marker up to its matching +.Ql D +marker, or the current end of the buffer if there is no +.Ql D +marker. +They do nothing if the cursor is not associated with an output range. +.Pp Copy commands may take an optional buffer prefix argument which is used to generate the buffer name (the default is .Ql buffer @@ -2675,6 +2753,60 @@ bind PageDown copy\-mode \-ed kills the pane when the mode is exited. .El .Pp +The following commands operate on OSC 133 command output. In copy mode, they +use the output on the cursor line; otherwise, they use the most recent output +in the target pane. If an output has a C marker but no D marker, it ends at the +end of the pane buffer. If there is no OSC 133 output, they do nothing unless +.Fl a +is given, which uses the entire buffer. +.Bl -tag -width Ds +.It Xo Ic select\-output +.Op Fl a +.Op Fl t Ar target\-pane +.Xc +Enter copy mode if necessary and select the output. +.It Xo Ic copy\-output +.Op Fl aCP +.Op Fl t Ar target\-pane +.Op Ar prefix +.Xc +Copy the output to a paste buffer. +.It Xo Ic copy\-pipe\-output +.Op Fl aCP +.Op Fl t Ar target\-pane +.Op Ar command +.Op Ar prefix +.Xc +Copy the output to a paste buffer and pipe it to +.Ar command . +.It Xo Ic pipe\-output +.Op Fl a +.Op Fl t Ar target\-pane +.Op Ar command +.Xc +Pipe the output to +.Ar command . +.It Xo Ic open\-output +.Op Fl a +.Op Fl t Ar target\-pane +.Op Ar command +.Xc +Open the 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 Fl t Ar target\-pane +.Op Ar command +.Xc +Open the current copy-mode selection in an editor. If +.Ar command +is given, it is used as the editor command; otherwise the +.Ic editor +option is used. +.El +.Pp A number of preset arrangements of panes are available, these are called layouts. These may be selected with the diff --git a/tmux.h b/tmux.h index 0db381fd6..99621e1b2 100644 --- a/tmux.h +++ b/tmux.h @@ -4012,6 +4012,9 @@ int window_copy_get_current_offset(struct window_pane *, u_int *, u_int *); char *window_copy_get_hyperlink(struct window_pane *, u_int, u_int); void window_copy_set_line_numbers(struct window_pane *, int); +void window_copy_output(struct window_pane *, struct client *, + struct session *, struct winlink *, struct cmdq_item *, + const char *, struct args *); /* window-customize.c */ extern const struct window_mode window_customize_mode; @@ -4223,7 +4226,7 @@ struct winlink *spawn_window(struct spawn_context *, char **); struct window_pane *spawn_pane(struct spawn_context *, char **); typedef void (*spawn_finish_edit_cb)(char *, size_t, void *); struct spawn_editor_state *spawn_editor(struct client *, const char *, size_t, - spawn_finish_edit_cb, void *); + const char *, spawn_finish_edit_cb, void *); void spawn_cancel_editor(struct spawn_editor_state *); pid_t spawn_get_editor_pid(struct spawn_editor_state *); void spawn_editor_finish(struct window_pane *); diff --git a/window-buffer.c b/window-buffer.c index bc25de66e..5ac906c7f 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -606,7 +606,8 @@ window_buffer_start_edit(struct window_buffer_modedata *data, ed->name = xstrdup(paste_buffer_name(pb)); ed->pb = pb; - ed->editor = spawn_editor(c, buf, len, window_buffer_edit_close_cb, ed); + ed->editor = spawn_editor(c, buf, len, NULL, + window_buffer_edit_close_cb, ed); if (ed->editor == NULL) window_buffer_finish_edit(ed); else { diff --git a/window-copy.c b/window-copy.c index 598426d02..46acdecfb 100644 --- a/window-copy.c +++ b/window-copy.c @@ -86,8 +86,15 @@ static int window_copy_set_fold(struct window_mode_entry *, int, int, u_int, int); static int window_copy_find_fold(struct window_mode_entry *, u_int, u_int *, u_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 screen *, u_int *, + u_int *, u_int *, u_int *); +static void window_copy_output_end(struct screen *, u_int *, u_int *); static void window_copy_cursor_source(struct window_mode_entry *, u_int *, u_int *); +static int window_copy_source_position(struct window_mode_entry *, u_int, + u_int, u_int *, u_int *); static int window_copy_restore_cursor(struct window_mode_entry *, u_int, u_int); static int window_copy_restore_command_end(struct window_mode_entry *, @@ -138,8 +145,14 @@ static int window_copy_update_selection(struct window_mode_entry *, int, int); static void window_copy_synchronize_cursor(struct window_mode_entry *, 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 grid *, 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 *, @@ -151,6 +164,8 @@ static void window_copy_append_selection(struct window_mode_entry *); static void window_copy_clear_selection(struct window_mode_entry *); static void window_copy_copy_line(struct window_mode_entry *, char **, size_t *, u_int, u_int, u_int); +static void window_copy_copy_grid_line(struct grid *, char **, size_t *, + u_int, u_int, u_int); static int window_copy_in_set(struct window_mode_entry *, u_int, u_int, const char *); static u_int window_copy_find_length(struct window_mode_entry *, u_int); @@ -335,6 +350,7 @@ struct window_copy_mode_data { int hide_position; /* hide position marker */ int line_numbers; /* 0 off, 1 from option, 2 absolute */ int fold_view; + int output_previous; u_int output_status_width; enum { @@ -2119,6 +2135,94 @@ 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, bsx, bsy, bex, bey, total; + int all = args_has(cs->wargs, 'a'); + + if (all) { + sx = sy = 0; + total = data->source->grid->hsize + data->source->grid->sy; + ey = total - 1; + ex = grid_get_line(data->source->grid, ey)->cellused; + } else if (data->output_previous) { + if (!window_copy_find_previous_output_range(data->source, &sx, &sy, + &ex, &ey)) + return (WINDOW_COPY_CMD_NOTHING); + } 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); + + if (all && data->fold_view) { + 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); + if (!window_copy_source_position(wme, sx, sy, &bsx, &bsy) || + !window_copy_source_position(wme, ex, ey, &bex, &bey)) + return (WINDOW_COPY_CMD_NOTHING); + + window_copy_clear_selection(wme); + data->lineflag = LINE_SEL_NONE; + data->rectflag = 0; + data->selflag = SEL_CHAR; + window_copy_scroll_to(wme, bsx, bsy, 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, bex, bey); + grid_reader_cursor_left(&gr, 1); + grid_reader_get_cursor(&gr, &bex, &bey); + } + window_copy_scroll_to(wme, bex, bey, 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) { @@ -3274,6 +3378,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) { @@ -3902,6 +4133,109 @@ window_copy_find_fold(struct window_mode_entry *wme, u_int target, return (0); } +/* 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; + 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; + + if (data->source == NULL) + return (0); + window_copy_cursor_source(wme, &cursor_x, &cursor_y); + + gd = data->source->grid; + 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) + return (0); + + for (y = prompt_y; y < total; y++) { + gl = grid_get_line(gd, y); + 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 && 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 (!found_start) + return (0); + if (!found_end) { + if (y != total) + return (0); + window_copy_output_end(data->source, ex, ey); + } + if (cursor_y > *ey || (cursor_y == *ey && + (found_end ? cursor_x >= *ex : cursor_x > *ex))) + return (0); + return (1); +} + +/* Find the most recent complete output, or one still running at buffer end. */ +static int +window_copy_find_previous_output_range(struct screen *s, u_int *sx, u_int *sy, + u_int *ex, u_int *ey) +{ + struct grid *gd = s->grid; + struct grid_line *gl; + u_int y, total; + int found = 0, pending = 0; + + total = gd->hsize + gd->sy; + for (y = 0; y < total; y++) { + gl = grid_get_line(gd, y); + if (gl->flags & GRID_LINE_START_OUTPUT) { + *sx = gl->osc133_data.out_start_col; + *sy = y; + pending = 1; + } + if (pending && gl->flags & GRID_LINE_END_OUTPUT) { + *ex = gl->osc133_data.out_end_col; + *ey = y; + found = 1; + pending = 0; + } + if (gl->flags & GRID_LINE_START_PROMPT) + pending = 0; + } + if (pending) { + window_copy_output_end(s, ex, ey); + return (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_cursor_source(struct window_mode_entry *wme, u_int *source_x, u_int *source_y) @@ -3919,6 +4253,31 @@ window_copy_cursor_source(struct window_mode_entry *wme, u_int *source_x, *source_y = line->source_line; } +static int +window_copy_source_position(struct window_mode_entry *wme, u_int source_x, + u_int source_y, u_int *backing_x, u_int *backing_y) +{ + struct window_copy_mode_data *data = wme->data; + struct window_copy_line *line; + u_int y, total; + + if (!data->fold_view) { + *backing_x = source_x; + *backing_y = source_y; + return (1); + } + total = screen_hsize(data->backing) + screen_size_y(data->backing); + for (y = 0; y < total; y++) { + line = window_copy_get_line_info(data, y); + if (line == NULL || line->source_line != source_y) + continue; + *backing_x = source_x; + *backing_y = y; + return (1); + } + return (0); +} + static int window_copy_restore_cursor(struct window_mode_entry *wme, u_int source_x, u_int source_y) @@ -4297,6 +4656,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, @@ -4543,6 +4914,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, @@ -4579,6 +4962,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, @@ -4762,6 +5151,12 @@ static const struct { .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, .f = window_copy_cmd_select_line }, + { .command = "select-output", + .args = { "a", 0, 0, NULL }, + .flags = 0, + .clear = WINDOW_COPY_CMD_CLEAR_ALWAYS, + .f = window_copy_cmd_select_output + }, { .command = "select-word", .args = { "", 0, 0, NULL }, .flags = 0, @@ -4900,6 +5295,69 @@ window_copy_command(struct window_mode_entry *wme, struct client *c, } } +void +window_copy_output(struct window_pane *wp, struct client *c, + struct session *s, struct winlink *wl, struct cmdq_item *item, + const char *command, struct args *args) +{ + struct window_mode_entry *wme = TAILQ_FIRST(&wp->modes); + struct window_copy_mode_data *data; + struct window_copy_cmd_state cs; + struct args *mode_args; + enum window_copy_cmd_action action; + int temporary = 0; + + if (wme == NULL || wme->mode != &window_copy_mode) { + if (strcmp(command, "open-selection") == 0) + return; + mode_args = args_create(); + if (window_pane_set_mode(wp, wp, &window_copy_mode, item, NULL, + mode_args)) { + args_free(mode_args); + return; + } + args_free(mode_args); + wme = TAILQ_FIRST(&wp->modes); + temporary = 1; + } + + cs.wme = wme; + cs.args = args; + cs.wargs = args; + cs.m = NULL; + cs.c = c; + cs.s = s; + cs.wl = wl; + + data = wme->data; + data->output_previous = temporary; + if (strcmp(command, "select-output") == 0) + action = window_copy_cmd_select_output(&cs); + else if (strcmp(command, "copy-output") == 0) + action = window_copy_cmd_copy_output(&cs); + else if (strcmp(command, "copy-pipe-output") == 0) + action = window_copy_cmd_copy_pipe_output(&cs); + else if (strcmp(command, "pipe-output") == 0) + action = window_copy_cmd_pipe_output(&cs); + else if (strcmp(command, "open-output") == 0) + action = window_copy_cmd_open_output(&cs); + else if (strcmp(command, "open-selection") == 0) + action = window_copy_cmd_open_selection(&cs); + else { + data->output_previous = 0; + if (temporary) + window_pane_reset_mode(wp); + return; + } + data->output_previous = 0; + + if (temporary && (strcmp(command, "select-output") != 0 || + action != WINDOW_COPY_CMD_REDRAW)) + window_pane_reset_mode(wp); + else if (action == WINDOW_COPY_CMD_REDRAW) + window_copy_redraw_screen(wme); +} + static void window_copy_scroll_to(struct window_mode_entry *wme, u_int px, u_int py, int no_redraw) @@ -7098,6 +7556,58 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len) return (buf); } +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; + u_int sx, sy, ex, ey, total; + + gd = data->source->grid; + total = gd->hsize + gd->sy; + if (all) { + sx = sy = 0; + ey = total - 1; + ex = grid_get_line(gd, ey)->cellused; + } else if (data->output_previous) { + if (!window_copy_find_previous_output_range(data->source, &sx, &sy, + &ex, &ey)) { + *len = 0; + return (NULL); + } + } else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) { + *len = 0; + return (NULL); + } + + return (window_copy_get_grid_range(gd, sx, sy, ex, ey, len)); +} + +static void * +window_copy_get_grid_range(struct grid *gd, u_int sx, u_int sy, u_int ex, + u_int ey, size_t *len) +{ + char *buf; + size_t off; + u_int i; + + buf = xmalloc(1); + off = 0; + for (i = sy; i <= ey; i++) { + window_copy_copy_grid_line(gd, &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_copy_buffer(struct window_mode_entry *wme, const char *prefix, void *buf, size_t len, int set_paste, int set_clip) @@ -7132,18 +7642,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 @@ -7224,7 +7744,14 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, u_int sy, u_int sx, u_int ex) { struct window_copy_mode_data *data = wme->data; - struct grid *gd = data->backing->grid; + + window_copy_copy_grid_line(data->backing->grid, buf, off, sy, sx, ex); +} + +static void +window_copy_copy_grid_line(struct grid *gd, char **buf, size_t *off, u_int sy, + u_int sx, u_int ex) +{ struct grid_cell gc; struct grid_line *gl; struct utf8_data ud; @@ -7246,7 +7773,7 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off, if (wrapped) xx = gl->cellsize; else - xx = window_copy_find_length(wme, sy); + xx = grid_line_length(gd, sy); if (ex > xx) ex = xx; if (sx > xx) diff --git a/window-customize.c b/window-customize.c index 1bf311ff2..7c3b42795 100644 --- a/window-customize.c +++ b/window-customize.c @@ -2104,8 +2104,8 @@ window_customize_start_edit(struct window_customize_modedata *data, buf = "\n"; len = 1; } - ed->editor = spawn_editor(c, buf, len, window_customize_edit_close_cb, - ed); + ed->editor = spawn_editor(c, buf, len, NULL, + window_customize_edit_close_cb, ed); free(value); if (ed->editor == NULL) window_customize_finish_edit(ed);