From d67245c734c9c600ad6d186570a1230aa21b80c8 Mon Sep 17 00:00:00 2001 From: nicm Date: Sat, 16 May 2020 16:02:24 +0000 Subject: [PATCH] Add a customize mode where keys and options may be browsed and changed, includes adding a brief description of each option. Bound to "C" by default. --- Makefile | 1 + cmd-choose-tree.c | 17 +- cmd-list-keys.c | 8 +- cmd-run-shell.c | 3 +- cmd-set-option.c | 216 +------ cmd-show-messages.c | 4 + cmd-show-options.c | 8 +- cmd.c | 2 + format-draw.c | 2 +- format.c | 2 +- key-bindings.c | 60 +- mode-tree.c | 97 ++- options-table.c | 358 ++++++++--- options.c | 252 +++++++- screen-write.c | 92 ++- style.c | 7 +- tmux.1 | 47 ++ tmux.h | 33 +- window-buffer.c | 4 +- window-client.c | 2 +- window-customize.c | 1430 +++++++++++++++++++++++++++++++++++++++++++ window-tree.c | 2 +- 22 files changed, 2284 insertions(+), 363 deletions(-) create mode 100644 window-customize.c diff --git a/Makefile b/Makefile index 0f1c94c4..7fb664ad 100644 --- a/Makefile +++ b/Makefile @@ -120,6 +120,7 @@ SRCS= alerts.c \ window-client.c \ window-clock.c \ window-copy.c \ + window-customize.c \ window-tree.c \ window.c \ xmalloc.c \ diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c index 0ada8fd4..a58469ac 100644 --- a/cmd-choose-tree.c +++ b/cmd-choose-tree.c @@ -68,6 +68,19 @@ const struct cmd_entry cmd_choose_buffer_entry = { .exec = cmd_choose_tree_exec }; +const struct cmd_entry cmd_customize_mode_entry = { + .name = "customize-mode", + .alias = NULL, + + .args = { "F:f:Nt:Z", 0, 0 }, + .usage = "[-NZ] [-F format] [-f filter] " CMD_TARGET_PANE_USAGE, + + .target = { 't', CMD_FIND_PANE, 0 }, + + .flags = 0, + .exec = cmd_choose_tree_exec +}; + static enum cmd_retval cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) { @@ -84,7 +97,9 @@ cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item) if (server_client_how_many() == 0) return (CMD_RETURN_NORMAL); mode = &window_client_mode; - } else + } else if (cmd_get_entry(self) == &cmd_customize_mode_entry) + mode = &window_customize_mode; + else mode = &window_tree_mode; window_pane_set_mode(wp, NULL, mode, target, args); diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 4e7ba39c..60ef73af 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -68,7 +68,8 @@ cmd_list_keys_get_width(const char *tablename, key_code only) while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - bd->note == NULL) { + bd->note == NULL || + *bd->note == '\0') { bd = key_bindings_next(table, bd); continue; } @@ -99,14 +100,15 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args, while (bd != NULL) { if ((only != KEYC_UNKNOWN && bd->key != only) || KEYC_IS_MOUSE(bd->key) || - (bd->note == NULL && !args_has(args, 'a'))) { + ((bd->note == NULL || *bd->note == '\0') && + !args_has(args, 'a'))) { bd = key_bindings_next(table, bd); continue; } found = 1; key = key_string_lookup_key(bd->key); - if (bd->note == NULL) + if (bd->note == NULL || *bd->note == '\0') note = cmd_list_print(bd->cmdlist, 1); else note = xstrdup(bd->note); diff --git a/cmd-run-shell.c b/cmd-run-shell.c index 82d4a1a2..3792aaae 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -190,7 +190,8 @@ cmd_run_shell_callback(struct job *job) retcode = WTERMSIG(status); xasprintf(&msg, "'%s' terminated by signal %d", cmd, retcode); retcode += 128; - } + } else + retcode = 0; if (msg != NULL) cmd_run_shell_print(job, msg); free(msg); diff --git a/cmd-set-option.c b/cmd-set-option.c index e04aa7ff..c6f83796 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -30,15 +30,6 @@ static enum cmd_retval cmd_set_option_exec(struct cmd *, struct cmdq_item *); -static int cmd_set_option_set(struct cmd *, struct cmdq_item *, - struct options *, struct options_entry *, const char *); -static int cmd_set_option_flag(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); -static int cmd_set_option_choice(struct cmdq_item *, - const struct options_table_entry *, struct options *, - const char *); - const struct cmd_entry cmd_set_option_entry = { .name = "set-option", .alias = "set", @@ -84,10 +75,6 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) struct args *args = cmd_get_args(self); int append = args_has(args, 'a'); struct cmd_find_state *target = cmdq_get_target(item); - struct client *loop; - struct session *s = target->s; - struct window *w; - struct window_pane *wp; struct options *oo; struct options_entry *parent, *o; char *name, *argument, *value = NULL, *cause; @@ -138,7 +125,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) parent = options_get(oo, name); /* Check that array options and indexes match up. */ - if (idx != -1 && (*name == '@' || !options_isarray(parent))) { + if (idx != -1 && (*name == '@' || !options_is_array(parent))) { cmdq_error(item, "not an array: %s", argument); goto fail; } @@ -185,10 +172,15 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) goto fail; } options_set_string(oo, name, append, "%s", value); - } else if (idx == -1 && !options_isarray(parent)) { - error = cmd_set_option_set(self, item, oo, parent, value); - if (error != 0) + } else if (idx == -1 && !options_is_array(parent)) { + error = options_from_string(oo, options_table_entry(parent), + options_table_entry(parent)->name, value, + args_has(args, 'a'), &cause); + if (error != 0) { + cmdq_error(item, "%s", cause); + free(cause); goto fail; + } } else { if (value == NULL) { cmdq_error(item, "empty value"); @@ -212,51 +204,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) } } - /* Update timers and so on for various options. */ - if (strcmp(name, "automatic-rename") == 0) { - RB_FOREACH(w, windows, &windows) { - if (w->active == NULL) - continue; - if (options_get_number(w->options, "automatic-rename")) - w->active->flags |= PANE_CHANGED; - } - } - if (strcmp(name, "key-table") == 0) { - TAILQ_FOREACH(loop, &clients, entry) - server_client_set_key_table(loop, NULL); - } - if (strcmp(name, "user-keys") == 0) { - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->tty.flags & TTY_OPENED) - tty_keys_build(&loop->tty); - } - } - if (strcmp(name, "status") == 0 || - strcmp(name, "status-interval") == 0) - status_timer_start_all(); - if (strcmp(name, "monitor-silence") == 0) - alerts_reset_all(); - if (strcmp(name, "window-style") == 0 || - strcmp(name, "window-active-style") == 0) { - RB_FOREACH(wp, window_pane_tree, &all_window_panes) - wp->flags |= PANE_STYLECHANGED; - } - if (strcmp(name, "pane-border-status") == 0) { - RB_FOREACH(w, windows, &windows) - layout_fix_panes(w); - } - RB_FOREACH(s, sessions, &sessions) - status_update_cache(s); - - /* - * Update sizes and redraw. May not always be necessary but do it - * anyway. - */ - recalculate_sizes(); - TAILQ_FOREACH(loop, &clients, entry) { - if (loop->session != NULL) - server_redraw_client(loop); - } + options_push_changes(name); out: free(argument); @@ -270,147 +218,3 @@ fail: free(name); return (CMD_RETURN_ERROR); } - -static int -cmd_set_option_check_string(const struct options_table_entry *oe, - const char *value, char **cause) -{ - struct style sy; - - if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { - xasprintf(cause, "not a suitable shell: %s", value); - return (-1); - } - if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { - xasprintf(cause, "value is invalid: %s", value); - return (-1); - } - if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && - strstr(value, "#{") == NULL && - style_parse(&sy, &grid_default_cell, value) != 0) { - xasprintf(cause, "invalid style: %s", value); - return (-1); - } - return (0); -} - -static int -cmd_set_option_set(struct cmd *self, struct cmdq_item *item, struct options *oo, - struct options_entry *parent, const char *value) -{ - const struct options_table_entry *oe; - struct args *args = cmd_get_args(self); - int append = args_has(args, 'a'); - long long number; - const char *errstr, *new; - char *old, *cause; - key_code key; - - oe = options_table_entry(parent); - if (value == NULL && - oe->type != OPTIONS_TABLE_FLAG && - oe->type != OPTIONS_TABLE_CHOICE) { - cmdq_error(item, "empty value"); - return (-1); - } - - switch (oe->type) { - case OPTIONS_TABLE_STRING: - old = xstrdup(options_get_string(oo, oe->name)); - options_set_string(oo, oe->name, append, "%s", value); - new = options_get_string(oo, oe->name); - if (cmd_set_option_check_string(oe, new, &cause) != 0) { - cmdq_error(item, "%s", cause); - free(cause); - - options_set_string(oo, oe->name, 0, "%s", old); - free(old); - return (-1); - } - free(old); - return (0); - case OPTIONS_TABLE_NUMBER: - number = strtonum(value, oe->minimum, oe->maximum, &errstr); - if (errstr != NULL) { - cmdq_error(item, "value is %s: %s", errstr, value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_KEY: - key = key_string_lookup_string(value); - if (key == KEYC_UNKNOWN) { - cmdq_error(item, "bad key: %s", value); - return (-1); - } - options_set_number(oo, oe->name, key); - return (0); - case OPTIONS_TABLE_COLOUR: - if ((number = colour_fromstring(value)) == -1) { - cmdq_error(item, "bad colour: %s", value); - return (-1); - } - options_set_number(oo, oe->name, number); - return (0); - case OPTIONS_TABLE_FLAG: - return (cmd_set_option_flag(item, oe, oo, value)); - case OPTIONS_TABLE_CHOICE: - return (cmd_set_option_choice(item, oe, oo, value)); - case OPTIONS_TABLE_COMMAND: - break; - } - return (-1); -} - -static int -cmd_set_option_flag(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - int flag; - - if (value == NULL || *value == '\0') - flag = !options_get_number(oo, oe->name); - else if (strcmp(value, "1") == 0 || - strcasecmp(value, "on") == 0 || - strcasecmp(value, "yes") == 0) - flag = 1; - else if (strcmp(value, "0") == 0 || - strcasecmp(value, "off") == 0 || - strcasecmp(value, "no") == 0) - flag = 0; - else { - cmdq_error(item, "bad value: %s", value); - return (-1); - } - options_set_number(oo, oe->name, flag); - return (0); -} - -static int -cmd_set_option_choice(struct cmdq_item *item, - const struct options_table_entry *oe, struct options *oo, - const char *value) -{ - const char **cp; - int n, choice = -1; - - if (value == NULL) { - choice = options_get_number(oo, oe->name); - if (choice < 2) - choice = !choice; - } else { - n = 0; - for (cp = oe->choices; *cp != NULL; cp++) { - if (strcmp(*cp, value) == 0) - choice = n; - n++; - } - if (choice == -1) { - cmdq_error(item, "unknown value: %s", value); - return (-1); - } - } - options_set_number(oo, oe->name, choice); - return (0); -} diff --git a/cmd-show-messages.c b/cmd-show-messages.c index 2fa68f2a..deb0487c 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -18,6 +18,7 @@ #include +#include #include #include #include @@ -29,6 +30,9 @@ * Show client message log. */ +#define SHOW_MESSAGES_TEMPLATE \ + "#{t/p:message_time}: #{message_text}" + static enum cmd_retval cmd_show_messages_exec(struct cmd *, struct cmdq_item *); diff --git a/cmd-show-options.c b/cmd-show-options.c index e2aec930..8e70eaa9 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -151,7 +151,7 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, xasprintf(&tmp, "%s[%d]", name, idx); name = tmp; } else { - if (options_isarray(o)) { + if (options_is_array(o)) { a = options_array_first(o); if (a == NULL) { if (!args_has(args, 'v')) @@ -168,10 +168,10 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, } } - value = options_tostring(o, idx, 0); + value = options_to_string(o, idx, 0); if (args_has(args, 'v')) cmdq_print(item, "%s", value); - else if (options_isstring(o)) { + else if (options_is_string(o)) { escaped = args_escape(value); if (parent) cmdq_print(item, "%s* %s", name, escaped); @@ -229,7 +229,7 @@ cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, } else parent = 0; - if (!options_isarray(o)) + if (!options_is_array(o)) cmd_show_options_print(self, item, o, -1, parent); else if ((a = options_array_first(o)) == NULL) { if (!args_has(args, 'v')) { diff --git a/cmd.c b/cmd.c index bc807cbe..8a977023 100644 --- a/cmd.c +++ b/cmd.c @@ -40,6 +40,7 @@ extern const struct cmd_entry cmd_clock_mode_entry; extern const struct cmd_entry cmd_command_prompt_entry; extern const struct cmd_entry cmd_confirm_before_entry; extern const struct cmd_entry cmd_copy_mode_entry; +extern const struct cmd_entry cmd_customize_mode_entry; extern const struct cmd_entry cmd_delete_buffer_entry; extern const struct cmd_entry cmd_detach_client_entry; extern const struct cmd_entry cmd_display_menu_entry; @@ -130,6 +131,7 @@ const struct cmd_entry *cmd_table[] = { &cmd_command_prompt_entry, &cmd_confirm_before_entry, &cmd_copy_mode_entry, + &cmd_customize_mode_entry, &cmd_delete_buffer_entry, &cmd_detach_client_entry, &cmd_display_menu_entry, diff --git a/format-draw.c b/format-draw.c index 3751082e..bd32b2a8 100644 --- a/format-draw.c +++ b/format-draw.c @@ -547,7 +547,7 @@ format_draw(struct screen_write_ctx *octx, const struct grid_cell *base, */ cp = expanded; while (*cp != '\0') { - if (cp[0] != '#' || cp[1] != '[') { + if (cp[0] != '#' || cp[1] != '[' || sy.ignore) { /* See if this is a UTF-8 character. */ if ((more = utf8_open(ud, *cp)) == UTF8_MORE) { while (*++cp != '\0' && more == UTF8_MORE) diff --git a/format.c b/format.c index 9cc8195c..c7f7b90a 100644 --- a/format.c +++ b/format.c @@ -1392,7 +1392,7 @@ format_find(struct format_tree *ft, const char *key, int modifiers, if (o == NULL) o = options_parse_get(global_s_options, key, &idx, 0); if (o != NULL) { - found = options_tostring(o, idx, 1); + found = options_to_string(o, idx, 1); goto found; } diff --git a/key-bindings.c b/key-bindings.c index 85bfb788..ecd2f7dc 100644 --- a/key-bindings.c +++ b/key-bindings.c @@ -89,9 +89,8 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2) } static void -key_bindings_free(struct key_table *table, struct key_binding *bd) +key_bindings_free(struct key_binding *bd) { - RB_REMOVE(key_bindings, &table->key_bindings, bd); cmd_list_free(bd->cmdlist); free((void *)bd->note); free(bd); @@ -110,6 +109,7 @@ key_bindings_get_table(const char *name, int create) table = xmalloc(sizeof *table); table->name = xstrdup(name); RB_INIT(&table->key_bindings); + RB_INIT(&table->default_key_bindings); table->references = 1; /* one reference in key_tables */ RB_INSERT(key_tables, &key_tables, table); @@ -138,8 +138,14 @@ key_bindings_unref_table(struct key_table *table) if (--table->references != 0) return; - RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) - key_bindings_free(table, bd); + RB_FOREACH_SAFE(bd, key_bindings, &table->key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } + RB_FOREACH_SAFE(bd, key_bindings, &table->default_key_bindings, bd1) { + RB_REMOVE(key_bindings, &table->default_key_bindings, bd); + key_bindings_free(bd); + } free((void *)table->name); free(table); @@ -154,6 +160,15 @@ key_bindings_get(struct key_table *table, key_code key) return (RB_FIND(key_bindings, &table->key_bindings, &bd)); } +struct key_binding * +key_bindings_get_default(struct key_table *table, key_code key) +{ + struct key_binding bd; + + bd.key = key; + return (RB_FIND(key_bindings, &table->default_key_bindings, &bd)); +} + struct key_binding * key_bindings_first(struct key_table *table) { @@ -176,8 +191,10 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat, table = key_bindings_get_table(name, 1); bd = key_bindings_get(table, key & ~KEYC_XTERM); - if (bd != NULL) - key_bindings_free(table, bd); + if (bd != NULL) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + } bd = xcalloc(1, sizeof *bd); bd->key = key; @@ -203,9 +220,12 @@ key_bindings_remove(const char *name, key_code key) bd = key_bindings_get(table, key & ~KEYC_XTERM); if (bd == NULL) return; - key_bindings_free(table, bd); - if (RB_EMPTY(&table->key_bindings)) { + RB_REMOVE(key_bindings, &table->key_bindings, bd); + key_bindings_free(bd); + + if (RB_EMPTY(&table->key_bindings) && + RB_EMPTY(&table->default_key_bindings)) { RB_REMOVE(key_tables, &key_tables, table); key_bindings_unref_table(table); } @@ -228,6 +248,28 @@ key_bindings_remove_table(const char *name) } } +static enum cmd_retval +key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data) +{ + struct key_table *table; + struct key_binding *bd, *new_bd; + + RB_FOREACH(table, key_tables, &key_tables) { + RB_FOREACH(bd, key_bindings, &table->key_bindings) { + new_bd = xcalloc(1, sizeof *bd); + new_bd->key = bd->key; + if (bd->note != NULL) + new_bd->note = xstrdup(bd->note); + new_bd->flags = bd->flags; + new_bd->cmdlist = bd->cmdlist; + new_bd->cmdlist->references++; + RB_INSERT(key_bindings, &table->default_key_bindings, + new_bd); + } + } + return (CMD_RETURN_NORMAL); +} + void key_bindings_init(void) { @@ -278,6 +320,7 @@ key_bindings_init(void) "bind -N 'Toggle the marked pane' m select-pane -m", "bind -N 'Select the next window' n next-window", "bind -N 'Select the next pane' o select-pane -t:.+", + "bind -N 'Customize options' C customize-mode -Z", "bind -N 'Select the previous pane' p previous-window", "bind -N 'Display pane numbers' q display-panes", "bind -N 'Redraw the current client' r refresh-client", @@ -524,6 +567,7 @@ key_bindings_init(void) cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL)); cmd_list_free(pr->cmdlist); } + cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL)); } static enum cmd_retval diff --git a/mode-tree.c b/mode-tree.c index b3b3f335..131830d6 100644 --- a/mode-tree.c +++ b/mode-tree.c @@ -45,6 +45,7 @@ struct mode_tree_data { mode_tree_draw_cb drawcb; mode_tree_search_cb searchcb; mode_tree_menu_cb menucb; + mode_tree_height_cb heightcb; struct mode_tree_list children; struct mode_tree_list saved; @@ -79,6 +80,7 @@ struct mode_tree_item { int expanded; int tagged; + int draw_as_parent; struct mode_tree_list children; TAILQ_ENTRY(mode_tree_item) entry; @@ -210,7 +212,7 @@ mode_tree_clear_tagged(struct mode_tree_list *mtl) } } -static void +void mode_tree_up(struct mode_tree_data *mtd, int wrap) { if (mtd->current == 0) { @@ -247,6 +249,12 @@ mode_tree_get_current(struct mode_tree_data *mtd) return (mtd->line_list[mtd->current].item->itemdata); } +const char * +mode_tree_get_current_name(struct mode_tree_data *mtd) +{ + return (mtd->line_list[mtd->current].item->name); +} + void mode_tree_expand_current(struct mode_tree_data *mtd) { @@ -256,6 +264,15 @@ mode_tree_expand_current(struct mode_tree_data *mtd) } } +void +mode_tree_collapse_current(struct mode_tree_data *mtd) +{ + if (mtd->line_list[mtd->current].item->expanded) { + mtd->line_list[mtd->current].item->expanded = 0; + mode_tree_build(mtd); + } +} + static int mode_tree_get_tag(struct mode_tree_data *mtd, uint64_t tag, u_int *found) { @@ -343,7 +360,8 @@ mode_tree_each_tagged(struct mode_tree_data *mtd, mode_tree_each_cb cb, struct mode_tree_data * mode_tree_start(struct window_pane *wp, struct args *args, mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb, - mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, void *modedata, + mode_tree_search_cb searchcb, mode_tree_menu_cb menucb, + mode_tree_height_cb heightcb, void *modedata, const struct menu_item *menu, const char **sort_list, u_int sort_size, struct screen **s) { @@ -381,6 +399,7 @@ mode_tree_start(struct window_pane *wp, struct args *args, mtd->drawcb = drawcb; mtd->searchcb = searchcb; mtd->menucb = menucb; + mtd->heightcb = heightcb; TAILQ_INIT(&mtd->children); @@ -404,6 +423,27 @@ mode_tree_zoom(struct mode_tree_data *mtd, struct args *args) mtd->zoomed = -1; } +static void +mode_tree_set_height(struct mode_tree_data *mtd) +{ + struct screen *s = &mtd->screen; + u_int height; + + if (mtd->heightcb != NULL) { + height = mtd->heightcb(mtd, screen_size_y(s)); + if (height < screen_size_y(s)) + mtd->height = screen_size_y(s) - height; + } else { + mtd->height = (screen_size_y(s) / 3) * 2; + if (mtd->height > mtd->line_size) + mtd->height = screen_size_y(s) / 2; + } + if (mtd->height < 10) + mtd->height = screen_size_y(s); + if (screen_size_y(s) - mtd->height < 2) + mtd->height = screen_size_y(s); +} + void mode_tree_build(struct mode_tree_data *mtd) { @@ -434,15 +474,9 @@ mode_tree_build(struct mode_tree_data *mtd) mode_tree_set_current(mtd, tag); mtd->width = screen_size_x(s); - if (mtd->preview) { - mtd->height = (screen_size_y(s) / 3) * 2; - if (mtd->height > mtd->line_size) - mtd->height = screen_size_y(s) / 2; - if (mtd->height < 10) - mtd->height = screen_size_y(s); - if (screen_size_y(s) - mtd->height < 2) - mtd->height = screen_size_y(s); - } else + if (mtd->preview) + mode_tree_set_height(mtd); + else mtd->height = screen_size_y(s); mode_tree_check_selected(mtd); } @@ -494,7 +528,7 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, struct mode_tree_item *mti, *saved; log_debug("%s: %llu, %s %s", __func__, (unsigned long long)tag, - name, text); + name, (text == NULL ? "" : text)); mti = xcalloc(1, sizeof *mti); mti->parent = parent; @@ -502,7 +536,8 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, mti->tag = tag; mti->name = xstrdup(name); - mti->text = xstrdup(text); + if (text != NULL) + mti->text = xstrdup(text); saved = mode_tree_find_item(&mtd->saved, tag); if (saved != NULL) { @@ -524,6 +559,12 @@ mode_tree_add(struct mode_tree_data *mtd, struct mode_tree_item *parent, return (mti); } +void +mode_tree_draw_as_parent(struct mode_tree_item *mti) +{ + mti->draw_as_parent = 1; +} + void mode_tree_remove(struct mode_tree_data *mtd, struct mode_tree_item *mti) { @@ -621,8 +662,8 @@ mode_tree_draw(struct mode_tree_data *mtd) tag = "*"; else tag = ""; - xasprintf(&text, "%-*s%s%s%s: ", keylen, key, start, mti->name, - tag); + xasprintf(&text, "%-*s%s%s%s%s", keylen, key, start, mti->name, + tag, (mti->text != NULL) ? ": " : "" ); width = utf8_cstrwidth(text); if (width > w) width = w; @@ -636,11 +677,17 @@ mode_tree_draw(struct mode_tree_data *mtd) if (i != mtd->current) { screen_write_clearendofline(&ctx, 8); screen_write_nputs(&ctx, w, &gc0, "%s", text); - format_draw(&ctx, &gc0, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc0, w - width, mti->text, + NULL); + } } else { screen_write_clearendofline(&ctx, gc.bg); screen_write_nputs(&ctx, w, &gc, "%s", text); - format_draw(&ctx, &gc, w - width, mti->text, NULL); + if (mti->text != NULL) { + format_draw(&ctx, &gc, w - width, mti->text, + NULL); + } } free(text); @@ -658,13 +705,18 @@ mode_tree_draw(struct mode_tree_data *mtd) line = &mtd->line_list[mtd->current]; mti = line->item; + if (mti->draw_as_parent) + mti = mti->parent; screen_write_cursormove(&ctx, 0, h, 0); screen_write_box(&ctx, w, sy - h); - xasprintf(&text, " %s (sort: %s%s)", mti->name, - mtd->sort_list[mtd->sort_crit.field], - mtd->sort_crit.reversed ? ", reversed" : ""); + if (mtd->sort_list != NULL) { + xasprintf(&text, " %s (sort: %s%s)", mti->name, + mtd->sort_list[mtd->sort_crit.field], + mtd->sort_crit.reversed ? ", reversed" : ""); + } else + xasprintf(&text, " %s", mti->name); if (w - 2 >= strlen(text)) { screen_write_cursormove(&ctx, 1, h, 0); screen_write_puts(&ctx, &gc0, "%s", text); @@ -680,7 +732,8 @@ mode_tree_draw(struct mode_tree_data *mtd) else screen_write_puts(&ctx, &gc0, "active"); screen_write_puts(&ctx, &gc0, ") "); - } + } else + screen_write_puts(&ctx, &gc0, " "); } free(text); @@ -1027,7 +1080,7 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key, break; case 'O': mtd->sort_crit.field++; - if (mtd->sort_crit.field == mtd->sort_size) + if (mtd->sort_crit.field >= mtd->sort_size) mtd->sort_crit.field = 0; mode_tree_build(mtd); break; diff --git a/options-table.c b/options-table.c index 320a380f..55eb5b9f 100644 --- a/options-table.c +++ b/options-table.c @@ -103,9 +103,9 @@ static const char *options_table_window_size_list[] = { "," \ "#[range=window|#{window_index} list=focus " \ "#{?#{!=:#{window-status-current-style},default}," \ - "#{window-status-current-style}," \ - "#{window-status-style}" \ - "}" \ + "#{window-status-current-style}," \ + "#{window-status-style}" \ + "}" \ "#{?#{&&:#{window_last_flag}," \ "#{!=:#{window-status-last-style},default}}, " \ "#{window-status-last-style}," \ @@ -175,6 +175,7 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SERVER, .default_num = '\177', + .text = "The key to send for backspace." }, { .name = "buffer-limit", @@ -182,7 +183,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 1, .maximum = INT_MAX, - .default_num = 50 + .default_num = 50, + .text = "The maximum number of automatic buffers. " + "When this is reached, the oldest buffer is deleted." }, { .name = "command-alias", @@ -195,25 +198,31 @@ const struct options_table_entry options_table[] = { "info=show-messages -JT," "choose-window=choose-tree -w," "choose-session=choose-tree -s", - .separator = "," + .separator = ",", + .text = "Array of command aliases. " + "Each entry is an alias and a command separated by '='." }, { .name = "copy-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "" + .default_str = "", + .text = "Shell command run when text is copied. " + "If empty, no command is run." }, { .name = "default-terminal", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "screen" + .default_str = "screen", + .text = "Default for the 'TERM' environment variable." }, { .name = "editor", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = _PATH_VI + .default_str = _PATH_VI, + .text = "Editor run to edit files." }, { .name = "escape-time", @@ -221,31 +230,38 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 500 + .default_num = 500, + .text = "Time to wait before assuming a key is Escape." }, { .name = "exit-empty", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 1 + .default_num = 1, + .text = "Whether the server should exit if there are no sessions." }, { .name = "exit-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether the server should exit if there are no attached " + "clients." }, { .name = "focus-events", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SERVER, - .default_num = 0 + .default_num = 0, + .text = "Whether to send focus events to applications." }, { .name = "history-file", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SERVER, - .default_str = "" + .default_str = "", + .text = "Location of the command prompt history file. " + "Empty does not write a history file." }, { .name = "message-limit", @@ -253,14 +269,18 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .minimum = 0, .maximum = INT_MAX, - .default_num = 1000 + .default_num = 1000, + .text = "Maximum number of server messages to keep." }, { .name = "set-clipboard", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SERVER, .choices = options_table_set_clipboard_list, - .default_num = 1 + .default_num = 1, + .text = "Whether to attempt to set the system clipboard ('on' or " + "'external') and whether to allow applications to create " + "paste buffers with an escape sequence ('on' only)." }, { .name = "terminal-overrides", @@ -268,7 +288,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", - .separator = "," + .separator = ",", + .text = "List of terminal capabilities overrides." }, { .name = "terminal-features", @@ -276,8 +297,10 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "xterm*:clipboard:ccolour:cstyle:title," - "screen*:title", - .separator = "," + "screen*:title", + .separator = ",", + .text = "List of terminal features, used if they cannot be " + "automatically detected." }, { .name = "user-keys", @@ -285,7 +308,10 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SERVER, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "", - .separator = "," + .separator = ",", + .text = "User key assignments. " + "Each sequence in the list is translated into a key: " + "'User0', 'User1' and so on." }, /* Session options. */ @@ -293,7 +319,8 @@ const struct options_table_entry options_table[] = { .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on an activity alert." }, { .name = "assume-paste-time", @@ -302,6 +329,9 @@ const struct options_table_entry options_table[] = { .minimum = 0, .maximum = INT_MAX, .default_num = 1, + .unit = "milliseconds", + .text = "Maximum time between input to assume it pasting rather " + "than typing." }, { .name = "base-index", @@ -309,57 +339,69 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Default index of the first window in each session." }, { .name = "bell-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_ANY + .default_num = ALERT_ANY, + .text = "Action to take on a bell alert." }, { .name = "default-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "" + .default_str = "", + .text = "Default command to run in new panes. If empty, a shell is " + "started." }, { .name = "default-shell", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = _PATH_BSHELL + .default_str = _PATH_BSHELL, + .text = "Location of default shell." }, { .name = "default-size", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .pattern = "[0-9]*x[0-9]*", - .default_str = "80x24" + .default_str = "80x24", + .text = "Initial size of new sessions." }, { .name = "destroy-unattached", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to destroy sessions when they have no attached " + "clients." }, { .name = "detach-on-destroy", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .default_num = 1, + .text = "Whether to detach when a session is destroyed, or switch " + "the client to another session if any exist." }, { .name = "display-panes-active-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 1 + .default_num = 1, + .text = "Colour of the active pane for 'display-panes'." }, { .name = "display-panes-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, - .default_num = 4 + .default_num = 4, + .text = "Colour of not active panes for 'display-panes'." }, { .name = "display-panes-time", @@ -367,7 +409,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 1, .maximum = INT_MAX, - .default_num = 1000 + .default_num = 1000, + .unit = "milliseconds", + .text = "Time for which 'display-panes' should show pane numbers." }, { .name = "display-time", @@ -375,7 +419,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 750 + .default_num = 750, + .unit = "milliseconds", + .text = "Time for which status line messages should appear." }, { .name = "history-limit", @@ -383,13 +429,19 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 2000 + .default_num = 2000, + .unit = "lines", + .text = "Maximum number of lines to keep in the history for each " + "pane. " + "If changed, the new value applies only to new panes." }, { .name = "key-table", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "root" + .default_str = "root", + .text = "Default key table. " + "Key presses are first looked up in this table." }, { .name = "lock-after-time", @@ -397,13 +449,16 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .unit = "seconds", + .text = "Time after which a client is locked if not used." }, { .name = "lock-command", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "lock -np" + .default_str = "lock -np", + .text = "Shell command to run to lock a client." }, { .name = "message-command-style", @@ -411,7 +466,9 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=black,fg=yellow", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the command prompt when in command mode, if " + "'mode-keys' is set to 'vi'." }, { .name = "message-style", @@ -419,31 +476,39 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the command prompt." }, { .name = "mouse", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether the mouse is recognised and mouse key bindings are " + "executed. " + "Applications inside panes can use the mouse even when 'off'." }, { .name = "prefix", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = '\002', + .text = "The prefix key." }, { .name = "prefix2", .type = OPTIONS_TABLE_KEY, .scope = OPTIONS_TABLE_SESSION, .default_num = KEYC_NONE, + .text = "A second prefix key." }, { .name = "renumber-windows", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether windows are automatically renumbered rather than " + "leaving gaps." }, { .name = "repeat-time", @@ -451,45 +516,56 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 500 + .default_num = 500, + .unit = "milliseconds", + .text = "Time to wait for a key binding to repeat, if it is bound " + "with the '-r' flag." }, { .name = "set-titles", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_SESSION, - .default_num = 0 + .default_num = 0, + .text = "Whether to set the terminal title, if supported." }, { .name = "set-titles-string", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "#S:#I:#W - \"#T\" #{session_alerts}" + .default_str = "#S:#I:#W - \"#T\" #{session_alerts}", + .text = "Format of the terminal title to set." }, { .name = "silence-action", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_bell_action_list, - .default_num = ALERT_OTHER + .default_num = ALERT_OTHER, + .text = "Action to take on a silence alert." }, { .name = "status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_list, - .default_num = 1 + .default_num = 1, + .text = "Number of lines in the status line." }, { .name = "status-bg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 8, + .text = "Background colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-fg", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_SESSION, .default_num = 8, + .text = "Foreground colour of the status line. This option is " + "deprecated, use 'status-style' instead." }, { .name = "status-format", @@ -497,6 +573,11 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_arr = options_table_status_format_default, + .text = "Formats for the status lines. " + "Each array member is the format for one status line. " + "The default status line is made up of several components " + "which may be configured individually with other option such " + "as 'status-left'." }, { .name = "status-interval", @@ -504,27 +585,32 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = INT_MAX, - .default_num = 15 + .default_num = 15, + .unit = "seconds", + .text = "Number of seconds between status line updates." }, { .name = "status-justify", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_justify_list, - .default_num = 0 + .default_num = 0, + .text = "Position of the window list in the status line." }, { .name = "status-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set to use at the command prompt." }, { .name = "status-left", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = "[#S] " + .default_str = "[#{session_name}] ", + .text = "Contents of the left side of the status line." }, { .name = "status-left-length", @@ -532,7 +618,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 10 + .default_num = 10, + .text = "Maximum width of the left side of the status line." }, { .name = "status-left-style", @@ -540,22 +627,26 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the left side of the status line." }, { .name = "status-position", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_status_position_list, - .default_num = 1 + .default_num = 1, + .text = "Position of the status line." }, { .name = "status-right", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, .default_str = "#{?window_bigger," - "[#{window_offset_x}#,#{window_offset_y}] ,}" - "\"#{=21:pane_title}\" %H:%M %d-%b-%y" + "[#{window_offset_x}#,#{window_offset_y}] ,}" + "\"#{=21:pane_title}\" %H:%M %d-%b-%y", + .text = "Contents of the right side of the status line." + }, { .name = "status-right-length", @@ -563,7 +654,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .minimum = 0, .maximum = SHRT_MAX, - .default_num = 40 + .default_num = 40, + .text = "Maximum width of the right side of the status line." }, { .name = "status-right-style", @@ -571,7 +663,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the right side of the status line." }, { .name = "status-style", @@ -579,7 +672,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .default_str = "bg=green,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the status line." }, { .name = "update-environment", @@ -587,79 +681,100 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_SESSION, .flags = OPTIONS_TABLE_IS_ARRAY, .default_str = "DISPLAY KRB5CCNAME SSH_ASKPASS SSH_AUTH_SOCK " - "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY" + "SSH_AGENT_PID SSH_CONNECTION WINDOWID XAUTHORITY", + .text = "List of environment variables to update in the session " + "environment when a client is attached." }, { .name = "visual-activity", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How activity alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-bell", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How bell alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "visual-silence", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_SESSION, .choices = options_table_visual_bell_list, - .default_num = VISUAL_OFF + .default_num = VISUAL_OFF, + .text = "How silence alerts should be shown: a message ('on'), " + "a message and a bell ('both') or nothing ('off')." }, { .name = "word-separators", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_SESSION, - .default_str = " " + .default_str = " ", + .text = "Characters considered to separate words." }, /* Window options. */ { .name = "aggressive-resize", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "When 'window-size' is 'smallest', whether the maximum size " + "of a window is the smallest attached session where it is " + "the current window ('on') or the smallest session it is " + "linked to ('off')." }, { .name = "allow-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .default_num = 0, + .text = "Whether applications are allowed to use the escape sequence " + "to rename windows." }, { .name = "alternate-screen", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 1 + .default_num = 1, + .text = "Whether applications are allowed to use the alternate " + "screen." }, { .name = "automatic-rename", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether windows are automatically renamed." }, { .name = "automatic-rename-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_in_mode,[tmux],#{pane_current_command}}" - "#{?pane_dead,[dead],}" + "#{?pane_dead,[dead],}", + .text = "Format used to automatically rename windows." }, { .name = "clock-mode-colour", .type = OPTIONS_TABLE_COLOUR, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 4 + .default_num = 4, + .text = "Colour of the clock in clock mode." }, { .name = "clock-mode-style", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_clock_mode_style_list, - .default_num = 1 + .default_num = 1, + .text = "Time format of the clock in clock mode." }, { .name = "copy-mode-match-style", @@ -667,7 +782,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=cyan,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of search matches in copy mode." }, { .name = "copy-mode-current-match-style", @@ -675,26 +791,32 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=magenta,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the current search match in copy mode." }, { .name = "main-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "24" + .default_str = "24", + .text = "Height of the main pane in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "main-pane-width", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "80" + .default_str = "80", + .text = "Width of the main pane in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "mode-keys", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_mode_keys_list, - .default_num = MODEKEY_EMACS + .default_num = MODEKEY_EMACS, + .text = "Key set used in copy mode." }, { .name = "mode-style", @@ -702,19 +824,22 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "bg=yellow,fg=black", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of indicators and highlighting in modes." }, { .name = "monitor-activity", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "Whether an alert is triggered by activity." }, { .name = "monitor-bell", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether an alert is triggered by a bell." }, { .name = "monitor-silence", @@ -722,19 +847,26 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = INT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Time after which an alert is triggered by silence. " + "Zero means no alert." + }, { .name = "other-pane-height", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "0" + .default_str = "0", + .text = "Height of the other panes in the 'main-horizontal' layout. " + "This may be a percentage, for example '10%'." }, { .name = "other-pane-width", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "0" + .default_str = "0", + .text = "Height of the other panes in the 'main-vertical' layout. " + "This may be a percentage, for example '10%'." }, { .name = "pane-active-border-style", @@ -742,7 +874,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_in_mode,fg=yellow,#{?synchronize-panes,fg=red,fg=green}}", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the active pane border." }, { .name = "pane-base-index", @@ -750,21 +883,24 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .minimum = 0, .maximum = USHRT_MAX, - .default_num = 0 + .default_num = 0, + .text = "Index of the first pane in each window." }, { .name = "pane-border-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, .default_str = "#{?pane_active,#[reverse],}#{pane_index}#[default] " - "\"#{pane_title}\"" + "\"#{pane_title}\"", + .text = "Format of text in the pane status lines." }, { .name = "pane-border-status", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_pane_status_list, - .default_num = PANE_STATUS_OFF + .default_num = PANE_STATUS_OFF, + .text = "Position of the pane status lines." }, { .name = "pane-border-style", @@ -772,19 +908,23 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the pane status lines." }, { .name = "remain-on-exit", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, - .default_num = 0 + .default_num = 0, + .text = "Whether panes should remain ('on') or be automatically " + "killed ('off') when the program inside exits." }, { .name = "synchronize-panes", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 0 + .default_num = 0, + .text = "Whether typing should be sent to all panes simultaneously." }, { .name = "window-active-style", @@ -792,14 +932,20 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Default style of the active pane." }, { .name = "window-size", .type = OPTIONS_TABLE_CHOICE, .scope = OPTIONS_TABLE_WINDOW, .choices = options_table_window_size_list, - .default_num = WINDOW_SIZE_LATEST + .default_num = WINDOW_SIZE_LATEST, + .text = "How window size is calculated. " + "'latest' uses the size of the most recently used client, " + "'largest' the largest client, 'smallest' the smallest " + "client and 'manual' a size set by the 'resize-window' " + "command." }, { .name = "window-style", @@ -807,7 +953,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Default style of panes that are not the active pane." }, { .name = "window-status-activity-style", @@ -815,7 +962,8 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "reverse", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line with an activity alert." }, { .name = "window-status-bell-style", @@ -823,13 +971,15 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "reverse", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line with a bell alert." }, { .name = "window-status-current-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of the current window in the status line." }, { .name = "window-status-current-style", @@ -837,13 +987,16 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the current window in the status line." }, { .name = "window-status-format", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = "#I:#W#{?window_flags,#{window_flags}, }" + .default_str = "#I:#W#{?window_flags,#{window_flags}, }", + .text = "Format of windows in the status line, except the current " + "window." }, { .name = "window-status-last-style", @@ -851,13 +1004,15 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of the last window in the status line." }, { .name = "window-status-separator", .type = OPTIONS_TABLE_STRING, .scope = OPTIONS_TABLE_WINDOW, - .default_str = " " + .default_str = " ", + .text = "Separator between windows in the status line." }, { .name = "window-status-style", @@ -865,19 +1020,24 @@ const struct options_table_entry options_table[] = { .scope = OPTIONS_TABLE_WINDOW, .default_str = "default", .flags = OPTIONS_TABLE_IS_STYLE, - .separator = "," + .separator = ",", + .text = "Style of windows in the status line, except the current and " + "last windows." }, { .name = "wrap-search", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether searching in copy mode should wrap at the top or " + "bottom." }, { .name = "xterm-keys", .type = OPTIONS_TABLE_FLAG, .scope = OPTIONS_TABLE_WINDOW, - .default_num = 1 + .default_num = 1, + .text = "Whether xterm-style function key sequences should be sent." }, /* Hook options. */ diff --git a/options.c b/options.c index 39a0d08f..58b7f7c1 100644 --- a/options.c +++ b/options.c @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -116,7 +117,7 @@ options_value_free(struct options_entry *o, union options_value *ov) } static char * -options_value_tostring(struct options_entry *o, union options_value *ov, +options_value_to_string(struct options_entry *o, union options_value *ov, int numeric) { char *s; @@ -175,6 +176,12 @@ options_free(struct options *oo) free(oo); } +struct options * +options_get_parent(struct options *oo) +{ + return (oo->parent); +} + void options_set_parent(struct options *oo, struct options *parent) { @@ -262,6 +269,35 @@ options_default(struct options *oo, const struct options_table_entry *oe) return (o); } +char * +options_default_to_string(const struct options_table_entry *oe) +{ + char *s; + + switch (oe->type) { + case OPTIONS_TABLE_STRING: + case OPTIONS_TABLE_COMMAND: + s = xstrdup(oe->default_str); + break; + case OPTIONS_TABLE_NUMBER: + xasprintf(&s, "%lld", oe->default_num); + break; + case OPTIONS_TABLE_KEY: + s = xstrdup(key_string_lookup_key(oe->default_num)); + break; + case OPTIONS_TABLE_COLOUR: + s = xstrdup(colour_tostring(oe->default_num)); + break; + case OPTIONS_TABLE_FLAG: + s = xstrdup(oe->default_num ? "on" : "off"); + break; + case OPTIONS_TABLE_CHOICE: + s = xstrdup(oe->choices[oe->default_num]); + break; + } + return (s); +} + static struct options_entry * options_add(struct options *oo, const char *name) { @@ -299,6 +335,12 @@ options_name(struct options_entry *o) return (o->name); } +struct options * +options_owner(struct options_entry *o) +{ + return (o->owner); +} + const struct options_table_entry * options_table_entry(struct options_entry *o) { @@ -492,19 +534,19 @@ options_array_item_value(struct options_array_item *a) } int -options_isarray(struct options_entry *o) +options_is_array(struct options_entry *o) { return (OPTIONS_IS_ARRAY(o)); } int -options_isstring(struct options_entry *o) +options_is_string(struct options_entry *o) { return (OPTIONS_IS_STRING(o)); } char * -options_tostring(struct options_entry *o, int idx, int numeric) +options_to_string(struct options_entry *o, int idx, int numeric) { struct options_array_item *a; @@ -514,9 +556,9 @@ options_tostring(struct options_entry *o, int idx, int numeric) a = options_array_item(o, idx); if (a == NULL) return (xstrdup("")); - return (options_value_tostring(o, &a->value, numeric)); + return (options_value_to_string(o, &a->value, numeric)); } - return (options_value_tostring(o, &o->value, numeric)); + return (options_value_to_string(o, &o->value, numeric)); } char * @@ -866,3 +908,201 @@ options_string_to_style(struct options *oo, const char *name, } return (&o->style); } + +static int +options_from_string_check(const struct options_table_entry *oe, + const char *value, char **cause) +{ + struct style sy; + + if (oe == NULL) + return (0); + if (strcmp(oe->name, "default-shell") == 0 && !checkshell(value)) { + xasprintf(cause, "not a suitable shell: %s", value); + return (-1); + } + if (oe->pattern != NULL && fnmatch(oe->pattern, value, 0) != 0) { + xasprintf(cause, "value is invalid: %s", value); + return (-1); + } + if ((oe->flags & OPTIONS_TABLE_IS_STYLE) && + strstr(value, "#{") == NULL && + style_parse(&sy, &grid_default_cell, value) != 0) { + xasprintf(cause, "invalid style: %s", value); + return (-1); + } + return (0); +} + +static int +options_from_string_flag(struct options *oo, const char *name, + const char *value, char **cause) +{ + int flag; + + if (value == NULL || *value == '\0') + flag = !options_get_number(oo, name); + else if (strcmp(value, "1") == 0 || + strcasecmp(value, "on") == 0 || + strcasecmp(value, "yes") == 0) + flag = 1; + else if (strcmp(value, "0") == 0 || + strcasecmp(value, "off") == 0 || + strcasecmp(value, "no") == 0) + flag = 0; + else { + xasprintf(cause, "bad value: %s", value); + return (-1); + } + options_set_number(oo, name, flag); + return (0); +} + +static int +options_from_string_choice(const struct options_table_entry *oe, + struct options *oo, const char *name, const char *value, char **cause) +{ + const char **cp; + int n, choice = -1; + + if (value == NULL) { + choice = options_get_number(oo, name); + if (choice < 2) + choice = !choice; + } else { + n = 0; + for (cp = oe->choices; *cp != NULL; cp++) { + if (strcmp(*cp, value) == 0) + choice = n; + n++; + } + if (choice == -1) { + xasprintf(cause, "unknown value: %s", value); + return (-1); + } + } + options_set_number(oo, name, choice); + return (0); +} + +int +options_from_string(struct options *oo, const struct options_table_entry *oe, + const char *name, const char *value, int append, char **cause) +{ + enum options_table_type type; + long long number; + const char *errstr, *new; + char *old; + key_code key; + + if (oe != NULL) { + if (value == NULL && + oe->type != OPTIONS_TABLE_FLAG && + oe->type != OPTIONS_TABLE_CHOICE) { + xasprintf(cause, "empty value"); + return (-1); + } + type = oe->type; + } else { + if (*name != '@') { + xasprintf(cause, "bad option name"); + return (-1); + } + type = OPTIONS_TABLE_STRING; + } + + switch (type) { + case OPTIONS_TABLE_STRING: + old = xstrdup(options_get_string(oo, name)); + options_set_string(oo, name, append, "%s", value); + + new = options_get_string(oo, name); + if (options_from_string_check(oe, new, cause) != 0) { + options_set_string(oo, name, 0, "%s", old); + free(old); + return (-1); + } + free(old); + return (0); + case OPTIONS_TABLE_NUMBER: + number = strtonum(value, oe->minimum, oe->maximum, &errstr); + if (errstr != NULL) { + xasprintf(cause, "value is %s: %s", errstr, value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_KEY: + key = key_string_lookup_string(value); + if (key == KEYC_UNKNOWN) { + xasprintf(cause, "bad key: %s", value); + return (-1); + } + options_set_number(oo, name, key); + return (0); + case OPTIONS_TABLE_COLOUR: + if ((number = colour_fromstring(value)) == -1) { + xasprintf(cause, "bad colour: %s", value); + return (-1); + } + options_set_number(oo, name, number); + return (0); + case OPTIONS_TABLE_FLAG: + return (options_from_string_flag(oo, name, value, cause)); + case OPTIONS_TABLE_CHOICE: + return (options_from_string_choice(oe, oo, name, value, cause)); + case OPTIONS_TABLE_COMMAND: + break; + } + return (-1); +} + +void +options_push_changes(const char *name) +{ + struct client *loop; + struct session *s; + struct window *w; + struct window_pane *wp; + + if (strcmp(name, "automatic-rename") == 0) { + RB_FOREACH(w, windows, &windows) { + if (w->active == NULL) + continue; + if (options_get_number(w->options, "automatic-rename")) + w->active->flags |= PANE_CHANGED; + } + } + if (strcmp(name, "key-table") == 0) { + TAILQ_FOREACH(loop, &clients, entry) + server_client_set_key_table(loop, NULL); + } + if (strcmp(name, "user-keys") == 0) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->tty.flags & TTY_OPENED) + tty_keys_build(&loop->tty); + } + } + if (strcmp(name, "status") == 0 || + strcmp(name, "status-interval") == 0) + status_timer_start_all(); + if (strcmp(name, "monitor-silence") == 0) + alerts_reset_all(); + if (strcmp(name, "window-style") == 0 || + strcmp(name, "window-active-style") == 0) { + RB_FOREACH(wp, window_pane_tree, &all_window_panes) + wp->flags |= PANE_STYLECHANGED; + } + if (strcmp(name, "pane-border-status") == 0) { + RB_FOREACH(w, windows, &windows) + layout_fix_panes(w); + } + RB_FOREACH(s, sessions, &sessions) + status_update_cache(s); + + recalculate_sizes(); + TAILQ_FOREACH(loop, &clients, entry) { + if (loop->session != NULL) + server_redraw_client(loop); + } +} diff --git a/screen-write.c b/screen-write.c index 909b8531..9571cbec 100644 --- a/screen-write.c +++ b/screen-write.c @@ -360,7 +360,97 @@ screen_write_strlen(const char *fmt, ...) return (size); } -/* Write simple string (no UTF-8 or maximum length). */ +/* Write string wrapped over lines. */ +int +screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width, + u_int lines, int more, const struct grid_cell *gcp, const char *fmt, ...) +{ + struct screen *s = ctx->s; + va_list ap; + char *tmp; + u_int cy = s->cy, i, end, next, idx = 0, at, left; + struct utf8_data *text; + struct grid_cell gc; + + memcpy(&gc, gcp, sizeof gc); + + va_start(ap, fmt); + xvasprintf(&tmp, fmt, ap); + va_end(ap); + + text = utf8_fromcstr(tmp); + free(tmp); + + left = (cx + width) - s->cx; + for (;;) { + /* Find the end of what can fit on the line. */ + at = 0; + for (end = idx; text[end].size != 0; end++) { + if (text[end].size == 1 && text[end].data[0] == '\n') + break; + if (at + text[end].width > left) + break; + at += text[end].width; + } + + /* + * If we're on a space, that's the end. If not, walk back to + * try and find one. + */ + if (text[end].size == 0) + next = end; + else if (text[end].size == 1 && text[end].data[0] == '\n') + next = end + 1; + else if (text[end].size == 1 && text[end].data[0] == ' ') + next = end + 1; + else { + for (i = end; i > idx; i--) { + if (text[i].size == 1 && text[i].data[0] == ' ') + break; + } + if (i != idx) { + next = i + 1; + end = i; + } else + next = end; + } + + /* Print the line. */ + for (i = idx; i < end; i++) { + utf8_copy(&gc.data, &text[i]); + screen_write_cell(ctx, &gc); + } + + /* If at the bottom, stop. */ + idx = next; + if (s->cy == cy + lines - 1 || text[idx].size == 0) + break; + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + left = width; + } + + /* + * Fail if on the last line and there is more to come or at the end, or + * if the text was not entirely consumed. + */ + if ((s->cy == cy + lines - 1 && (!more || s->cx == cx + width)) || + text[idx].size != 0) { + free(text); + return (0); + } + free(text); + + /* + * If no more to come, move to the next line. Otherwise, leave on + * the same line (except if at the end). + */ + if (!more || s->cx == cx + width) + screen_write_cursormove(ctx, cx, s->cy + 1, 0); + return (1); +} + +/* Write simple string (no maximum length). */ void screen_write_puts(struct screen_write_ctx *ctx, const struct grid_cell *gcp, const char *fmt, ...) diff --git a/style.c b/style.c index 3c615852..08614f9c 100644 --- a/style.c +++ b/style.c @@ -31,6 +31,7 @@ /* Default style. */ static struct style style_default = { { { { ' ' }, 0, 1, 1 }, 0, 0, 8, 8, 0 }, + 0, 8, STYLE_ALIGN_DEFAULT, @@ -78,7 +79,11 @@ style_parse(struct style *sy, const struct grid_cell *base, const char *in) sy->gc.bg = base->bg; sy->gc.attr = base->attr; sy->gc.flags = base->flags; - } else if (strcasecmp(tmp, "push-default") == 0) + } else if (strcasecmp(tmp, "ignore") == 0) + sy->ignore = 1; + else if (strcasecmp(tmp, "noignore") == 0) + sy->ignore = 0; + else if (strcasecmp(tmp, "push-default") == 0) sy->default_type = STYLE_DEFAULT_PUSH; else if (strcasecmp(tmp, "pop-default") == 0) sy->default_type = STYLE_DEFAULT_POP; diff --git a/tmux.1 b/tmux.1 index 852ae80f..45c004ff 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1952,6 +1952,53 @@ includes all sessions in any session groups in the tree rather than only the first. This command works only if at least one client is attached. .It Xo +.Ic customize-mode +.Op Fl NZ +.Op Fl F Ar format +.Op Fl f Ar filter +.Op Fl t Ar target-pane +.Op Ar template +.Xc +Put a pane into customize mode, where options and key bindings may be browsed +and modified from a list. +Option values in the list are shown for the active pane in the current window. +.Fl Z +zooms the pane. +The following keys may be used in customize mode: +.Bl -column "Key" "Function" -offset indent +.It Sy "Key" Ta Sy "Function" +.It Li "Enter" Ta "Set pane, window, session or global option value" +.It Li "Up" Ta "Select previous item" +.It Li "Down" Ta "Select next item" +.It Li "+" Ta "Expand selected item" +.It Li "-" Ta "Collapse selected item" +.It Li "M-+" Ta "Expand all items" +.It Li "M--" Ta "Collapse all items" +.It Li "s" Ta "Set option value or key attribute" +.It Li "S" Ta "Set global option value" +.It Li "w" Ta "Set window option value, if option is for pane and window" +.It Li "u" Ta "Unset an option (set to default value if global) or unbind a key" +.It Li "U" Ta "Unset tagged options and unbind tagged keys" +.It Li "C-s" Ta "Search by name" +.It Li "n" Ta "Repeat last search" +.It Li "t" Ta "Toggle if item is tagged" +.It Li "T" Ta "Tag no items" +.It Li "C-t" Ta "Tag all items" +.It Li "f" Ta "Enter a format to filter items" +.It Li "v" Ta "Toggle option information" +.It Li "q" Ta "Exit mode" +.El +.Pp +.Fl f +specifies an initial filter: the filter is a format - if it evaluates to zero, +the item in the list is not shown, otherwise it is shown. +If a filter would lead to an empty list, it is ignored. +.Fl F +specifies the format for each item in the tree. +.Fl N +starts without the option information. +This command works only if at least one client is attached. +.It Xo .Ic display-panes .Op Fl b .Op Fl d Ar duration diff --git a/tmux.h b/tmux.h index 385ba212..1abb6ca6 100644 --- a/tmux.h +++ b/tmux.h @@ -745,6 +745,7 @@ enum style_default_type { /* Style option. */ struct style { struct grid_cell gc; + int ignore; int fill; enum style_align align; @@ -1665,6 +1666,7 @@ RB_HEAD(key_bindings, key_binding); struct key_table { const char *name; struct key_bindings key_bindings; + struct key_bindings default_key_bindings; u_int references; @@ -1719,6 +1721,9 @@ struct options_table_entry { const char *separator; const char *pattern; + + const char *text; + const char *unit; }; /* Common command usages. */ @@ -1896,6 +1901,7 @@ void notify_pane(const char *, struct window_pane *); /* options.c */ struct options *options_create(struct options *); void options_free(struct options *); +struct options *options_get_parent(struct options *); void options_set_parent(struct options *, struct options *); struct options_entry *options_first(struct options *); struct options_entry *options_next(struct options_entry *); @@ -1903,7 +1909,9 @@ struct options_entry *options_empty(struct options *, const struct options_table_entry *); struct options_entry *options_default(struct options *, const struct options_table_entry *); +char *options_default_to_string(const struct options_table_entry *); const char *options_name(struct options_entry *); +struct options *options_owner(struct options_entry *); const struct options_table_entry *options_table_entry(struct options_entry *); struct options_entry *options_get_only(struct options *, const char *); struct options_entry *options_get(struct options *, const char *); @@ -1918,9 +1926,9 @@ struct options_array_item *options_array_first(struct options_entry *); struct options_array_item *options_array_next(struct options_array_item *); u_int options_array_item_index(struct options_array_item *); union options_value *options_array_item_value(struct options_array_item *); -int options_isarray(struct options_entry *); -int options_isstring(struct options_entry *); -char *options_tostring(struct options_entry *, int, int); +int options_is_array(struct options_entry *); +int options_is_string(struct options_entry *); +char *options_to_string(struct options_entry *, int, int); char *options_parse(const char *, int *); struct options_entry *options_parse_get(struct options *, const char *, int *, int); @@ -1940,6 +1948,10 @@ int options_scope_from_flags(struct args *, int, struct cmd_find_state *, struct options **, char **); struct style *options_string_to_style(struct options *, const char *, struct format_tree *); +int options_from_string(struct options *, + const struct options_table_entry *, const char *, + const char *, int, char **); +void options_push_changes(const char *); /* options-table.c */ extern const struct options_table_entry options_table[]; @@ -2231,6 +2243,7 @@ struct key_table *key_bindings_first_table(void); struct key_table *key_bindings_next_table(struct key_table *); void key_bindings_unref_table(struct key_table *); struct key_binding *key_bindings_get(struct key_table *, key_code); +struct key_binding *key_bindings_get_default(struct key_table *, key_code); struct key_binding *key_bindings_first(struct key_table *); struct key_binding *key_bindings_next(struct key_table *, struct key_binding *); void key_bindings_add(const char *, key_code, const char *, int, @@ -2460,6 +2473,8 @@ void screen_write_start_callback(struct screen_write_ctx *, struct screen *, void screen_write_stop(struct screen_write_ctx *); void screen_write_reset(struct screen_write_ctx *); size_t printflike(1, 2) screen_write_strlen(const char *, ...); +int printflike(7, 8) screen_write_text(struct screen_write_ctx *, u_int, u_int, + u_int, int, const struct grid_cell *, const char *, ...); void printflike(3, 4) screen_write_puts(struct screen_write_ctx *, const struct grid_cell *, const char *, ...); void printflike(4, 5) screen_write_nputs(struct screen_write_ctx *, @@ -2678,19 +2693,23 @@ typedef void (*mode_tree_draw_cb)(void *, void *, struct screen_write_ctx *, u_int, u_int); typedef int (*mode_tree_search_cb)(void *, void *, const char *); typedef void (*mode_tree_menu_cb)(void *, struct client *, key_code); +typedef u_int (*mode_tree_height_cb)(void *, u_int); typedef void (*mode_tree_each_cb)(void *, void *, struct client *, key_code); u_int mode_tree_count_tagged(struct mode_tree_data *); void *mode_tree_get_current(struct mode_tree_data *); +const char *mode_tree_get_current_name(struct mode_tree_data *); void mode_tree_expand_current(struct mode_tree_data *); +void mode_tree_collapse_current(struct mode_tree_data *); void mode_tree_expand(struct mode_tree_data *, uint64_t); int mode_tree_set_current(struct mode_tree_data *, uint64_t); void mode_tree_each_tagged(struct mode_tree_data *, mode_tree_each_cb, struct client *, key_code, int); +void mode_tree_up(struct mode_tree_data *, int); void mode_tree_down(struct mode_tree_data *, int); struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *, mode_tree_build_cb, mode_tree_draw_cb, mode_tree_search_cb, - mode_tree_menu_cb, void *, const struct menu_item *, const char **, - u_int, struct screen **); + mode_tree_menu_cb, mode_tree_height_cb, void *, + const struct menu_item *, const char **, u_int, struct screen **); void mode_tree_zoom(struct mode_tree_data *, struct args *); void mode_tree_build(struct mode_tree_data *); void mode_tree_free(struct mode_tree_data *); @@ -2698,6 +2717,7 @@ void mode_tree_resize(struct mode_tree_data *, u_int, u_int); struct mode_tree_item *mode_tree_add(struct mode_tree_data *, struct mode_tree_item *, void *, uint64_t, const char *, const char *, int); +void mode_tree_draw_as_parent(struct mode_tree_item *); void mode_tree_remove(struct mode_tree_data *, struct mode_tree_item *); void mode_tree_draw(struct mode_tree_data *); int mode_tree_key(struct mode_tree_data *, struct client *, key_code *, @@ -2728,6 +2748,9 @@ void window_copy_start_drag(struct client *, struct mouse_event *); char *window_copy_get_word(struct window_pane *, u_int, u_int); char *window_copy_get_line(struct window_pane *, u_int); +/* window-option.c */ +extern const struct window_mode window_customize_mode; + /* names.c */ void check_window_name(struct window *); char *default_window_name(struct window *); diff --git a/window-buffer.c b/window-buffer.c index 74be73a4..6156e6b4 100644 --- a/window-buffer.c +++ b/window-buffer.c @@ -298,8 +298,8 @@ window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_buffer_build, - window_buffer_draw, window_buffer_search, window_buffer_menu, data, - window_buffer_menu_items, window_buffer_sort_list, + window_buffer_draw, window_buffer_search, window_buffer_menu, NULL, + data, window_buffer_menu_items, window_buffer_sort_list, nitems(window_buffer_sort_list), &s); mode_tree_zoom(data->data, args); diff --git a/window-client.c b/window-client.c index cd424dd7..159efa5a 100644 --- a/window-client.c +++ b/window-client.c @@ -271,7 +271,7 @@ window_client_init(struct window_mode_entry *wme, data->command = xstrdup(args->argv[0]); data->data = mode_tree_start(wp, args, window_client_build, - window_client_draw, NULL, window_client_menu, data, + window_client_draw, NULL, window_client_menu, NULL, data, window_client_menu_items, window_client_sort_list, nitems(window_client_sort_list), &s); mode_tree_zoom(data->data, args); diff --git a/window-customize.c b/window-customize.c new file mode 100644 index 00000000..2d82897f --- /dev/null +++ b/window-customize.c @@ -0,0 +1,1430 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 Nicholas Marriott + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include +#include + +#include "tmux.h" + +static struct screen *window_customize_init(struct window_mode_entry *, + struct cmd_find_state *, struct args *); +static void window_customize_free(struct window_mode_entry *); +static void window_customize_resize(struct window_mode_entry *, + u_int, u_int); +static void window_customize_key(struct window_mode_entry *, + struct client *, struct session *, + struct winlink *, key_code, struct mouse_event *); + +#define WINDOW_CUSTOMIZE_DEFAULT_FORMAT \ + "#{?is_option," \ + "#{?option_is_global,,#[reverse](#{option_scope})#[default] }" \ + "#[ignore]" \ + "#{option_value}#{?option_unit, #{option_unit},}" \ + "," \ + "#{key}" \ + "}" + +static const struct menu_item window_customize_menu_items[] = { + { "Select", '\r', NULL }, + { "Expand", KEYC_RIGHT, NULL }, + { "", KEYC_NONE, NULL }, + { "Tag", 't', NULL }, + { "Tag All", '\024', NULL }, + { "Tag None", 'T', NULL }, + { "", KEYC_NONE, NULL }, + { "Cancel", 'q', NULL }, + + { NULL, KEYC_NONE, NULL } +}; + +const struct window_mode window_customize_mode = { + .name = "options-mode", + .default_format = WINDOW_CUSTOMIZE_DEFAULT_FORMAT, + + .init = window_customize_init, + .free = window_customize_free, + .resize = window_customize_resize, + .key = window_customize_key, +}; + +enum window_customize_scope { + WINDOW_CUSTOMIZE_NONE, + WINDOW_CUSTOMIZE_KEY, + WINDOW_CUSTOMIZE_SERVER, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, + WINDOW_CUSTOMIZE_SESSION, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, + WINDOW_CUSTOMIZE_WINDOW, + WINDOW_CUSTOMIZE_PANE +}; + +struct window_customize_itemdata { + struct window_customize_modedata *data; + enum window_customize_scope scope; + + char *table; + key_code key; + + struct options *oo; + char *name; + int idx; +}; + +struct window_customize_modedata { + struct window_pane *wp; + int dead; + int references; + + struct mode_tree_data *data; + char *format; + int hide_global; + + struct window_customize_itemdata **item_list; + u_int item_size; + + struct cmd_find_state fs; +}; + +static uint64_t +window_customize_get_tag(struct options_entry *o, int idx, + const struct options_table_entry *oe) +{ + uint64_t offset; + + if (oe == NULL) + return ((uint64_t)o); + offset = ((char *)oe - (char *)options_table) / sizeof *options_table; + return ((2ULL << 62)|(offset << 32)|((idx + 1) << 1)|1); +} + +static struct options * +window_customize_get_tree(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + switch (scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + return (NULL); + case WINDOW_CUSTOMIZE_SERVER: + return (global_options); + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + return (global_s_options); + case WINDOW_CUSTOMIZE_SESSION: + return (fs->s->options); + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + return (global_w_options); + case WINDOW_CUSTOMIZE_WINDOW: + return (fs->w->options); + case WINDOW_CUSTOMIZE_PANE: + return (fs->wp->options); + } + return (NULL); +} + +static int +window_customize_check_item(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct cmd_find_state *fsp) +{ + struct cmd_find_state fs; + + if (fsp == NULL) + fsp = &fs; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(fsp, &data->fs); + else + cmd_find_from_pane(fsp, data->wp, 0); + return (item->oo == window_customize_get_tree(item->scope, fsp)); +} + +static int +window_customize_get_key(struct window_customize_itemdata *item, + struct key_table **ktp, struct key_binding **bdp) +{ + struct key_table *kt; + struct key_binding *bd; + + kt = key_bindings_get_table(item->table, 0); + if (kt == NULL) + return (0); + bd = key_bindings_get(kt, item->key); + if (bd == NULL) + return (0); + + if (ktp != NULL) + *ktp = kt; + if (bdp != NULL) + *bdp = bd; + return (1); +} + +static char * +window_customize_scope_text(enum window_customize_scope scope, + struct cmd_find_state *fs) +{ + char *s; + u_int idx; + + switch (scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + s = xstrdup(""); + break; + case WINDOW_CUSTOMIZE_PANE: + window_pane_index(fs->wp, &idx); + xasprintf(&s, "pane %u", idx); + break; + case WINDOW_CUSTOMIZE_SESSION: + xasprintf(&s, "session %s", fs->s->name); + break; + case WINDOW_CUSTOMIZE_WINDOW: + xasprintf(&s, "window %u", fs->wl->idx); + break; + } + return (s); +} + +static struct window_customize_itemdata * +window_customize_add_item(struct window_customize_modedata *data) +{ + struct window_customize_itemdata *item; + + data->item_list = xreallocarray(data->item_list, data->item_size + 1, + sizeof *data->item_list); + item = data->item_list[data->item_size++] = xcalloc(1, sizeof *item); + return (item); +} + +static void +window_customize_free_item(struct window_customize_itemdata *item) +{ + free(item->table); + free(item->name); + free(item); +} + +static void +window_customize_build_array(struct window_customize_modedata *data, + struct mode_tree_item *top, enum window_customize_scope scope, + struct options_entry *o, struct format_tree *ft) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + struct window_customize_itemdata *item; + struct options_array_item *ai; + char *name, *value, *text; + u_int idx; + uint64_t tag; + + ai = options_array_first(o); + while (ai != NULL) { + idx = options_array_item_index(ai); + + xasprintf(&name, "%s[%u]", options_name(o), idx); + format_add(ft, "option_name", "%s", name); + value = options_to_string(o, idx, 0); + format_add(ft, "option_value", "%s", value); + + item = window_customize_add_item(data); + item->scope = scope; + item->oo = oo; + item->name = xstrdup(options_name(o)); + item->idx = idx; + + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, idx, oe); + mode_tree_add(data->data, top, item, tag, name, text, -1); + free(text); + + free(name); + free(value); + + ai = options_array_next(ai); + } +} + +static void +window_customize_build_option(struct window_customize_modedata *data, + struct mode_tree_item *top, enum window_customize_scope scope, + struct options_entry *o, struct format_tree *ft, + const char *filter, struct cmd_find_state *fs) +{ + const struct options_table_entry *oe = options_table_entry(o); + struct options *oo = options_owner(o); + const char *name = options_name(o); + struct window_customize_itemdata *item; + char *text, *expanded, *value; + int global = 0, array = 0; + uint64_t tag; + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)) + return; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) + array = 1; + + if (scope == WINDOW_CUSTOMIZE_SERVER || + scope == WINDOW_CUSTOMIZE_GLOBAL_SESSION || + scope == WINDOW_CUSTOMIZE_GLOBAL_WINDOW) + global = 1; + if (data->hide_global && global) + return; + + format_add(ft, "option_name", "%s", name); + format_add(ft, "option_is_global", "%d", global); + format_add(ft, "option_is_array", "%d", array); + + text = window_customize_scope_text(scope, fs); + format_add(ft, "option_scope", "%s", text); + free(text); + + if (oe != NULL && oe->unit != NULL) + format_add(ft, "option_unit", "%s", oe->unit); + else + format_add(ft, "option_unit", "%s", ""); + + if (!array) { + value = options_to_string(o, -1, 0); + format_add(ft, "option_value", "%s", value); + free(value); + } + + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + return; + } + free(expanded); + } + item = window_customize_add_item(data); + item->oo = oo; + item->scope = scope; + item->name = xstrdup(name); + item->idx = -1; + + if (array) + text = NULL; + else + text = format_expand(ft, data->format); + tag = window_customize_get_tag(o, -1, oe); + top = mode_tree_add(data->data, top, item, tag, name, text, 0); + free(text); + + if (array) + window_customize_build_array(data, top, scope, o, ft); +} + +static void +window_customize_find_user_options(struct options *oo, const char ***list, + u_int *size) +{ + struct options_entry *o; + const char *name; + u_int i; + + o = options_first(oo); + while (o != NULL) { + name = options_name(o); + if (*name != '@') { + o = options_next(o); + continue; + } + for (i = 0; i < *size; i++) { + if (strcmp((*list)[i], name) == 0) + break; + } + if (i != *size) { + o = options_next(o); + continue; + } + *list = xreallocarray(*list, (*size) + 1, sizeof **list); + (*list)[(*size)++] = name; + + o = options_next(o); + } +} + +static void +window_customize_build_options(struct window_customize_modedata *data, + const char *title, uint64_t tag, + enum window_customize_scope scope0, struct options *oo0, + enum window_customize_scope scope1, struct options *oo1, + enum window_customize_scope scope2, struct options *oo2, + struct format_tree *ft, const char *filter, struct cmd_find_state *fs) +{ + struct mode_tree_item *top; + struct options_entry *o, *loop; + const char **list = NULL, *name; + u_int size = 0, i; + enum window_customize_scope scope; + + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + + /* + * We get the options from the first tree, but build it using the + * values from the other two. Any tree can have user options so we need + * to build a separate list of them. + */ + + window_customize_find_user_options(oo0, &list, &size); + if (oo1 != NULL) + window_customize_find_user_options(oo1, &list, &size); + if (oo2 != NULL) + window_customize_find_user_options(oo2, &list, &size); + + for (i = 0; i < size; i++) { + if (oo2 != NULL) + o = options_get(oo0, list[i]); + else if (oo1 != NULL) + o = options_get(oo1, list[i]); + else + o = options_get(oo2, list[i]); + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + } + free(list); + + loop = options_first(oo0); + while (loop != NULL) { + name = options_name(loop); + if (*name == '@') { + loop = options_next(loop); + continue; + } + if (oo2 != NULL) + o = options_get(oo2, name); + else if (oo1 != NULL) + o = options_get(oo1, name); + else + o = loop; + if (options_owner(o) == oo2) + scope = scope2; + else if (options_owner(o) == oo1) + scope = scope1; + else + scope = scope0; + window_customize_build_option(data, top, scope, o, ft, filter, + fs); + loop = options_next(loop); + } +} + +static void +window_customize_build_keys(struct window_customize_modedata *data, + struct key_table *kt, struct format_tree *ft, const char *filter, + struct cmd_find_state *fs, u_int number) +{ + struct mode_tree_item *top, *child, *mti; + struct window_customize_itemdata *item; + struct key_binding *bd; + char *title, *text, *tmp, *expanded; + const char *flag; + uint64_t tag; + + tag = (1ULL << 62)|((uint64_t)number << 54)|1; + + xasprintf(&title, "Key Table - %s", kt->name); + top = mode_tree_add(data->data, NULL, NULL, tag, title, NULL, 0); + free(title); + + ft = format_create_from_state(NULL, NULL, fs); + format_add(ft, "is_option", "0"); + format_add(ft, "is_key", "1"); + + bd = key_bindings_first(kt); + while (bd != NULL) { + format_add(ft, "key", "%s", key_string_lookup_key(bd->key)); + if (bd->note != NULL) + format_add(ft, "key_note", "%s", bd->note); + if (filter != NULL) { + expanded = format_expand(ft, filter); + if (!format_true(expanded)) { + free(expanded); + continue; + } + free(expanded); + } + + item = window_customize_add_item(data); + item->scope = WINDOW_CUSTOMIZE_KEY; + item->table = xstrdup(kt->name); + item->key = bd->key; + + expanded = format_expand(ft, data->format); + child = mode_tree_add(data->data, top, item, (uint64_t)bd, + expanded, NULL, 0); + free(expanded); + + tmp = cmd_list_print(bd->cmdlist, 0); + xasprintf(&text, "#[ignore]%s", tmp); + free(tmp); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(0 << 1)|1, "Command", text, -1); + mode_tree_draw_as_parent(mti); + free(text); + + if (bd->note != NULL) + xasprintf(&text, "#[ignore]%s", bd->note); + else + text = xstrdup(""); + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(1 << 1)|1, "Note", text, -1); + mode_tree_draw_as_parent(mti); + free(text); + + if (bd->flags & KEY_BINDING_REPEAT) + flag = "on"; + else + flag = "off"; + mti = mode_tree_add(data->data, child, item, + tag|(bd->key << 3)|(2 << 1)|1, "Repeat", flag, -1); + mode_tree_draw_as_parent(mti); + + bd = key_bindings_next(kt, bd); + } + + format_free(ft); +} + +static void +window_customize_build(void *modedata, + __unused struct mode_tree_sort_criteria *sort_crit, __unused uint64_t *tag, + const char *filter) +{ + struct window_customize_modedata *data = modedata; + struct cmd_find_state fs; + struct format_tree *ft; + u_int i; + struct key_table *kt; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + data->item_list = NULL; + data->item_size = 0; + + if (cmd_find_valid_state(&data->fs)) + cmd_find_copy_state(&fs, &data->fs); + else + cmd_find_from_pane(&fs, data->wp, 0); + + ft = format_create_from_state(NULL, NULL, &fs); + format_add(ft, "is_option", "1"); + format_add(ft, "is_key", "0"); + + window_customize_build_options(data, "Server Options", + (3ULL << 62)|(OPTIONS_TABLE_SERVER << 1)|1, + WINDOW_CUSTOMIZE_SERVER, global_options, + WINDOW_CUSTOMIZE_NONE, NULL, + WINDOW_CUSTOMIZE_NONE, NULL, + ft, filter, &fs); + window_customize_build_options(data, "Session Options", + (3ULL << 62)|(OPTIONS_TABLE_SESSION << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_SESSION, global_s_options, + WINDOW_CUSTOMIZE_SESSION, fs.s->options, + WINDOW_CUSTOMIZE_NONE, NULL, + ft, filter, &fs); + window_customize_build_options(data, "Window & Pane Options", + (3ULL << 62)|(OPTIONS_TABLE_WINDOW << 1)|1, + WINDOW_CUSTOMIZE_GLOBAL_WINDOW, global_w_options, + WINDOW_CUSTOMIZE_WINDOW, fs.w->options, + WINDOW_CUSTOMIZE_PANE, fs.wp->options, + ft, filter, &fs); + + format_free(ft); + ft = format_create_from_state(NULL, NULL, &fs); + + i = 0; + kt = key_bindings_first_table(); + while (kt != NULL) { + if (!RB_EMPTY(&kt->key_bindings)) { + window_customize_build_keys(data, kt, ft, filter, &fs, + i); + if (++i == 256) + break; + } + kt = key_bindings_next_table(kt); + } + + format_free(ft); +} + +static void +window_customize_draw_key(__unused struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct screen_write_ctx *ctx, + u_int sx, u_int sy) +{ + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; + struct key_table *kt; + struct key_binding *bd, *default_bd; + const char *note, *period = ""; + char *cmd, *default_cmd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + note = bd->note; + if (note == NULL) + note = "There is no note for this key."; + if (*note != '\0' && note[strlen (note) - 1] != '.') + period = "."; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s%s", + note, period)) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This key is in the %s table.", kt->name)) + return; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This key %s repeat.", + (bd->flags & KEY_BINDING_REPEAT) ? "does" : "does not")) + return; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + return; + + cmd = cmd_list_print(bd->cmdlist, 0); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Command: %s", cmd)) { + free(cmd); + return; + } + default_bd = key_bindings_get_default(kt, bd->key); + if (default_bd != NULL) { + default_cmd = cmd_list_print(default_bd->cmdlist, 0); + if (strcmp(cmd, default_cmd) != 0 && + !screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "The default is: %s", default_cmd)) { + free(default_cmd); + free(cmd); + return; + } + free(default_cmd); + } + free(cmd); +} + +static void +window_customize_draw_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item, struct screen_write_ctx *ctx, + u_int sx, u_int sy) +{ + struct screen *s = ctx->s; + u_int cx = s->cx, cy = s->cy; + int idx; + struct options_entry *o, *parent; + struct options *go, *wo; + const struct options_table_entry *oe; + struct grid_cell gc; + const char **choice, *text, *name; + const char *space = "", *unit = ""; + char *value = NULL, *expanded; + char *default_value = NULL; + char choices[256] = ""; + struct cmd_find_state fs; + struct format_tree *ft; + + if (!window_customize_check_item(data, item, &fs)) + return; + name = item->name; + idx = item->idx; + + o = options_get(item->oo, name); + if (o == NULL) + return; + oe = options_table_entry(o); + + if (oe != NULL && oe->unit != NULL) { + space = " "; + unit = oe->unit; + } + ft = format_create_from_state(NULL, NULL, &fs); + + if (oe == NULL) + text = "This is a user option."; + else if (oe->text == NULL) + text = "This option doesn't have a description."; + else + text = oe->text; + if (!screen_write_text(ctx, cx, sx, sy, 0, &grid_default_cell, "%s", + text)) + goto out; + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + if (oe == NULL) + text = "user"; + else if ((oe->scope & (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) == + (OPTIONS_TABLE_WINDOW|OPTIONS_TABLE_PANE)) + text = "window and pane"; + else if (oe->scope & OPTIONS_TABLE_WINDOW) + text = "window"; + else if (oe->scope & OPTIONS_TABLE_SESSION) + text = "session"; + else + text = "server"; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "This is a %s option.", text)) + goto out; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx != -1) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, + "This is an array option, index %u.", idx)) + goto out; + } else { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, "This is an array option.")) + goto out; + } + if (idx == -1) + goto out; + } + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy >= cy + sy - 1) + goto out; + + value = options_to_string(o, idx, 0); + if (oe != NULL && idx == -1) { + default_value = options_default_to_string(oe); + if (strcmp(default_value, value) == 0) { + free(default_value); + default_value = NULL; + } + } + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Option value: %s%s%s", value, space, unit)) + goto out; + if (oe == NULL || oe->type == OPTIONS_TABLE_STRING) { + expanded = format_expand(ft, value); + if (strcmp(expanded, value) != 0) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), + 0, &grid_default_cell, "This expands to: %s", + expanded)) + goto out; + } + free(expanded); + } + if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + for (choice = oe->choices; *choice != NULL; choice++) { + strlcat(choices, *choice, sizeof choices); + strlcat(choices, ", ", sizeof choices); + } + choices[strlen(choices) - 2] = '\0'; + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "Available values are: %s", + choices)) + goto out; + } + if (oe != NULL && oe->type == OPTIONS_TABLE_COLOUR) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a colour option: ")) + goto out; + memcpy(&gc, &grid_default_cell, sizeof gc); + gc.fg = options_get_number(item->oo, name); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_STYLE)) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 1, + &grid_default_cell, "This is a style option: ")) + goto out; + style_apply(&gc, item->oo, name, ft); + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, &gc, + "EXAMPLE")) + goto out; + } + if (default_value != NULL) { + if (!screen_write_text(ctx, cx, sx, sy - (s->cy - cy), 0, + &grid_default_cell, "The default is: %s%s%s", default_value, + space, unit)) + goto out; + } + + screen_write_cursormove(ctx, cx, s->cy + 1, 0); /* skip line */ + if (s->cy > cy + sy - 1) + goto out; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + wo = NULL; + go = NULL; + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_PANE: + wo = options_get_parent(item->oo); + go = options_get_parent(wo); + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_SESSION: + wo = NULL; + go = options_get_parent(item->oo); + break; + default: + wo = NULL; + go = NULL; + break; + } + } + if (wo != NULL && options_owner(o) != wo) { + parent = options_get_only(wo, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 0); + if (!screen_write_text(ctx, s->cx, sx, + sy - (s->cy - cy), 0, &grid_default_cell, + "Window value (from window %u): %s%s%s", fs.wl->idx, + value, space, unit)) + goto out; + } + } + if (go != NULL && options_owner(o) != go) { + parent = options_get_only(go, name); + if (parent != NULL) { + value = options_to_string(parent, -1 , 0); + if (!screen_write_text(ctx, s->cx, sx, + sy - (s->cy - cy), 0, &grid_default_cell, + "Global value: %s%s%s", value, space, unit)) + goto out; + } + } + +out: + free(value); + free(default_value); + format_free(ft); +} + +static void +window_customize_draw(void *modedata, void *itemdata, + struct screen_write_ctx *ctx, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item = itemdata; + + if (item == NULL) + return; + + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_draw_key(data, item, ctx, sx, sy); + else + window_customize_draw_option(data, item, ctx, sx, sy); +} + +static void +window_customize_menu(void *modedata, struct client *c, key_code key) +{ + struct window_customize_modedata *data = modedata; + struct window_pane *wp = data->wp; + struct window_mode_entry *wme; + + wme = TAILQ_FIRST(&wp->modes); + if (wme == NULL || wme->data != modedata) + return; + window_customize_key(wme, c, NULL, NULL, key, NULL); +} + +static u_int +window_customize_height(__unused void *modedata, __unused u_int height) +{ + return (12); +} + +static struct screen * +window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs, + struct args *args) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data; + struct screen *s; + + wme->data = data = xcalloc(1, sizeof *data); + data->wp = wp; + data->references = 1; + + memcpy(&data->fs, fs, sizeof data->fs); + + if (args == NULL || !args_has(args, 'F')) + data->format = xstrdup(WINDOW_CUSTOMIZE_DEFAULT_FORMAT); + else + data->format = xstrdup(args_get(args, 'F')); + + data->data = mode_tree_start(wp, args, window_customize_build, + window_customize_draw, NULL, window_customize_menu, + window_customize_height, data, window_customize_menu_items, NULL, 0, + &s); + mode_tree_zoom(data->data, args); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + + return (s); +} + +static void +window_customize_destroy(struct window_customize_modedata *data) +{ + u_int i; + + if (--data->references != 0) + return; + + for (i = 0; i < data->item_size; i++) + window_customize_free_item(data->item_list[i]); + free(data->item_list); + + free(data->format); + + free(data); +} + +static void +window_customize_free(struct window_mode_entry *wme) +{ + struct window_customize_modedata *data = wme->data; + + if (data == NULL) + return; + + data->dead = 1; + mode_tree_free(data->data); + window_customize_destroy(data); +} + +static void +window_customize_resize(struct window_mode_entry *wme, u_int sx, u_int sy) +{ + struct window_customize_modedata *data = wme->data; + + mode_tree_resize(data->data, sx, sy); +} + +static void +window_customize_free_callback(void *modedata) +{ + window_customize_destroy(modedata); +} + +static void +window_customize_free_item_callback(void *itemdata) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + + window_customize_free_item(item); + window_customize_destroy(data); +} + +static int +window_customize_set_option_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo = item->oo; + const char *name = item->name; + char *cause; + int idx = item->idx; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return (0); + o = options_get(oo, name); + if (o == NULL) + return (0); + oe = options_table_entry(o); + + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + for (idx = 0; idx < INT_MAX; idx++) { + if (options_array_get(o, idx) == NULL) + break; + } + } + if (options_array_set(o, idx, s, 0, &cause) != 0) + goto fail; + } else { + if (options_from_string(oo, oe, name, s, 0, &cause) != 0) + goto fail; + } + + options_push_changes(item->name); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *cause = toupper((u_char)*cause); + status_message_set(c, 1, "%s", cause); + free(cause); + return (0); +} + +static void +window_customize_set_option(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item, int global, int pane) +{ + struct options_entry *o; + const struct options_table_entry *oe; + struct options *oo; + struct window_customize_itemdata *new_item; + int flag, idx = item->idx; + enum window_customize_scope scope; + u_int choice; + const char *name = item->name, *space = ""; + char *prompt, *value, *text; + struct cmd_find_state fs; + + if (item == NULL || !window_customize_check_item(data, item, &fs)) + return; + o = options_get(item->oo, name); + if (o == NULL) + return; + + oe = options_table_entry(o); + if (~oe->scope & OPTIONS_TABLE_PANE) + pane = 0; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + scope = item->scope; + oo = item->oo; + } else { + if (global) { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_SESSION: + scope = WINDOW_CUSTOMIZE_GLOBAL_SESSION; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + scope = WINDOW_CUSTOMIZE_GLOBAL_WINDOW; + break; + } + } else { + switch (item->scope) { + case WINDOW_CUSTOMIZE_NONE: + case WINDOW_CUSTOMIZE_KEY: + case WINDOW_CUSTOMIZE_SERVER: + case WINDOW_CUSTOMIZE_SESSION: + scope = item->scope; + break; + case WINDOW_CUSTOMIZE_WINDOW: + case WINDOW_CUSTOMIZE_PANE: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + case WINDOW_CUSTOMIZE_GLOBAL_SESSION: + scope = WINDOW_CUSTOMIZE_SESSION; + break; + case WINDOW_CUSTOMIZE_GLOBAL_WINDOW: + if (pane) + scope = WINDOW_CUSTOMIZE_PANE; + else + scope = WINDOW_CUSTOMIZE_WINDOW; + break; + } + } + if (scope == item->scope) + oo = item->oo; + else + oo = window_customize_get_tree(scope, &fs); + } + + if (oe != NULL && oe->type == OPTIONS_TABLE_FLAG) { + flag = options_get_number(oo, name); + options_set_number(oo, name, !flag); + } else if (oe != NULL && oe->type == OPTIONS_TABLE_CHOICE) { + choice = options_get_number(oo, name); + if (oe->choices[choice + 1] == NULL) + choice = 0; + else + choice++; + options_set_number(oo, name, choice); + } else { + text = window_customize_scope_text(scope, &fs); + if (*text != '\0') + space = ", for "; + else if (scope != WINDOW_CUSTOMIZE_SERVER) + space = ", global"; + if (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_ARRAY)) { + if (idx == -1) { + xasprintf(&prompt, "(%s[+]%s%s) ", name, space, + text); + } else { + xasprintf(&prompt, "(%s[%d]%s%s) ", name, idx, + space, text); + } + } else + xasprintf(&prompt, "(%s%s%s) ", name, space, text); + free(text); + + value = options_to_string(o, idx, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = scope; + new_item->oo = oo; + new_item->name = xstrdup(name); + new_item->idx = idx; + + data->references++; + status_prompt_set(c, prompt, value, + window_customize_set_option_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + + free(prompt); + free(value); + } +} + +static void +window_customize_unset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options_entry *o; + const struct options_table_entry *oe; + + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + + o = options_get(item->oo, item->name); + if (o == NULL) + return; + if (item->idx != -1) { + if (item == mode_tree_get_current(data->data)) + mode_tree_up(data->data, 0); + options_array_set(o, item->idx, NULL, 0, NULL); + return; + } + oe = options_table_entry(o); + if (oe != NULL && + options_owner(o) != global_options && + options_owner(o) != global_s_options && + options_owner(o) != global_w_options) + options_remove(o); + else + options_default(options_owner(o), oe); +} + +static int +window_customize_set_command_callback(struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + struct cmd_parse_result *pr; + char *error; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + pr = cmd_parse_from_string(s, NULL); + switch (pr->status) { + case CMD_PARSE_EMPTY: + error = xstrdup("empty command"); + goto fail; + case CMD_PARSE_ERROR: + error = pr->error; + goto fail; + case CMD_PARSE_SUCCESS: + break; + } + cmd_list_free(bd->cmdlist); + bd->cmdlist = pr->cmdlist; + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); + +fail: + *error = toupper((u_char)*error); + status_message_set(c, 1, "%s", error); + free(error); + return (0); +} + +static int +window_customize_set_note_callback(__unused struct client *c, void *itemdata, + const char *s, __unused int done) +{ + struct window_customize_itemdata *item = itemdata; + struct window_customize_modedata *data = item->data; + struct key_binding *bd; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return (0); + + free((void *)bd->note); + bd->note = xstrdup(s); + + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_customize_set_key(struct client *c, + struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + key_code key = item->key; + struct key_binding *bd; + const char *s; + char *prompt, *value; + struct window_customize_itemdata *new_item; + + if (item == NULL || !window_customize_get_key(item, NULL, &bd)) + return; + + s = mode_tree_get_current_name(data->data); + if (strcmp(s, "Repeat") == 0) + bd->flags ^= KEY_BINDING_REPEAT; + else if (strcmp(s, "Command") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + value = cmd_list_print(bd->cmdlist, 0); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, prompt, value, + window_customize_set_command_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + free(prompt); + free(value); + } else if (strcmp(s, "Note") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key)); + + new_item = xcalloc(1, sizeof *new_item); + new_item->data = data; + new_item->scope = item->scope; + new_item->table = xstrdup(item->table); + new_item->key = key; + + data->references++; + status_prompt_set(c, prompt, (bd->note == NULL ? "" : bd->note), + window_customize_set_note_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT); + free(prompt); + } +} + +static void +window_customize_unset_key(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct key_table *kt; + struct key_binding *bd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + if (item == mode_tree_get_current(data->data)) { + mode_tree_collapse_current(data->data); + mode_tree_up(data->data, 0); + } + key_bindings_remove(kt->name, bd->key); +} + +static void +window_customize_unset_each(void *modedata, void *itemdata, + __unused struct client *c, __unused key_code key) +{ + struct window_customize_itemdata *item = itemdata; + + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(modedata, item); + else { + window_customize_unset_option(modedata, item); + options_push_changes(item->name); + } +} + +static int +window_customize_unset_current_callback(__unused struct client *c, + void *modedata, const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + item = mode_tree_get_current(data->data); + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else { + window_customize_unset_option(data, item); + options_push_changes(item->name); + } + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static int +window_customize_unset_tagged_callback(struct client *c, void *modedata, + const char *s, __unused int done) +{ + struct window_customize_modedata *data = modedata; + + if (s == NULL || *s == '\0' || data->dead) + return (0); + if (tolower((u_char) s[0]) != 'y' || s[1] != '\0') + return (0); + + mode_tree_each_tagged(data->data, window_customize_unset_each, c, + KEYC_NONE, 0); + mode_tree_build(data->data); + mode_tree_draw(data->data); + data->wp->flags |= PANE_REDRAW; + + return (0); +} + +static void +window_customize_key(struct window_mode_entry *wme, struct client *c, + __unused struct session *s, __unused struct winlink *wl, key_code key, + struct mouse_event *m) +{ + struct window_pane *wp = wme->wp; + struct window_customize_modedata *data = wme->data; + struct window_customize_itemdata *item, *new_item; + int finished; + char *prompt; + u_int tagged; + + item = mode_tree_get_current(data->data); + finished = mode_tree_key(data->data, c, &key, m, NULL, NULL); + if (item != (new_item = mode_tree_get_current(data->data))) + item = new_item; + + switch (key) { + case '\r': + case 's': + if (item == NULL) + break; + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_set_key(c, data, item); + else { + window_customize_set_option(c, data, item, 0, 1); + options_push_changes(item->name); + } + mode_tree_build(data->data); + break; + case 'w': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_option(c, data, item, 0, 0); + options_push_changes(item->name); + mode_tree_build(data->data); + break; + case 'S': + case 'W': + if (item == NULL || item->scope == WINDOW_CUSTOMIZE_KEY) + break; + window_customize_set_option(c, data, item, 1, 0); + options_push_changes(item->name); + mode_tree_build(data->data); + break; + case 'u': + if (item == NULL) + break; + if (item->scope == WINDOW_CUSTOMIZE_KEY) { + xasprintf(&prompt, "Unbind key %s? ", + key_string_lookup_key(item->key)); + } else + xasprintf(&prompt, "Unset option %s? ", item->name); + data->references++; + status_prompt_set(c, prompt, "", + window_customize_unset_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; + case 'U': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Unset or unbind %u tagged? ", tagged); + data->references++; + status_prompt_set(c, prompt, "", + window_customize_unset_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT); + free(prompt); + break; + case 'H': + data->hide_global = !data->hide_global; + mode_tree_build(data->data); + break; + } + if (finished) + window_pane_reset_mode(wp); + else { + mode_tree_draw(data->data); + wp->flags |= PANE_REDRAW; + } +} diff --git a/window-tree.c b/window-tree.c index 2eff4b8a..5731fff6 100644 --- a/window-tree.c +++ b/window-tree.c @@ -885,7 +885,7 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs, data->squash_groups = !args_has(args, 'G'); data->data = mode_tree_start(wp, args, window_tree_build, - window_tree_draw, window_tree_search, window_tree_menu, data, + window_tree_draw, window_tree_search, window_tree_menu, NULL, data, window_tree_menu_items, window_tree_sort_list, nitems(window_tree_sort_list), &s); mode_tree_zoom(data->data, args);