From aca3ffb30a5c15ca494941f4f0c46678546d081c Mon Sep 17 00:00:00 2001 From: nicm <nicm> Date: Mon, 24 Mar 2025 20:01:03 +0000 Subject: [PATCH 1/3] Add default-client-command to set the command used is tmux is run without a command (the default stays new-session). From David Mandelberg in GitHub issue 4422. --- cmd.c | 4 ++-- options-table.c | 7 ++++++ options.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++ server-client.c | 39 ++++++++++++++++---------------- tmux.1 | 11 ++++++--- tmux.h | 7 ++++-- 6 files changed, 101 insertions(+), 26 deletions(-) diff --git a/cmd.c b/cmd.c index 64f63c92..61519f3f 100644 --- a/cmd.c +++ b/cmd.c @@ -637,7 +637,7 @@ cmd_list_free(struct cmd_list *cmdlist) /* Copy a command list, expanding %s in arguments. */ struct cmd_list * -cmd_list_copy(struct cmd_list *cmdlist, int argc, char **argv) +cmd_list_copy(const struct cmd_list *cmdlist, int argc, char **argv) { struct cmd *cmd; struct cmd_list *new_cmdlist; @@ -668,7 +668,7 @@ cmd_list_copy(struct cmd_list *cmdlist, int argc, char **argv) /* Get a command list as a string. */ char * -cmd_list_print(struct cmd_list *cmdlist, int escaped) +cmd_list_print(const struct cmd_list *cmdlist, int escaped) { struct cmd *cmd, *next; char *buf, *this; diff --git a/options-table.c b/options-table.c index 6de2fb02..f5b7c45f 100644 --- a/options-table.c +++ b/options-table.c @@ -286,6 +286,13 @@ const struct options_table_entry options_table[] = { .text = "Style of the cursor." }, + { .name = "default-client-command", + .type = OPTIONS_TABLE_COMMAND, + .scope = OPTIONS_TABLE_SERVER, + .default_str = "new-session", + .text = "Default command to run when tmux is run without a command." + }, + { .name = "default-terminal", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, diff --git a/options.c b/options.c index 5541a376..f39fbf2a 100644 --- a/options.c +++ b/options.c @@ -260,6 +260,7 @@ options_default(struct options *oo, const struct options_table_entry *oe) struct options_entry *o; union options_value *ov; u_int i; + struct cmd_parse_result *pr; o = options_empty(oo, oe); ov = &o->value; @@ -278,6 +279,17 @@ options_default(struct options *oo, const struct options_table_entry *oe) case OPTIONS_TABLE_STRING: ov->string = xstrdup(oe->default_str); break; + case OPTIONS_TABLE_COMMAND: + pr = cmd_parse_from_string(oe->default_str, NULL); + switch (pr->status) { + case CMD_PARSE_ERROR: + free(pr->error); + break; + case CMD_PARSE_SUCCESS: + ov->cmdlist = pr->cmdlist; + break; + } + break; default: ov->number = oe->default_num; break; @@ -737,6 +749,19 @@ options_get_number(struct options *oo, const char *name) return (o->value.number); } +const struct cmd_list * +options_get_command(struct options *oo, const char *name) +{ + struct options_entry *o; + + o = options_get(oo, name); + if (o == NULL) + fatalx("missing option %s", name); + if (!OPTIONS_IS_COMMAND(o)) + fatalx("option %s is not a command", name); + return (o->value.cmdlist); +} + struct options_entry * options_set_string(struct options *oo, const char *name, int append, const char *fmt, ...) @@ -798,6 +823,30 @@ options_set_number(struct options *oo, const char *name, long long value) return (o); } +struct options_entry * +options_set_command(struct options *oo, const char *name, + struct cmd_list *value) +{ + struct options_entry *o; + + if (*name == '@') + fatalx("user option %s must be a string", name); + + o = options_get_only(oo, name); + if (o == NULL) { + o = options_default(oo, options_parent_table_entry(oo, name)); + if (o == NULL) + return (NULL); + } + + if (!OPTIONS_IS_COMMAND(o)) + fatalx("option %s is not a command", name); + if (o->value.cmdlist != NULL) + cmd_list_free(o->value.cmdlist); + o->value.cmdlist = value; + return (o); +} + int options_scope_from_name(struct args *args, int window, const char *name, struct cmd_find_state *fs, struct options **oo, @@ -1054,6 +1103,7 @@ options_from_string(struct options *oo, const struct options_table_entry *oe, const char *errstr, *new; char *old; key_code key; + struct cmd_parse_result *pr; if (oe != NULL) { if (value == NULL && @@ -1112,6 +1162,15 @@ options_from_string(struct options *oo, const struct options_table_entry *oe, case OPTIONS_TABLE_CHOICE: return (options_from_string_choice(oe, oo, name, value, cause)); case OPTIONS_TABLE_COMMAND: + pr = cmd_parse_from_string(value, NULL); + switch (pr->status) { + case CMD_PARSE_ERROR: + *cause = pr->error; + return (-1); + case CMD_PARSE_SUCCESS: + options_set_command(oo, name, pr->cmdlist); + return (0); + } break; } return (-1); diff --git a/server-client.c b/server-client.c index 8ab00fbf..1922f498 100644 --- a/server-client.c +++ b/server-client.c @@ -3452,6 +3452,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) struct cmd_parse_result *pr; struct args_value *values; struct cmdq_item *new_item; + struct cmd_list *cmdlist; if (c->flags & CLIENT_EXIT) return; @@ -3472,33 +3473,33 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg) } if (argc == 0) { - argc = 1; - argv = xcalloc(1, sizeof *argv); - *argv = xstrdup("new-session"); + cmdlist = cmd_list_copy(options_get_command(global_options, + "default-client-command"), 0, NULL); + } else { + values = args_from_vector(argc, argv); + pr = cmd_parse_from_arguments(values, argc, NULL); + switch (pr->status) { + case CMD_PARSE_ERROR: + cause = pr->error; + goto error; + case CMD_PARSE_SUCCESS: + break; + } + args_free_values(values, argc); + free(values); + cmd_free_argv(argc, argv); + cmdlist = pr->cmdlist; } - values = args_from_vector(argc, argv); - pr = cmd_parse_from_arguments(values, argc, NULL); - switch (pr->status) { - case CMD_PARSE_ERROR: - cause = pr->error; - goto error; - case CMD_PARSE_SUCCESS: - break; - } - args_free_values(values, argc); - free(values); - cmd_free_argv(argc, argv); - if ((c->flags & CLIENT_READONLY) && - !cmd_list_all_have(pr->cmdlist, CMD_READONLY)) + !cmd_list_all_have(cmdlist, CMD_READONLY)) new_item = cmdq_get_callback(server_client_read_only, NULL); else - new_item = cmdq_get_command(pr->cmdlist, NULL); + new_item = cmdq_get_command(cmdlist, NULL); cmdq_append(c, new_item); cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL)); - cmd_list_free(pr->cmdlist); + cmd_list_free(cmdlist); return; error: diff --git a/tmux.1 b/tmux.1 index 1c10fc5c..33410173 100644 --- a/tmux.1 +++ b/tmux.1 @@ -252,9 +252,10 @@ was given) and off. This specifies one of a set of commands used to control .Nm , as described in the following sections. -If no commands are specified, the -.Ic new-session -command is assumed. +If no commands are specified, the command in +.Ic default-client-command +is assumed, which defaults to +.Ic new-session . .El .Sh DEFAULT KEY BINDINGS .Nm @@ -4083,6 +4084,10 @@ where the number is a hexadecimal number. Give the command to pipe to if the .Ic copy-pipe copy mode command is used without arguments. +.It Ic default-client-command Ar command +Set the default command to run when tmux is called without a command. +The default is +.Ic new-session . .It Ic default-terminal Ar terminal Set the default terminal for new windows created in this session - the default value of the diff --git a/tmux.h b/tmux.h index fcb064dd..5852b2db 100644 --- a/tmux.h +++ b/tmux.h @@ -2359,10 +2359,13 @@ struct options_entry *options_match_get(struct options *, const char *, int *, int, int *); const char *options_get_string(struct options *, const char *); long long options_get_number(struct options *, const char *); +const struct cmd_list *options_get_command(struct options *, const char *); struct options_entry * printflike(4, 5) options_set_string(struct options *, const char *, int, const char *, ...); struct options_entry *options_set_number(struct options *, const char *, long long); +struct options_entry *options_set_command(struct options *, const char *, + struct cmd_list *); int options_scope_from_name(struct args *, int, const char *, struct cmd_find_state *, struct options **, char **); @@ -2636,12 +2639,12 @@ struct cmd *cmd_copy(struct cmd *, int, char **); void cmd_free(struct cmd *); char *cmd_print(struct cmd *); struct cmd_list *cmd_list_new(void); -struct cmd_list *cmd_list_copy(struct cmd_list *, int, char **); +struct cmd_list *cmd_list_copy(const struct cmd_list *, int, char **); void cmd_list_append(struct cmd_list *, struct cmd *); void cmd_list_append_all(struct cmd_list *, struct cmd_list *); void cmd_list_move(struct cmd_list *, struct cmd_list *); void cmd_list_free(struct cmd_list *); -char *cmd_list_print(struct cmd_list *, int); +char *cmd_list_print(const struct cmd_list *, int); struct cmd *cmd_list_first(struct cmd_list *); struct cmd *cmd_list_next(struct cmd *); int cmd_list_all_have(struct cmd_list *, int); From 34a35b0f0954de47592b7d68bb8555b864f439c5 Mon Sep 17 00:00:00 2001 From: nicm <nicm> Date: Mon, 24 Mar 2025 20:13:03 +0000 Subject: [PATCH 2/3] Expand formats with the pane modifier in tree mode so that #() doesn't always use the same value. From Michael Grant in GitHub issues 4412 and 4420. --- window-tree.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/window-tree.c b/window-tree.c index af171956..bffbeec3 100644 --- a/window-tree.c +++ b/window-tree.c @@ -302,6 +302,7 @@ window_tree_build_pane(struct session *s, struct winlink *wl, struct window_tree_itemdata *item; char *name, *text; u_int idx; + struct format_tree *ft; window_pane_index(wp, &idx); @@ -311,8 +312,11 @@ window_tree_build_pane(struct session *s, struct winlink *wl, item->winlink = wl->idx; item->pane = wp->id; - text = format_single(NULL, data->format, NULL, s, wl, wp); + ft = format_create(NULL, NULL, FORMAT_PANE|wp->id, 0); + format_defaults(ft, NULL, s, wl, wp); + text = format_expand(ft, data->format); xasprintf(&name, "%u", idx); + format_free(ft); mode_tree_add(data->data, parent, item, (uint64_t)wp, name, text, -1); free(text); @@ -348,6 +352,7 @@ window_tree_build_window(struct session *s, struct winlink *wl, struct window_pane *wp, **l; u_int n, i; int expanded; + struct format_tree *ft; item = window_tree_add_item(data); item->type = WINDOW_TREE_WINDOW; @@ -355,8 +360,11 @@ window_tree_build_window(struct session *s, struct winlink *wl, item->winlink = wl->idx; item->pane = -1; - text = format_single(NULL, data->format, NULL, s, wl, NULL); + ft = format_create(NULL, NULL, FORMAT_PANE|wl->window->active->id, 0); + format_defaults(ft, NULL, s, wl, NULL); + text = format_expand(ft, data->format); xasprintf(&name, "%u", wl->idx); + format_free(ft); if (data->type == WINDOW_TREE_SESSION || data->type == WINDOW_TREE_WINDOW) @@ -373,7 +381,6 @@ window_tree_build_window(struct session *s, struct winlink *wl, if (TAILQ_NEXT(wp, entry) == NULL) { if (!window_tree_filter_pane(s, wl, wp, filter)) goto empty; - return (1); } l = NULL; @@ -411,9 +418,10 @@ window_tree_build_session(struct session *s, void *modedata, struct window_tree_itemdata *item; struct mode_tree_item *mti; char *text; - struct winlink *wl, **l; + struct winlink *wl = s->curw, **l; u_int n, i, empty; int expanded; + struct format_tree *ft; item = window_tree_add_item(data); item->type = WINDOW_TREE_SESSION; @@ -421,7 +429,10 @@ window_tree_build_session(struct session *s, void *modedata, item->winlink = -1; item->pane = -1; - text = format_single(NULL, data->format, NULL, s, NULL, NULL); + ft = format_create(NULL, NULL, FORMAT_PANE|wl->window->active->id, 0); + format_defaults(ft, NULL, s, NULL, NULL); + text = format_expand(ft, data->format); + format_free(ft); if (data->type == WINDOW_TREE_SESSION) expanded = 0; From 483b2b3edb0d01cde0aa3fa6aa7a02bdd90b8026 Mon Sep 17 00:00:00 2001 From: nicm <nicm> Date: Mon, 24 Mar 2025 20:17:24 +0000 Subject: [PATCH 3/3] Correctly skip wide characters in hyperlinks, from someone in GitHub issue 4425. --- format.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/format.c b/format.c index 13e7f149..a454a031 100644 --- a/format.c +++ b/format.c @@ -5428,9 +5428,14 @@ format_grid_hyperlink(struct grid *gd, u_int x, u_int y, struct screen* s) const char *uri; struct grid_cell gc; - grid_get_cell(gd, x, y, &gc); - if (gc.flags & GRID_FLAG_PADDING) - return (NULL); + for (;;) { + grid_get_cell(gd, x, y, &gc); + if (~gc.flags & GRID_FLAG_PADDING) + break; + if (x == 0) + return (NULL); + x--; + } if (s->hyperlinks == NULL || gc.link == 0) return (NULL); if (!hyperlinks_get(s->hyperlinks, gc.link, &uri, NULL, NULL))