Add commands to copy mode to select, pipe and open OSC 133 command

output, from Michael Grant.
This commit is contained in:
nicm
2026-10-08 07:50:05 +00:00
committed by tmux update bot
parent cb44afd415
commit a0a0e12234
10 changed files with 744 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid-reader.c,v 1.11 2026/07/29 17:42:56 nicm Exp $ */
/* $OpenBSD: grid-reader.c,v 1.12 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2020 Anindya Mukherjee <anindya49@hotmail.com>
@@ -436,3 +436,208 @@ grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
gr->cx = oldx;
gr->cy = oldy;
}
/* Get the end of the last used line. */
static void
grid_reader_output_end(struct grid *gd, u_int *x, u_int *y)
{
u_int last = gd->hsize + gd->sy - 1;
while (last > 0 && grid_get_line(gd, last)->cellused == 0)
last--;
*x = grid_get_line(gd, last)->cellused;
*y = last;
}
/* Find the most recent complete output at or before the cursor. */
static int
grid_reader_previous_output_range(struct grid_reader *gr, u_int *sx,
u_int *sy, u_int *ex, u_int *ey)
{
struct grid *gd = gr->gd;
struct grid_line *gl;
struct osc133_data *od;
u_int cursor_x = gr->cx, cursor_y = gr->cy;
u_int start_x, start_y, y, total;
int found = 0, have_prompt = 0;
int pending = 0;
int has_start, has_end, end_first;
int cleared;
total = gd->hsize + gd->sy;
/*
* Scan from the top because markers arrive in their natural order, C
* then D. A backward scan would meet D first and need extra state to
* find its C. The last complete output found is the one wanted.
*/
for (y = 0; y < total && y <= cursor_y; y++) {
gl = grid_get_line(gd, y);
od = &gl->osc133_data;
/* On the cursor's line, ignore markers after the cursor. */
has_start = has_end = end_first = 0;
if (gl->flags & GRID_LINE_START_OUTPUT) {
if (y != cursor_y || od->out_start_col <= cursor_x)
has_start = 1;
}
if (gl->flags & GRID_LINE_END_OUTPUT) {
if (y != cursor_y || od->out_end_col <= cursor_x)
has_end = 1;
}
/* An end before the start ends the previous output. */
if (has_start && has_end && od->out_end_col < od->out_start_col)
end_first = 1;
/* A C marker starts an output that is pending until its D. */
if (has_start && !end_first) {
start_x = od->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)) {
cleared = 1;
if ((gl->flags & GRID_LINE_START_PROMPT) &&
od->out_end_col > od->prompt_col)
cleared = 0;
if (cleared) {
start_x = start_y = 0;
pending = 1;
}
}
/* A D marker completes the pending output. */
if (pending && has_end) {
*sx = start_x;
*sy = start_y;
*ex = od->out_end_col;
*ey = y;
found = 1;
pending = 0;
}
if (has_start && end_first) {
start_x = od->out_start_col;
start_y = y;
pending = 1;
}
/* A prompt drops an unfinished output. */
if (gl->flags & GRID_LINE_START_PROMPT) {
if (!has_start || od->out_start_col < od->prompt_col)
pending = 0;
have_prompt = 1;
}
}
return (found);
}
/*
* Find the output range for the command at the cursor, from its C marker to
* its D marker. If the prompt has no output yet, use the previous output.
* Returns 0 if there is no usable range.
*/
int
grid_reader_output_range(struct grid_reader *gr, u_int *sx, u_int *sy,
u_int *ex, u_int *ey)
{
struct grid *gd = gr->gd;
struct grid_line *gl;
struct osc133_data *od;
u_int cursor_x = gr->cx, cursor_y = gr->cy;
u_int prompt_x = 0, prompt_y = UINT_MAX;
u_int y, total;
int found_start = 0, found_end = 0;
int next_prompt, in_range, end_ok;
int found;
/* Find the last prompt at or before the cursor. */
total = gd->hsize + gd->sy;
for (y = 0; y < total; y++) {
gl = grid_get_line(gd, y);
od = &gl->osc133_data;
if (~gl->flags & GRID_LINE_START_PROMPT)
continue;
if (y > cursor_y)
break;
if (y == cursor_y && od->prompt_col > cursor_x)
break;
prompt_y = y;
prompt_x = od->prompt_col;
}
/* With no prompt, its A marker may have left history, so start at 0. */
if (prompt_y == UINT_MAX)
y = 0;
else
y = prompt_y;
/* Walk down from the prompt to its C and D, up to the next prompt. */
for (; y < total; y++) {
gl = grid_get_line(gd, y);
od = &gl->osc133_data;
next_prompt = 0;
if (y != prompt_y && (gl->flags & GRID_LINE_START_PROMPT))
next_prompt = 1;
/* Output before the next prompt on its line is ours. */
if (y == prompt_y)
in_range = (od->out_start_col >= prompt_x);
else if (next_prompt)
in_range = (od->out_start_col < od->prompt_col);
else
in_range = 1;
if (gl->flags & GRID_LINE_START_OUTPUT && in_range) {
*sx = od->out_start_col;
*sy = y;
found_start = 1;
}
/* Both A and C may have left history while D remains. */
if (!found_start && prompt_y == UINT_MAX &&
(gl->flags & GRID_LINE_END_OUTPUT)) {
if (!next_prompt || od->out_end_col <= od->prompt_col) {
*sx = *sy = 0;
found_start = 1;
}
}
/* An output may end on the same line or the next prompt's. */
if (found_start && (gl->flags & GRID_LINE_END_OUTPUT)) {
end_ok = 1;
if (y == prompt_y && od->out_end_col < prompt_x)
end_ok = 0;
if (y == *sy && od->out_end_col < *sx)
end_ok = 0;
if (end_ok) {
*ex = od->out_end_col;
*ey = y;
found_end = 1;
break;
}
}
if (next_prompt)
break;
}
/* The cursor is on a prompt with no output yet. */
if (!found_start) {
found = grid_reader_previous_output_range(gr, sx, sy, ex, ey);
return (found);
}
/*
* Without a D marker the command is still running, so its output runs
* to the last used line. If the next prompt came first, it has no
* usable end.
*/
if (!found_end) {
if (y != total)
return (0);
grid_reader_output_end(gd, ex, ey);
}
return (1);
}

81
grid.c
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: grid.c,v 1.158 2026/09/01 12:49:49 nicm Exp $ */
/* $OpenBSD: grid.c,v 1.159 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -1296,12 +1296,60 @@ grid_reflow_move(struct grid *gd, struct grid_line *from)
return (to);
}
/* Move the OSC 133 markers from one line to another. */
static void
grid_reflow_move_osc133(struct grid_line *to, struct grid_line *from,
u_int to_col, u_int count, int last)
{
struct osc133_data *src = &from->osc133_data;
struct osc133_data *dst = &to->osc133_data;
u_short *src_col[] = {
&src->prompt_col,
&src->cmd_col,
&src->out_start_col,
&src->out_end_col
};
u_short *dst_col[] = {
&dst->prompt_col,
&dst->cmd_col,
&dst->out_start_col,
&dst->out_end_col
};
u_int flags[] = {
GRID_LINE_START_PROMPT|GRID_LINE_SECOND_PROMPT,
GRID_LINE_START_COMMAND,
GRID_LINE_START_OUTPUT,
GRID_LINE_END_OUTPUT
};
u_int i, flag;
if ((from->flags & GRID_LINE_OSC133_FLAGS) == 0)
return;
for (i = 0; i < nitems(flags); i++) {
flag = from->flags & flags[i];
if (flag == 0)
continue;
if (!last && *src_col[i] >= count) {
*src_col[i] -= count;
continue;
}
*dst_col[i] = to_col + *src_col[i];
to->flags |= flag;
from->flags &= ~flag;
*src_col[i] = 0;
if (flag == GRID_LINE_END_OUTPUT) {
dst->exit_status = src->exit_status;
src->exit_status = 0;
}
}
}
/* Join line below onto this one. */
static void
grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
u_int width, int already)
{
struct grid_line *gl, *from = NULL;
struct grid_line *gl, *from = NULL, *next;
struct grid_cell gc;
u_int lines, left, i, to, line, want = 0;
u_int at;
@@ -1332,13 +1380,20 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
break;
line = yy + 1 + lines;
/* If the next line is empty, skip it. */
/* Consume empty lines, including any markers. */
if (~gd->linedata[line].flags & GRID_LINE_WRAPPED)
wrapped = 0;
if (gd->linedata[line].cellused == 0) {
next = &gd->linedata[line];
if (!wrapped &&
(next->flags & GRID_LINE_OSC133_FLAGS) == 0)
break;
from = next;
want = 0;
grid_reflow_move_osc133(gl, from, at, 0, 1);
lines++;
if (!wrapped)
break;
lines++;
continue;
}
@@ -1365,6 +1420,8 @@ grid_reflow_join(struct grid *target, struct grid *gd, u_int sx, u_int yy,
grid_set_cell(target, at, to, &gc);
at++;
}
grid_reflow_move_osc133(gl, from, at - want, want,
want == from->cellused);
lines++;
/*
@@ -1435,6 +1492,14 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
line = target->sy + 1;
first = grid_reflow_add(target, lines);
/* The first line keeps only the markers within its first at cells. */
memcpy(first, gl, sizeof *first);
first->cellsize = first->cellused = at;
first->flags &= ~GRID_LINE_OSC133_FLAGS;
first->flags |= GRID_LINE_WRAPPED;
memset(&first->osc133_data, 0, sizeof first->osc133_data);
grid_reflow_move_osc133(first, gl, 0, at, 0);
/* Copy sections from the original line. */
width = 0;
xx = 0;
@@ -1442,6 +1507,8 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
grid_get_cell1(gl, i, &gc);
if (width + gc.data.width > sx) {
target->linedata[line].flags |= GRID_LINE_WRAPPED;
grid_reflow_move_osc133(&target->linedata[line], gl, 0,
xx, 0);
line++;
width = 0;
@@ -1453,11 +1520,7 @@ grid_reflow_split(struct grid *target, struct grid *gd, u_int sx, u_int yy,
}
if (flags & GRID_LINE_WRAPPED)
target->linedata[line].flags |= GRID_LINE_WRAPPED;
/* Move the remainder of the original line. */
gl->cellsize = gl->cellused = at;
gl->flags |= GRID_LINE_WRAPPED;
memcpy(first, gl, sizeof *first);
grid_reflow_move_osc133(&target->linedata[line], gl, 0, xx, 1);
grid_reflow_dead(gl);
/* Adjust the scroll position. */

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: input.c,v 1.272 2026/09/21 10:22:31 nicm Exp $ */
/* $OpenBSD: input.c,v 1.273 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -3241,7 +3241,6 @@ input_osc_133(struct input_ctx *ictx, const char *p)
case 'A':
case 'N':
if (gl != NULL) {
memset(&gl->osc133_data, 0, sizeof gl->osc133_data);
gl->osc133_data.prompt_col = s->cx;
gl->flags |= GRID_LINE_START_PROMPT;
}

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: key-bindings.c,v 1.193 2026/09/21 20:45:48 nicm Exp $ */
/* $OpenBSD: key-bindings.c,v 1.194 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -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 -- '%%' } }",

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: spawn.c,v 1.56 2026/10/08 07:06:26 nicm Exp $ */
/* $OpenBSD: spawn.c,v 1.57 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -685,7 +685,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 };
@@ -699,13 +699,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);

70
tmux.1
View File

@@ -1,4 +1,4 @@
.\" $OpenBSD: tmux.1,v 1.1180 2026/10/07 13:13:49 nicm Exp $
.\" $OpenBSD: tmux.1,v 1.1181 2026/10/08 07:50:05 nicm Exp $
.\"
.\" Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
.\"
@@ -14,7 +14,7 @@
.\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
.\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
.\"
.Dd $Mdocdate: October 7 2026 $
.Dd $Mdocdate: October 8 2026 $
.Dt TMUX 1
.Os
.Sh NAME
@@ -2011,6 +2011,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
@@ -2229,6 +2248,31 @@ 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
(vi: M\-e)
(emacs: e)
.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
(vi: M\-e)
(emacs: e)
.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.
.It Xo
.Ic page\-down
(vi: C\-f)
(emacs: PageDown)
@@ -2269,6 +2313,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
@@ -2458,6 +2517,13 @@ 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
(vi: M\-o)
(emacs: C\-o)
.Xc
Select the current OSC 133 command output.
.It Xo
.Ic select\-line
(vi: V)
.Xc

6
tmux.h
View File

@@ -1,4 +1,4 @@
/* $OpenBSD: tmux.h,v 1.1453 2026/10/06 17:49:45 nicm Exp $ */
/* $OpenBSD: tmux.h,v 1.1454 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -3487,6 +3487,8 @@ void grid_reader_start(struct grid_reader *, struct grid *, u_int, u_int);
void grid_reader_get_cursor(struct grid_reader *, u_int *, u_int *);
u_int grid_reader_line_length(struct grid_reader *);
int grid_reader_in_set(struct grid_reader *, const char *);
int grid_reader_output_range(struct grid_reader *, u_int *, u_int *,
u_int *, u_int *);
void grid_reader_cursor_right(struct grid_reader *, int, int, int);
void grid_reader_cursor_left(struct grid_reader *, int);
void grid_reader_cursor_down(struct grid_reader *);
@@ -4171,7 +4173,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

@@ -1,4 +1,4 @@
/* $OpenBSD: window-buffer.c,v 1.52 2026/08/24 21:19:40 nicm Exp $ */
/* $OpenBSD: window-buffer.c,v 1.53 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -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

@@ -1,4 +1,4 @@
/* $OpenBSD: window-copy.c,v 1.436 2026/10/07 12:59:50 nicm Exp $ */
/* $OpenBSD: window-copy.c,v 1.437 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -121,9 +121,17 @@ 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_get_output_range(struct window_mode_entry *, int,
u_int *, u_int *, 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 *,
@@ -133,6 +141,8 @@ static void window_copy_copy_selection(struct window_mode_entry *,
const char *, int, int);
static void window_copy_append_selection(struct window_mode_entry *);
static void window_copy_clear_selection(struct window_mode_entry *);
static u_int window_copy_copy_line_length(struct window_mode_entry *,
u_int, int *);
static void window_copy_copy_line(struct window_mode_entry *, char **,
size_t *, u_int, u_int, u_int);
static int window_copy_in_set(struct window_mode_entry *, u_int, u_int,
@@ -1155,7 +1165,9 @@ window_copy_formats(struct window_mode_entry *wme, struct format_tree *ft)
format_add(ft, "selection_active", "1");
else
format_add(ft, "selection_active", "0");
if (data->endselx != data->selx || data->endsely != data->sely)
if (data->modekeys == MODEKEY_VI ||
data->endselx != data->selx ||
data->endsely != data->sely)
format_add(ft, "selection_present", "1");
else
format_add(ft, "selection_present", "0");
@@ -1605,6 +1617,84 @@ 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, last;
int all = args_has(cs->wargs, 'a');
int wrapped;
if (!window_copy_get_output_range(wme, all, &sx, &sy, &ex, &ey))
return (WINDOW_COPY_CMD_NOTHING);
/* An empty output selects nothing. */
buf = window_copy_get_grid_range(wme, sx, sy, ex, ey, &len);
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);
/* The end is inclusive in vi mode, so step back onto the last cell. */
if (data->modekeys == MODEKEY_VI) {
last = window_copy_copy_line_length(wme, ey, NULL);
if (ex > last)
ex = last;
if (ex == 0 && ey > 0) {
ey--;
ex = window_copy_copy_line_length(wme, ey, &wrapped);
if (wrapped && ex > 0)
ex--;
} else {
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(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 all = args_has(cs->wargs, 'a');
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, all);
if (buf != NULL) {
window_copy_copy_buffer(wme, prefix, buf, len,
set_paste, set_clip);
}
}
free(prefix);
window_copy_clear_selection(wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static enum window_copy_cmd_action
window_copy_cmd_cursor_down(struct window_copy_cmd_state *cs)
{
@@ -2772,6 +2862,127 @@ 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(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 all = args_has(cs->wargs, 'a');
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, all);
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);
window_copy_clear_selection(wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static enum window_copy_cmd_action
window_copy_cmd_pipe_output(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;
int all = args_has(cs->wargs, 'a');
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, all);
if (buf != NULL) {
window_copy_pipe_buffer(s, command, buf, len);
free(buf);
}
}
free(command);
window_copy_clear_selection(wme);
return (WINDOW_COPY_CMD_REDRAW);
}
static void
window_copy_open_done(char *buf, __unused size_t len, __unused void *arg)
{
free(buf);
}
static void
window_copy_open_buffer(struct window_copy_cmd_state *cs, void *buf,
size_t len)
{
struct window_mode_entry *wme = cs->wme;
struct window_pane *wp = wme->wp;
char *editor = NULL;
const char *arg0 = args_string(cs->wargs, 0);
if (arg0 != NULL && *arg0 != '\0')
editor = format_single(NULL, arg0, cs->c, cs->s, cs->wl, wp);
spawn_editor(cs->c, buf, len, editor, window_copy_open_done, NULL);
free(editor);
}
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;
size_t len;
if (cs->c == NULL)
return (WINDOW_COPY_CMD_NOTHING);
if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE)
return (WINDOW_COPY_CMD_NOTHING);
buf = window_copy_get_selection(wme, &len);
if (buf != NULL) {
window_copy_open_buffer(cs, buf, len);
free(buf);
}
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;
size_t len;
if (cs->c == NULL)
return (WINDOW_COPY_CMD_NOTHING);
buf = window_copy_get_output(wme, &len, args_has(cs->wargs, 'a'));
if (buf != NULL) {
window_copy_open_buffer(cs, buf, len);
free(buf);
}
return (WINDOW_COPY_CMD_NOTHING);
}
static enum window_copy_cmd_action
window_copy_cmd_goto_line(struct window_copy_cmd_state *cs)
{
@@ -3426,6 +3637,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,
@@ -3648,6 +3871,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,
@@ -3684,6 +3919,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,
@@ -3861,6 +4108,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,
@@ -6054,9 +6307,11 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
window_copy_redraw_lines(wme, cy, sy - cy + 1);
} else {
if (endsy < cy) {
window_copy_redraw_lines(wme, endsy, cy - endsy + 1);
window_copy_redraw_lines(wme, endsy,
cy - endsy + 1);
} else {
window_copy_redraw_lines(wme, cy, endsy - cy + 1);
window_copy_redraw_lines(wme, cy,
endsy - cy + 1);
}
}
}
@@ -6064,6 +6319,78 @@ window_copy_set_selection(struct window_mode_entry *wme, int may_redraw,
return (1);
}
/* Get the range of the current output, or the whole buffer with all. */
static int
window_copy_get_output_range(struct window_mode_entry *wme, int all,
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_reader gr;
u_int total, cursor_y;
int found;
if (!all) {
cursor_y = screen_hsize(data->backing) + data->cy - data->oy;
grid_reader_start(&gr, gd, data->cx, cursor_y);
found = grid_reader_output_range(&gr, sx, sy, ex, ey);
return (found);
}
total = gd->hsize + gd->sy;
*sx = *sy = 0;
*ey = total - 1;
*ex = grid_get_line(gd, *ey)->cellused;
return (1);
}
/* Get current output. */
static void *
window_copy_get_output(struct window_mode_entry *wme, size_t *len, int all)
{
void *buf;
u_int sx, sy, ex, ey;
if (!window_copy_get_output_range(wme, all, &sx, &sy, &ex, &ey)) {
*len = 0;
return (NULL);
}
buf = window_copy_get_grid_range(wme, sx, sy, ex, ey, len);
return (buf);
}
/* Get the text from sx,sy up to ex,ey, or NULL if there is none. */
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, first, last;
buf = xmalloc(1);
off = 0;
for (i = sy; i <= ey; i++) {
first = 0;
last = gd->sx;
if (i == sy)
first = sx;
if (i == ey)
last = ex;
window_copy_copy_line(wme, &buf, &off, i, first, last);
}
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)
{
@@ -6074,7 +6401,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
size_t off;
u_int i, xx, yy, sx, sy, ex, ey, ey_last;
u_int firstsx, lastex, restex, restsx, selx;
int keys;
int keys, wrapped;
if (data->screen.sel == NULL && data->lineflag == LINE_SEL_NONE) {
buf = window_copy_match_at_cursor(data);
@@ -6107,7 +6434,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
}
/* Trim ex to end of line. */
ey_last = window_copy_find_length(wme, ey);
ey_last = window_copy_copy_line_length(wme, ey, &wrapped);
if (ex > ey_last)
ex = ey_last;
@@ -6178,8 +6505,7 @@ window_copy_get_selection(struct window_mode_entry *wme, size_t *len)
}
/* Remove final \n (unless at end in vi mode). */
if (keys == MODEKEY_EMACS || lastex <= ey_last) {
if (~grid_get_line(data->backing->grid, ey)->flags &
GRID_LINE_WRAPPED || lastex != ey_last)
if (!wrapped || lastex != ey_last)
off -= 1;
}
*len = off;
@@ -6220,18 +6546,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
@@ -6307,6 +6643,29 @@ window_copy_append_selection(struct window_mode_entry *wme)
free(bufname);
}
/* Get the length for copying, preserving spaces on wrapped lines. */
static u_int
window_copy_copy_line_length(struct window_mode_entry *wme, u_int sy,
int *wrapped)
{
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_line *gl = grid_get_line(gd, sy);
u_int length;
int line_wrapped;
line_wrapped = 0;
if ((gl->flags & GRID_LINE_WRAPPED) && gl->cellsize <= gd->sx)
line_wrapped = 1;
if (wrapped != NULL)
*wrapped = line_wrapped;
if (line_wrapped)
length = gl->cellsize;
else
length = window_copy_find_length(wme, sy);
return (length);
}
static void
window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off,
u_int sy, u_int sx, u_int ex)
@@ -6314,27 +6673,15 @@ window_copy_copy_line(struct window_mode_entry *wme, char **buf, size_t *off,
struct window_copy_mode_data *data = wme->data;
struct grid *gd = data->backing->grid;
struct grid_cell gc;
struct grid_line *gl;
struct utf8_data ud;
u_int i, xx, wrapped = 0;
u_int i, xx;
const char *s;
int wrapped;
if (sx > ex)
return;
/*
* Work out if the line was wrapped at the screen edge and all of it is
* on screen.
*/
gl = grid_get_line(gd, sy);
if (gl->flags & GRID_LINE_WRAPPED && gl->cellsize <= gd->sx)
wrapped = 1;
/* If the line was wrapped, don't strip spaces (use the full length). */
if (wrapped)
xx = gl->cellsize;
else
xx = window_copy_find_length(wme, sy);
xx = window_copy_copy_line_length(wme, sy, &wrapped);
if (ex > xx)
ex = xx;
if (sx > xx)

View File

@@ -1,4 +1,4 @@
/* $OpenBSD: window-customize.c,v 1.36 2026/07/28 10:35:31 nicm Exp $ */
/* $OpenBSD: window-customize.c,v 1.37 2026/10/08 07:50:05 nicm Exp $ */
/*
* Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -2011,13 +2011,15 @@ window_customize_edit_close_cb(char *buf, size_t len, void *arg)
switch (ed->edit_type) {
case WINDOW_CUSTOMIZE_EDIT_OPTION:
if (window_customize_option_editable(data, item) &&
window_customize_set_option_value(item, value, &cause) != 0) {
window_customize_set_option_value(item, value,
&cause) != 0) {
free(cause);
goto out;
}
break;
case WINDOW_CUSTOMIZE_EDIT_KEY_COMMAND:
if (window_customize_set_command_value(item, value, &cause) != 0) {
if (window_customize_set_command_value(item, value,
&cause) != 0) {
free(cause);
goto out;
}
@@ -2104,8 +2106,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);