Compare commits

..

3 Commits

Author SHA1 Message Date
Michael Grant
a8b05dd7d7 Add pipe-selection copy mode command 2026-09-14 06:25:40 +01:00
Michael Grant
71865a58d1 Add OSC 133 copy mode output commands 2026-09-14 05:35:34 +01:00
Michael Grant
9837766857 Allow callers to override editor command 2026-09-14 05:35:00 +01:00
9 changed files with 663 additions and 27 deletions

View File

@@ -115,15 +115,11 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
/*
* -c is intended to be the client where the message should be
* displayed if -p is not given, but it makes sense to use it for the
* client formats too. If it was given explicitly, use it even when it
* is not in the target session, since the formats should describe the
* client which was asked for; the session, window and pane formats
* still come from the target. Otherwise it is just the current client,
* so only use it if it matches the session and fall back to the best
* client for the session if not.
* displayed if -p is not given. But it makes sense to use it for the
* formats too, assuming it matches the session. If it doesn't, use the
* best client for the session.
*/
if (tc != NULL && (args_has(args, 'c') || tc->session == s))
if (tc != NULL && tc->session == s)
c = tc;
else if (s != NULL)
c = cmd_find_best_client(s);

View File

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

115
regress/output-commands.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/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 select-output || exit 1
$TMUX send-keys -X pipe-selection "wc -c >$OUT" || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || 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

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

91
tmux.1
View File

@@ -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,21 @@ 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 pipe\-selection
.Op Ar command
.Xc
Pipe the selected text to
.Ar command
and clear the selection.
.It Xo
.Ic previous\-matching\-bracket
(emacs: M\-C\-b)
.Xc
@@ -2453,6 +2513,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 +2565,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
@@ -8328,17 +8411,11 @@ Otherwise, the format of
.Ar message
is described in the
.Sx FORMATS
section; session, window and pane information is taken from
section; information is taken from
.Ar target\-pane
if
.Fl t
is given, otherwise the active pane.
If
.Fl c
is given, the
.Ql #{client_*}
variables are taken from
.Ar target\-client .
.Pp
If
.Fl j

2
tmux.h
View File

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

@@ -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,18 @@ 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 = "pipe-selection",
.args = { "", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe
},
{ .command = "previous-matching-bracket",
.args = { "", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
@@ -3839,6 +4087,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 +6224,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 +6535,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 +6558,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

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