From 73d8f81c2cff3c95377d90fdbf6244db8cb8f127 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 15 Oct 2026 08:07:17 +0000 Subject: [PATCH] Add -f to switch-mode to read choices from a file, GitHub issue 5738. --- cmd-choose-tree.c | 63 +++++++++++++++++++++++++++++++--- tmux.1 | 24 +++++++++++-- tmux.h | 3 +- window-switch.c | 86 +++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 163 insertions(+), 13 deletions(-) diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 62df6ac6c..47b59a72e 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-choose-tree.c,v 1.60 2026/10/09 13:12:14 nicm Exp $ */ +/* $OpenBSD: cmd-choose-tree.c,v 1.61 2026/10/15 08:07:17 nicm Exp $ */ /* * Copyright (c) 2012 Thomas Adam @@ -18,6 +18,9 @@ #include +#include +#include + #include "tmux.h" /* @@ -28,6 +31,9 @@ static enum args_parse_type cmd_choose_tree_args_parse(struct args *args, u_int idx, char **cause); static enum cmd_retval cmd_choose_tree_exec(struct cmd *, struct cmdq_item *); +static void cmd_choose_tree_read_done(struct client *, + const char *, int, int, struct evbuffer *, + void *); const struct cmd_entry cmd_choose_tree_entry = { .name = "choose-tree", @@ -93,9 +99,9 @@ const struct cmd_entry cmd_switch_mode_entry = { .description = "Switch the active pane mode.", .alias = NULL, - .args = { "F:kO:rst:wZ", 0, 1, cmd_choose_tree_args_parse }, - .usage = "[-krsZw] [-F format] [-O sort-order] " CMD_TARGET_PANE_USAGE - " [command]", + .args = { "F:f:kO:rst:wZ", 0, 1, cmd_choose_tree_args_parse }, + .usage = "[-krsZw] [-F format] [-f path] [-O sort-order] " + CMD_TARGET_PANE_USAGE " [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -131,9 +137,11 @@ cmd_choose_tree_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); struct window_pane *wp = target->wp; const struct window_mode *mode; enum sort_order order; + char *path; order = sort_order_from_string(args_get(args, 'O')); if (order == SORT_END && args_has(args, 'O')) { @@ -158,6 +166,53 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) else mode = &window_tree_mode; + if (mode == &window_switch_mode && args_has(args, 'f')) { + path = format_single_from_target(item, args_get(args, 'f')); + file_read(c, path, cmd_choose_tree_read_done, item); + free(path); + return (CMD_RETURN_WAIT); + } window_pane_set_mode(wp, NULL, mode, item, target, args); return (CMD_RETURN_NORMAL); } + +static void +cmd_choose_tree_read_done(__unused struct client *c, const char *path, + int error, int closed, struct evbuffer *buffer, void *data) +{ + struct cmdq_item *item = data; + struct cmd *cmd = cmdq_get_cmd(item); + struct args *args = cmd_get_args(cmd); + struct cmd_find_state *target = cmdq_get_target(item); + struct window_pane *wp = target->wp; + struct window_mode_entry *wme; + char *buf = EVBUFFER_DATA(buffer); + size_t len = EVBUFFER_LENGTH(buffer); + + if (!closed) + return; + + if (error != 0) { + cmdq_error(item, "%s: %s", strerror(error), path); + goto out; + } + if (len != 0 && memchr(buf, '\0', len) != NULL) { + cmdq_error(item, "%s: contains binary data", path); + goto out; + } + if (!cmd_find_valid_state(target)) { + cmdq_error(item, "target pane no longer exists"); + goto out; + } + + TAILQ_FOREACH(wme, &wp->modes, entry) { + if (wme->mode == &window_switch_mode) + break; + } + if (window_pane_set_mode(wp, NULL, &window_switch_mode, item, target, + args) == 0 && wme == NULL) + window_switch_set_input(TAILQ_FIRST(&wp->modes), buffer); + +out: + cmdq_continue(item); +} diff --git a/tmux.1 b/tmux.1 index 6267b8227..e459d3423 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.1186 2026/10/14 12:33:46 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.1187 2026/10/15 08:07:17 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -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 14 2026 $ +.Dd $Mdocdate: October 15 2026 $ .Dt TMUX 1 .Os .Sh NAME @@ -3205,6 +3205,7 @@ command works only if at least one client is attached. .Ic switch\-mode .Op Fl krsZw .Op Fl F Ar format +.Op Fl f Ar path .Op Fl O Ar sort\-order .Op Fl t Ar target\-pane .Op Ar command @@ -3250,6 +3251,24 @@ If is not given, "switch\-client \-Zt \[aq]%%\[aq]" is used. .Fl F specifies the format for each item in the list. +.Pp +.Fl f +reads choices from +.Ar path +instead of listing sessions or windows. +Each line is one choice. +With +.Fl f , +.Ql switch_item +contains the original line and the default display format is +.Ql #{switch_item} . +Fuzzy matching uses the displayed text. +When a choice is selected, +.Ql %% +and +.Ql %1 +are replaced by the original line +.Pp .Fl k kills the pane when the mode is exited. .Pp @@ -7619,6 +7638,7 @@ The following variables are available, where appropriate: .It Li "session_windows" Ta "" Ta "Number of windows in session" .It Li "socket_path" Ta "" Ta "Server socket path" .It Li "start_time" Ta "" Ta "Server start time" +.It Li "switch_item" Ta "" Ta "Original input line in switch mode" .It Li "synchronized_output_flag" Ta "" Ta "1 if pane has synchronized output enabled" .It Li "uid" Ta "" Ta "Server UID" .It Li "user" Ta "" Ta "Server user" diff --git a/tmux.h b/tmux.h index f0c6ca61b..a03a39602 100644 --- a/tmux.h +++ b/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.1463 2026/10/14 12:48:50 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.1464 2026/10/15 08:07:17 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -3962,6 +3962,7 @@ extern const struct window_mode window_tree_mode; /* window-switch.c */ extern const struct window_mode window_switch_mode; +void window_switch_set_input(struct window_mode_entry *, struct evbuffer *); /* window-clock.c */ extern const struct window_mode window_clock_mode; diff --git a/window-switch.c b/window-switch.c index 24b3851ac..cafc18ca4 100644 --- a/window-switch.c +++ b/window-switch.c @@ -1,4 +1,4 @@ -/* $OpenBSD: window-switch.c,v 1.5 2026/10/07 13:13:49 nicm Exp $ */ +/* $OpenBSD: window-switch.c,v 1.6 2026/10/15 08:07:17 nicm Exp $ */ /* * Copyright (c) 2026 Nicholas Marriott @@ -63,7 +63,8 @@ const struct window_mode window_switch_mode = { enum window_switch_type { WINDOW_SWITCH_TYPE_SESSION, - WINDOW_SWITCH_TYPE_WINDOW + WINDOW_SWITCH_TYPE_WINDOW, + WINDOW_SWITCH_TYPE_INPUT }; struct window_switch_itemdata { @@ -73,6 +74,7 @@ struct window_switch_itemdata { uint64_t tag; char *text; + const char *value; bitstr_t *match; u_int score; @@ -87,6 +89,9 @@ struct window_switch_modedata { char *format; char *command; + char **lines; + u_int lines_size; + enum window_switch_type type; enum sort_order order; int reversed; @@ -165,6 +170,20 @@ window_switch_add_window(struct window_switch_modedata *data, format_free(ft); } +static void +window_switch_add_input(struct window_switch_modedata *data, u_int idx, + struct format_tree *ft) +{ + struct window_switch_itemdata *item; + + item = window_switch_add_item(data); + item->type = WINDOW_SWITCH_TYPE_INPUT; + item->value = data->lines[idx]; + item->order = data->reversed ? data->lines_size - idx - 1 : idx; + format_add(ft, "switch_item", "%s", item->value); + item->text = format_expand(ft, data->format); +} + static int window_switch_compare(const void *a0, const void *b0) { @@ -192,6 +211,8 @@ window_switch_build(struct window_switch_modedata *data) struct session **sl; struct winlink **wl; struct sort_criteria sort_crit; + struct cmd_find_state fs; + struct format_tree *ft; sort_crit.order = data->order; sort_crit.reversed = data->reversed; @@ -214,6 +235,14 @@ window_switch_build(struct window_switch_modedata *data) for (i = 0; i < nw; i++) window_switch_add_window(data, wl[i], &order); break; + case WINDOW_SWITCH_TYPE_INPUT: + cmd_find_from_pane(&fs, data->wp, 0); + ft = format_create(NULL, NULL, FORMAT_NONE, 0); + format_defaults(ft, NULL, fs.s, fs.wl, fs.wp); + for (i = 0; i < data->lines_size; i++) + window_switch_add_input(data, i, ft); + format_free(ft); + break; } for (i = 0; i < data->item_size; i++) { @@ -365,11 +394,16 @@ window_switch_init(struct window_mode_entry *wme, data->type = WINDOW_SWITCH_TYPE_WINDOW; else data->type = WINDOW_SWITCH_TYPE_SESSION; + if (args_has(args, 'f')) + data->type = WINDOW_SWITCH_TYPE_INPUT; data->filter = xstrdup(""); - if (args == NULL || !args_has(args, 'F')) - data->format = xstrdup(WINDOW_SWITCH_DEFAULT_FORMAT); - else + if (args == NULL || !args_has(args, 'F')) { + if (data->type == WINDOW_SWITCH_TYPE_INPUT) + data->format = xstrdup("#{switch_item}"); + else + data->format = xstrdup(WINDOW_SWITCH_DEFAULT_FORMAT); + } else data->format = xstrdup(args_get(args, 'F')); if (args == NULL || args_count(args) == 0) data->command = xstrdup(WINDOW_SWITCH_DEFAULT_COMMAND); @@ -407,6 +441,36 @@ window_switch_init(struct window_mode_entry *wme, return (s); } +void +window_switch_set_input(struct window_mode_entry *wme, struct evbuffer *buffer) +{ + struct window_switch_modedata *data = wme->data; + char *line; + size_t size; + + for (;;) { + line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); + if (line == NULL) { + size = EVBUFFER_LENGTH(buffer); + if (size == 0) + break; + line = xmemdup(EVBUFFER_DATA(buffer), size); + evbuffer_drain(buffer, size); + } + if (*line == '\0') { + free(line); + continue; + } + data->lines = xreallocarray(data->lines, data->lines_size + 1, + sizeof *data->lines); + data->lines[data->lines_size++] = line; + } + window_switch_build(data); + window_switch_set_current(data, 0); + window_switch_draw_screen(wme); + wme->wp->flags |= PANE_REDRAW; +} + static void window_switch_free(struct window_mode_entry *wme) { @@ -419,6 +483,9 @@ window_switch_free(struct window_mode_entry *wme) for (i = 0; i < data->item_size; i++) window_switch_free_item(data->item_list[i]); free(data->item_list); + for (i = 0; i < data->lines_size; i++) + free(data->lines[i]); + free(data->lines); free(data->matches); free(data->filter); @@ -477,6 +544,10 @@ window_switch_run_command(struct window_switch_modedata *data, struct client *c) } } break; + case WINDOW_SWITCH_TYPE_INPUT: + target = xstrdup(item->value); + cmd_find_from_pane(&fs, data->wp, 0); + break; } if (target == NULL) return (0); @@ -484,6 +555,8 @@ window_switch_run_command(struct window_switch_modedata *data, struct client *c) command = cmd_template_replace(data->command, target, 1); if (command != NULL && *command != '\0') { state = cmdq_new_state(&fs, NULL, 0); + if (item->type == WINDOW_SWITCH_TYPE_INPUT) + cmdq_add_format(state, "switch_item", "%s", item->value); status = cmd_parse_and_append(command, NULL, c, state, &error); if (status == CMD_PARSE_ERROR) { if (c != NULL) { @@ -539,7 +612,8 @@ window_switch_key(struct window_mode_entry *wme, struct client *c, return; if (data->prompt != NULL && screen_size_y(&data->screen) != 0 && y == screen_size_y(&data->screen) - 1 && - MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_1 && !MOUSE_DRAG(m->b) && + MOUSE_BUTTONS(m->b) == MOUSE_BUTTON_1 && + !MOUSE_DRAG(m->b) && !MOUSE_RELEASE(m->b)) { result = prompt_mouse(data->prompt, x, 0, screen_size_x(&data->screen), &redraw);