- Added top-level commands:

- select-output
    - copy-output
    - copy-pipe-output
    - pipe-output
    - open-output
    - open-selection

- Added -a support to operate on the entire pane/copy-mode buffer, including panes without OSC 133 markers.
- Output extraction now supports unfinished commands with C but no D, ending at the current pane cursor.
- Top-level commands use the most recent OSC 133 output when outside copy mode.
- select-output enters copy mode and selects the output; other top-level operations use a temporary copy-mode snapshot.
- Added copy-mode bindings to open the selection when present, otherwise the current command output:
    - emacs: e
    - vi: M-e

- Added copy-mode bidings to select the output of command cursor is on:
    - emacs copy mode: C-o → select-output
    - vi copy mode: M-o → select-output

- Updated command registration, build sources, and tmux.1 documentation.
- Added/extended regress/output-commands.sh.
- Verified with make, regress/collapse-output.sh, and regress/output-commands.sh.
This commit is contained in:
Michael Grant
2026-09-05 18:43:15 +01:00
parent 6294be4986
commit 081b79fd6e
11 changed files with 912 additions and 12 deletions

View File

@@ -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 \

113
cmd-output.c Normal file
View File

@@ -0,0 +1,113 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
*
* 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 <sys/types.h>
#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);
}

12
cmd.c
View File

@@ -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,

View File

@@ -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 }",

106
regress/output-commands.sh Normal file
View File

@@ -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

View File

@@ -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);

132
tmux.1
View File

@@ -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

5
tmux.h
View File

@@ -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 *);

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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);