diff options
Diffstat (limited to 'window-customize.c')
-rw-r--r-- | window-customize.c | 1512 |
1 files changed, 1512 insertions, 0 deletions
diff --git a/window-customize.c b/window-customize.c new file mode 100644 index 0000000..4a16e90 --- /dev/null +++ b/window-customize.c @@ -0,0 +1,1512 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2020 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + +#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 +}; + +enum window_customize_change { + WINDOW_CUSTOMIZE_UNSET, + WINDOW_CUSTOMIZE_RESET, +}; + +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; + enum window_customize_change change; +}; + +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_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; + default: + s = xstrdup(""); + 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 = NULL, *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); + mode_tree_no_tag(top); + + /* + * 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(oo2, list[i]); + if (o == NULL && oo1 != NULL) + o = options_get(oo1, list[i]); + if (o == NULL) + o = options_get(oo0, 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); + mode_tree_no_tag(top); + 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, 0)); + 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; + item->name = xstrdup(key_string_lookup_key(item->key, 0)); + item->idx = -1; + + 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); + mode_tree_no_tag(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); + mode_tree_no_tag(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); + mode_tree_no_tag(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 || 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, NULL, 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, 1, 0, "%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 = WINDOW_CUSTOMIZE_NONE; + 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 != NULL && ~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, NULL, prompt, value, + window_customize_set_option_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + + free(prompt); + free(value); + } +} + +static void +window_customize_unset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options_entry *o; + + 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 && item == mode_tree_get_current(data->data)) + mode_tree_up(data->data, 0); + options_remove_or_default(o, item->idx, NULL); +} + +static void +window_customize_reset_option(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct options *oo; + struct options_entry *o; + + if (item == NULL || !window_customize_check_item(data, item, NULL)) + return; + if (item->idx != -1) + return; + + oo = item->oo; + while (oo != NULL) { + o = options_get_only(item->oo, item->name); + if (o != NULL) + options_remove_or_default(o, -1, NULL); + oo = options_get_parent(oo); + } +} + +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_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, 1, 0, "%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, 0)); + 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, NULL, prompt, value, + window_customize_set_command_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + free(value); + } else if (strcmp(s, "Note") == 0) { + xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 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, NULL, prompt, + (bd->note == NULL ? "" : bd->note), + window_customize_set_note_callback, + window_customize_free_item_callback, new_item, + PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + 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_reset_key(struct window_customize_modedata *data, + struct window_customize_itemdata *item) +{ + struct key_table *kt; + struct key_binding *dd, *bd; + + if (item == NULL || !window_customize_get_key(item, &kt, &bd)) + return; + + dd = key_bindings_get_default(kt, bd->key); + if (dd != NULL && bd->cmdlist == dd->cmdlist) + return; + if (dd == NULL && item == mode_tree_get_current(data->data)) { + mode_tree_collapse_current(data->data); + mode_tree_up(data->data, 0); + } + key_bindings_reset(kt->name, bd->key); +} + +static void +window_customize_change_each(void *modedata, void *itemdata, + __unused struct client *c, __unused key_code key) +{ + struct window_customize_modedata *data = modedata; + struct window_customize_itemdata *item = itemdata; + + switch (data->change) { + case WINDOW_CUSTOMIZE_UNSET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else + window_customize_unset_option(data, item); + break; + case WINDOW_CUSTOMIZE_RESET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_reset_key(data, item); + else + window_customize_reset_option(data, item); + break; + } + if (item->scope != WINDOW_CUSTOMIZE_KEY) + options_push_changes(item->name); +} + +static int +window_customize_change_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); + switch (data->change) { + case WINDOW_CUSTOMIZE_UNSET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_unset_key(data, item); + else + window_customize_unset_option(data, item); + break; + case WINDOW_CUSTOMIZE_RESET: + if (item->scope == WINDOW_CUSTOMIZE_KEY) + window_customize_reset_key(data, item); + else + window_customize_reset_option(data, item); + break; + } + if (item->scope != WINDOW_CUSTOMIZE_KEY) + 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_change_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_change_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, idx; + 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 'd': + if (item == NULL || item->idx != -1) + break; + xasprintf(&prompt, "Reset %s to default? ", item->name); + data->references++; + data->change = WINDOW_CUSTOMIZE_RESET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'D': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Reset %u tagged to default? ", tagged); + data->references++; + data->change = WINDOW_CUSTOMIZE_RESET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'u': + if (item == NULL) + break; + idx = item->idx; + if (idx != -1) + xasprintf(&prompt, "Unset %s[%d]? ", item->name, idx); + else + xasprintf(&prompt, "Unset %s? ", item->name); + data->references++; + data->change = WINDOW_CUSTOMIZE_UNSET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_current_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + free(prompt); + break; + case 'U': + tagged = mode_tree_count_tagged(data->data); + if (tagged == 0) + break; + xasprintf(&prompt, "Unset %u tagged? ", tagged); + data->references++; + data->change = WINDOW_CUSTOMIZE_UNSET; + status_prompt_set(c, NULL, prompt, "", + window_customize_change_tagged_callback, + window_customize_free_callback, data, + PROMPT_SINGLE|PROMPT_NOFORMAT, PROMPT_TYPE_COMMAND); + 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; + } +} |