Add OSC 133 copy mode output commands

This commit is contained in:
Michael Grant
2026-09-14 05:35:34 +01:00
parent 9837766857
commit 71865a58d1
4 changed files with 630 additions and 5 deletions

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

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

@@ -0,0 +1,108 @@
#!/bin/sh
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -L${TEST_SOCKET:-testO$$} -f/dev/null"
OUT=/tmp/tmux-output-commands-$$
EXPECTED=$(printf 'one\ntwo')
cleanup()
{
$TMUX kill-server 2>/dev/null
rm -f "$OUT"
}
trap cleanup EXIT HUP INT TERM
$TMUX kill-server 2>/dev/null
$TMUX new-session -d -x80 -y20 "sh -c 'printf \"\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\ntwo\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007broken\\n\\033]133;C\\007unfinished\"; exec sleep 100'" || exit 1
sleep 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X pipe-output "wc -c >$OUT" || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || exit 1
$TMUX send-keys -X copy-pipe-output "wc -c >$OUT" output || exit 1
sleep 1
[ "$(cat "$OUT")" = 10 ] || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X select-output || exit 1
$TMUX send-keys -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = unfinished ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X search-backward separator || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX copy-mode || exit 1
$TMUX send-keys -X search-backward one || exit 1
$TMUX send-keys -X copy-output || exit 1
[ "$($TMUX show-buffer)" = "$EXPECTED" ] || exit 1
$TMUX send-keys -X cancel || exit 1
$TMUX new-window -d -n plain "printf 'alpha\\nbeta\\n'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X copy-output -a || exit 1
all=$($TMUX show-buffer)
case "$all" in
*alpha*beta*) ;;
*) exit 1 ;;
esac
$TMUX send-keys -t :plain.0 -X cancel || exit 1
$TMUX set-buffer -b keep unchanged || exit 1
$TMUX copy-mode -t :plain || exit 1
$TMUX send-keys -t :plain.0 -X copy-output || exit 1
[ "$($TMUX show-buffer -b keep)" = unchanged ] || exit 1
$TMUX send-keys -t :plain.0 -X cancel || exit 1
$TMUX new-window -d -n empty "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007true\\033]133;C\\033]133;D;0\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :empty || exit 1
$TMUX send-keys -t :empty.0 -X copy-output || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :empty.0 -X cancel || exit 1
$TMUX new-window -d -n prompt "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\033]133;C\\007one\\n\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :prompt || exit 1
$TMUX send-keys -t :prompt.0 -X copy-output || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
$TMUX copy-mode -t :prompt || exit 1
$TMUX send-keys -t :prompt.0 -X -N 100 cursor-down || exit 1
$TMUX send-keys -t :prompt.0 C-o || exit 1
[ "$($TMUX display-message -p -t :prompt.0 '#{selection_present}')" = 1 ] || exit 1
$TMUX send-keys -t :prompt.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :prompt.0 -X cancel || exit 1
$TMUX new-window -d -n same "printf '\\033]133;A\\007p\\$ \\033]133;B\\007echo\\n\\033]133;C\\007one\\033]133;D;0\\007\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :same || exit 1
$TMUX send-keys -t :same.0 -X search-backward one || exit 1
$TMUX send-keys -t :same.0 -X select-output || exit 1
$TMUX send-keys -t :same.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = one ] || exit 1
$TMUX send-keys -t :same.0 -X cancel || exit 1
$TMUX new-window -d -n clear "printf 'old1\\nold2\\nold3\\nold4\\nold5\\nold6\\n\\033]133;A\\007p\\$ \\033]133;B\\007echo 1; clear; ps\\n\\033]133;C\\0071\\n\\033[H\\033[2JPID TTY\\n1 pts/0\\n\\033]133;D;0\\007separator\\n\\033]133;A\\007p\\$ \\033]133;B\\007'; exec sleep 100" || exit 1
sleep 1
$TMUX copy-mode -t :clear || exit 1
$TMUX send-keys -t :clear.0 C-o || exit 1
$TMUX send-keys -t :clear.0 -X copy-selection || exit 1
[ "$($TMUX show-buffer)" = "$(printf 'PID TTY\n1 pts/0')" ] || exit 1
$TMUX send-keys -t :clear.0 -X cancel || exit 1
exit 0

76
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,14 @@ Same as
.Ic pipe
but also exit copy mode.
.It Xo
.Ic pipe\-output
.Op Fl a
.Op Ar command
.Xc
Pipe the current OSC 133 command output to
.Ar command
and clear the selection.
.It Xo
.Ic previous\-matching\-bracket
(emacs: M\-C\-b)
.Xc
@@ -2453,6 +2506,15 @@ Search forward for the specified plain text.
Repeat the last search in the reverse direction (forward becomes backward and
backward becomes forward).
.It Xo
.Ic select\-output
.Op Fl a
.Xc
Select the current OSC 133 command output.
The default emacs binding is
.Ql C\-o
and the default vi binding is
.Ql M\-o .
.It Xo
.Ic select\-line
(vi: V)
.Xc
@@ -2496,6 +2558,20 @@ Toggle the visibility of the position indicator in the top right.
Move to the top line.
.El
.Pp
The output commands use OSC 133 output text from the
.Ql C
marker up to its matching
.Ql D
marker.
If there is no
.Ql D
marker, the output ends at the end of the copy-mode buffer.
At the following prompt or below it, they use the most recent nonempty output.
With
.Fl a ,
they use the entire copy-mode buffer instead.
If no output range can be found, they do nothing.
.Pp
The search commands come in several varieties:
.Ql search\-forward
and

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,12 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe_and_cancel
},
{ .command = "pipe-output",
.args = { "a", 0, 1, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_pipe_output
},
{ .command = "previous-matching-bracket",
.args = { "", 0, 0, NULL },
.flags = WINDOW_COPY_CMD_FLAG_READONLY,
@@ -3839,6 +4081,12 @@ static const struct {
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_search_reverse
},
{ .command = "select-output",
.args = { "a", 0, 0, NULL },
.flags = 0,
.clear = WINDOW_COPY_CMD_CLEAR_ALWAYS,
.f = window_copy_cmd_select_output
},
{ .command = "select-line",
.args = { "", 0, 0, NULL },
.flags = 0,
@@ -5970,6 +6218,185 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
return (1);
}
/* Find the output range for the command at the cursor. */
static int
window_copy_find_output_range(struct window_mode_entry *wme, u_int *sx,
u_int *sy, u_int *ex, u_int *ey)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
u_int cursor_x, cursor_y, prompt_x = 0;
u_int prompt_y = UINT_MAX, y, total;
int found_start = 0, found_end = 0;
cursor_x = data->cx;
cursor_y = screen_hsize(data->backing) + data->cy - data->oy;
log_debug("%s: cursor at %u,%u", __func__, cursor_x, cursor_y);
total = gd->hsize + gd->sy;
for (y = 0; y < total; y++) {
gl = grid_get_line(gd, y);
if (~gl->flags & GRID_LINE_START_PROMPT)
continue;
if (y > cursor_y ||
(y == cursor_y && gl->osc133_data.prompt_col > cursor_x))
break;
prompt_y = y;
prompt_x = gl->osc133_data.prompt_col;
}
if (prompt_y == UINT_MAX) {
log_debug("%s: no osc133 prompt before cursor", __func__);
return (0);
}
log_debug("%s: prompt at %u,%u", __func__, prompt_x, prompt_y);
for (y = prompt_y; y < total; y++) {
gl = grid_get_line(gd, y);
/* An output may end on the next prompt's line. */
if (found_start && gl->flags & GRID_LINE_END_OUTPUT &&
(y != prompt_y || gl->osc133_data.out_end_col >= prompt_x)) {
*ex = gl->osc133_data.out_end_col;
*ey = y;
found_end = 1;
break;
}
if (y != prompt_y && gl->flags & GRID_LINE_START_PROMPT)
break;
if (gl->flags & GRID_LINE_START_OUTPUT &&
(y != prompt_y || gl->osc133_data.out_start_col >= prompt_x)) {
*sx = gl->osc133_data.out_start_col;
*sy = y;
found_start = 1;
}
}
if (!found_start) {
log_debug("%s: no output after prompt", __func__);
return (window_copy_find_previous_output_range(wme, cursor_x,
cursor_y, sx, sy, ex, ey));
}
if (!found_end) {
if (y != total) {
log_debug("%s: output interrupted by next prompt", __func__);
return (0);
}
window_copy_output_end(data->backing, ex, ey);
}
log_debug("%s: output from %u,%u to %u,%u", __func__, *sx, *sy,
*ex, *ey);
return (1);
}
/* Find the most recent complete output at or before a position. */
static int
window_copy_find_previous_output_range(struct window_mode_entry *wme,
u_int cursor_x, u_int cursor_y, u_int *sx, u_int *sy, u_int *ex,
u_int *ey)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl;
void *buf;
u_int start_x, start_y, end_x, end_y;
u_int y, total;
size_t len;
int found = 0, have_prompt = 0;
int pending = 0;
total = gd->hsize + gd->sy;
for (y = 0; y < total && y <= cursor_y; y++) {
gl = grid_get_line(gd, y);
if (gl->flags & GRID_LINE_START_OUTPUT &&
(y != cursor_y || gl->osc133_data.out_start_col <= cursor_x)) {
start_x = gl->osc133_data.out_start_col;
start_y = y;
pending = 1;
}
/* The output may have cleared its C marker from the screen. */
if (!pending && !have_prompt &&
gl->flags & GRID_LINE_END_OUTPUT &&
(~gl->flags & GRID_LINE_START_PROMPT ||
gl->osc133_data.out_end_col <= gl->osc133_data.prompt_col)) {
start_x = start_y = 0;
pending = 1;
}
if (pending && gl->flags & GRID_LINE_END_OUTPUT &&
(y != cursor_y || gl->osc133_data.out_end_col <= cursor_x)) {
end_x = gl->osc133_data.out_end_col;
end_y = y;
buf = window_copy_get_grid_range(wme, start_x, start_y,
end_x, end_y, &len);
if (buf != NULL) {
free(buf);
*sx = start_x;
*sy = start_y;
*ex = end_x;
*ey = end_y;
found = 1;
}
pending = 0;
}
if (gl->flags & GRID_LINE_START_PROMPT) {
pending = 0;
have_prompt = 1;
}
}
return (found);
}
static void
window_copy_output_end(struct screen *s, u_int *x, u_int *y)
{
*x = s->cx;
*y = screen_hsize(s) + s->cy;
}
static void *
window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
u_int sx, sy, ex, ey, total;
total = gd->hsize + gd->sy;
if (all) {
sx = sy = 0;
ey = total - 1;
ex = grid_get_line(gd, ey)->cellused;
} else if (!window_copy_find_output_range(wme, &sx, &sy, &ex, &ey)) {
*len = 0;
return (NULL);
}
return (window_copy_get_grid_range(wme, sx, sy, ex, ey, len));
}
static void *
window_copy_get_grid_range(struct window_mode_entry *wme, u_int sx, u_int sy,
u_int ex, u_int ey, size_t *len)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
char *buf;
size_t off;
u_int i;
buf = xmalloc(1);
off = 0;
for (i = sy; i <= ey; i++) {
window_copy_copy_line(wme, &buf, &off, i, i == sy ? sx : 0,
i == ey ? ex : gd->sx);
}
if (off != 0 && buf[off - 1] == '\n')
off--;
if (off == 0) {
free(buf);
*len = 0;
return (NULL);
}
*len = off;
return (buf);
}
static void *
window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
{
@@ -6102,8 +6529,7 @@ window_copy_copy_buffer(struct window_mode_entry *wme, const char *prefix,
if (set_clip &&
options_get_number(global_options, "set-clipboard") != 0) {
if (window_copy_line_numbers_active(wme) &&
(wp->flags & PANE_REDRAW)) {
if (wp->flags & PANE_REDRAW) {
/* Clear PANE_REDRAW so clipboard write not skipped. */
redraw = PANE_REDRAW;
wp->flags &= ~PANE_REDRAW;
@@ -6126,18 +6552,28 @@ window_copy_pipe_run(struct window_mode_entry *wme, struct session *s,
const char *cmd, size_t *len)
{
void *buf;
struct job *job;
buf = window_copy_get_selection(wme, len);
window_copy_pipe_buffer(s, cmd, buf, *len);
return (buf);
}
static void
window_copy_pipe_buffer(struct session *s, const char *cmd, void *buf,
size_t len)
{
struct job *job;
if (buf == NULL)
return;
if (cmd == NULL || *cmd == '\0')
cmd = options_get_string(global_options, "copy-command");
if (cmd != NULL && *cmd != '\0') {
job = job_run(cmd, 0, NULL, NULL, s, NULL, NULL, NULL, NULL,
NULL, JOB_NOWAIT, -1, -1);
if (job != NULL)
bufferevent_write(job_get_event(job), buf, *len);
bufferevent_write(job_get_event(job), buf, len);
}
return (buf);
}
static void