From 86785004baf4086816b5d6684b9d0e4c56b58ea6 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 28 Jul 2009 07:03:32 +0000 Subject: [PATCH] Next step towards customisable mode keys: build each default table of keys into a named tree on start and use that for lookups. Also add command to string translation tables and modify list-keys to show the the mode key bindings (new -t argument). --- cmd-list-keys.c | 63 +++++++++++++++-- mode-key.c | 179 +++++++++++++++++++++++++++++++++++++++++++----- server.c | 2 + status.c | 4 +- tmux.1 | 39 ++++++++++- tmux.h | 56 +++++++++++---- window-choose.c | 4 +- window-copy.c | 4 +- window-more.c | 4 +- window-scroll.c | 4 +- 10 files changed, 311 insertions(+), 48 deletions(-) diff --git a/cmd-list-keys.c b/cmd-list-keys.c index 9110d12a..0a22e82c 100644 --- a/cmd-list-keys.c +++ b/cmd-list-keys.c @@ -28,25 +28,31 @@ int cmd_list_keys_exec(struct cmd *, struct cmd_ctx *); +int cmd_list_keys_table(struct cmd *, struct cmd_ctx *); + const struct cmd_entry cmd_list_keys_entry = { "list-keys", "lsk", - "", + "[-t key-table]", 0, 0, - NULL, - NULL, + cmd_target_init, + cmd_target_parse, cmd_list_keys_exec, - NULL, - NULL + cmd_target_free, + cmd_target_print }; int -cmd_list_keys_exec(unused struct cmd *self, struct cmd_ctx *ctx) +cmd_list_keys_exec(struct cmd *self, struct cmd_ctx *ctx) { + struct cmd_target_data *data = self->data; struct key_binding *bd; const char *key; char tmp[BUFSIZ], keytmp[64]; int width, keywidth; + if (data->target != NULL) + return (cmd_list_keys_table(self, ctx)); + width = 0; SPLAY_FOREACH(bd, key_bindings, &key_bindings) { key = key_string_lookup_key(bd->key & ~KEYC_PREFIX); @@ -76,3 +82,48 @@ cmd_list_keys_exec(unused struct cmd *self, struct cmd_ctx *ctx) return (0); } + +int +cmd_list_keys_table(struct cmd *self, struct cmd_ctx *ctx) +{ + struct cmd_target_data *data = self->data; + const struct mode_key_table *mtab; + struct mode_key_binding *mbind; + const char *key, *cmdstr, *mode; + int width, keywidth; + + for (mtab = mode_key_tables; mtab->name != NULL; mtab++) { + if (strcasecmp(data->target, mtab->name) == 0) + break; + } + if (mtab->name == NULL) { + ctx->error(ctx, "unknown key table: %s", data->target); + return (-1); + } + + width = 0; + SPLAY_FOREACH(mbind, mode_key_tree, mtab->tree) { + key = key_string_lookup_key(mbind->key); + if (key == NULL) + continue; + + keywidth = strlen(key) + 1; + if (keywidth > width) + width = keywidth; + } + + SPLAY_FOREACH(mbind, mode_key_tree, mtab->tree) { + key = key_string_lookup_key(mbind->key); + if (key == NULL) + continue; + + mode = ""; + if (mbind->mode != 0) + mode = "(command mode) "; + cmdstr = mode_key_tostring(mtab->cmdstr, mbind->cmd); + if (cmdstr != NULL) + ctx->print(ctx, "%*s: %s%s", width, key, mode, cmdstr); + } + + return (0); +} diff --git a/mode-key.c b/mode-key.c index 154da684..e0f773d5 100644 --- a/mode-key.c +++ b/mode-key.c @@ -20,6 +20,71 @@ #include "tmux.h" +/* + * Mode keys. These are the key bindings used when editing (status prompt), and + * in the modes. They are split into two sets of three tables, one set of three + * for vi and the other for emacs key bindings. The three tables are for + * editing, for menu-like modes (choice, more), and for copy modes (copy, + * scroll). + * + * The fixed tables of struct mode_key_entry below are the defaults: they are + * built into a tree of struct mode_key_binding by mode_key_init_trees, which + * can then be modified. + * + * vi command mode is handled by having a mode flag in the struct which allows + * two sets of bindings to be swapped between. A couple of editing commands + * (MODEKEYEDIT_SWITCHMODE and MODEKEYEDIT_SWITCHMODEAPPEND) are special-cased + * to do this. + */ + +/* Edit keys command strings. */ +struct mode_key_cmdstr mode_key_cmdstr_edit[] = { + { MODEKEYEDIT_BACKSPACE, "backspace" }, + { MODEKEYEDIT_CANCEL, "cancel" }, + { MODEKEYEDIT_COMPLETE, "complete" }, + { MODEKEYEDIT_CURSORLEFT, "cursor-left" }, + { MODEKEYEDIT_CURSORRIGHT, "cursor-right" }, + { MODEKEYEDIT_DELETE, "delete" }, + { MODEKEYEDIT_DELETETOENDOFLINE, "delete-end-of-line" }, + { MODEKEYEDIT_ENDOFLINE, "end-of-line" }, + { MODEKEYEDIT_ENTER, "enter" }, + { MODEKEYEDIT_HISTORYDOWN, "history-down" }, + { MODEKEYEDIT_HISTORYUP, "history-up" }, + { MODEKEYEDIT_PASTE, "paste" }, + { MODEKEYEDIT_STARTOFLINE, "start-of-line" }, + { MODEKEYEDIT_SWITCHMODE, "switch-mode" }, + { MODEKEYEDIT_SWITCHMODEAPPEND, "switch-mode-append" }, +}; + +/* Choice keys command strings. */ +struct mode_key_cmdstr mode_key_cmdstr_choice[] = { + { MODEKEYCHOICE_CANCEL, "cancel" }, + { MODEKEYCHOICE_CHOOSE, "choose" }, + { MODEKEYCHOICE_DOWN, "down" }, + { MODEKEYCHOICE_PAGEDOWN, "page-down" }, + { MODEKEYCHOICE_PAGEUP, "page-up" }, + { MODEKEYCHOICE_UP, "up" }, +}; + +/* Copy keys command strings. */ +struct mode_key_cmdstr mode_key_cmdstr_copy[] = { + { MODEKEYCOPY_CANCEL, "cancel" }, + { MODEKEYCOPY_BACKTOINDENTATION, "back-to-indentation" }, + { MODEKEYCOPY_CLEARSELECTION, "clear-selection" }, + { MODEKEYCOPY_COPYSELECTION, "copy-selection" }, + { MODEKEYCOPY_DOWN, "cursor-down" }, + { MODEKEYCOPY_ENDOFLINE, "end-of-line" }, + { MODEKEYCOPY_LEFT, "cursor-left" }, + { MODEKEYCOPY_NEXTPAGE, "page-down" }, + { MODEKEYCOPY_NEXTWORD, "next-word" }, + { MODEKEYCOPY_PREVIOUSPAGE, "page-up" }, + { MODEKEYCOPY_PREVIOUSWORD, "previous-word" }, + { MODEKEYCOPY_RIGHT, "cursor-right" }, + { MODEKEYCOPY_STARTOFLINE, "start-of-line" }, + { MODEKEYCOPY_STARTSELECTION, "begin-selection" }, + { MODEKEYCOPY_UP, "cursor-up" }, +}; + /* vi editing keys. */ const struct mode_key_entry mode_key_vi_edit[] = { { '\003' /* C-c */, 0, MODEKEYEDIT_CANCEL }, @@ -53,6 +118,7 @@ const struct mode_key_entry mode_key_vi_edit[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_vi_edit; /* vi choice selection keys. */ const struct mode_key_entry mode_key_vi_choice[] = { @@ -68,6 +134,7 @@ const struct mode_key_entry mode_key_vi_choice[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_vi_choice; /* vi copy mode keys. */ const struct mode_key_entry mode_key_vi_copy[] = { @@ -98,6 +165,7 @@ const struct mode_key_entry mode_key_vi_copy[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_vi_copy; /* emacs editing keys. */ const struct mode_key_entry mode_key_emacs_edit[] = { @@ -123,6 +191,7 @@ const struct mode_key_entry mode_key_emacs_edit[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_emacs_edit; /* emacs choice selection keys. */ const struct mode_key_entry mode_key_emacs_choice[] = { @@ -137,6 +206,7 @@ const struct mode_key_entry mode_key_emacs_choice[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_emacs_choice; /* emacs copy mode keys. */ const struct mode_key_entry mode_key_emacs_copy[] = { @@ -168,34 +238,105 @@ const struct mode_key_entry mode_key_emacs_copy[] = { { 0, -1, 0 } }; +struct mode_key_tree mode_key_tree_emacs_copy; + +/* Table mapping key table names to default settings and trees. */ +const struct mode_key_table mode_key_tables[] = { + { "vi-edit", mode_key_cmdstr_edit, + &mode_key_tree_vi_edit, mode_key_vi_edit }, + { "vi-choice", mode_key_cmdstr_choice, + &mode_key_tree_vi_choice, mode_key_vi_choice }, + { "vi-copy", mode_key_cmdstr_copy, + &mode_key_tree_vi_copy, mode_key_vi_copy }, + { "emacs-edit", mode_key_cmdstr_edit, + &mode_key_tree_emacs_edit, mode_key_emacs_edit }, + { "emacs-choice", mode_key_cmdstr_choice, + &mode_key_tree_emacs_choice, mode_key_emacs_choice }, + { "emacs-copy", mode_key_cmdstr_copy, + &mode_key_tree_emacs_copy, mode_key_emacs_copy }, + + { NULL, NULL, NULL, NULL } +}; + +SPLAY_GENERATE(mode_key_tree, mode_key_binding, entry, mode_key_cmp); + +int +mode_key_cmp(struct mode_key_binding *mbind1, struct mode_key_binding *mbind2) +{ + if (mbind1->mode != mbind2->mode) + return (mbind1->mode - mbind2->mode); + return (mbind1->key - mbind2->key); +} + +const char * +mode_key_tostring(struct mode_key_cmdstr *cmdstr, enum mode_key_cmd cmd) +{ + for (; cmdstr->name != NULL; cmdstr++) { + if (cmdstr->cmd == cmd) + return (cmdstr->name); + } + return (NULL); +} void -mode_key_init(struct mode_key_data *mdata, const struct mode_key_entry *table) +mode_key_init_trees(void) { - mdata->table = table; + const struct mode_key_table *mtab; + const struct mode_key_entry *ment; + struct mode_key_binding *mbind; + + for (mtab = mode_key_tables; mtab->name != NULL; mtab++) { + SPLAY_INIT(mtab->tree); + for (ment = mtab->table; ment->mode != -1; ment++) { + mbind = xmalloc(sizeof *mbind); + mbind->key = ment->key; + mbind->mode = ment->mode; + mbind->cmd = ment->cmd; + SPLAY_INSERT(mode_key_tree, mtab->tree, mbind); + } + } +} + +void +mode_key_free_trees(void) +{ + const struct mode_key_table *mtab; + struct mode_key_binding *mbind; + + for (mtab = mode_key_tables; mtab->name != NULL; mtab++) { + while (!SPLAY_EMPTY(mtab->tree)) { + mbind = SPLAY_ROOT(mtab->tree); + SPLAY_REMOVE(mode_key_tree, mtab->tree, mbind); + } + } +} + +void +mode_key_init(struct mode_key_data *mdata, struct mode_key_tree *mtree) +{ + mdata->tree = mtree; mdata->mode = 0; } enum mode_key_cmd mode_key_lookup(struct mode_key_data *mdata, int key) { - const struct mode_key_entry *ment; - int mode; + struct mode_key_binding *mbind, mtmp; - mode = mdata->mode; - for (ment = mdata->table; ment->mode != -1; ment++) { - if (ment->mode == mode && key == ment->key) { - switch (ment->cmd) { - case MODEKEYEDIT_SWITCHMODE: - case MODEKEYEDIT_SWITCHMODEAPPEND: - mdata->mode = 1 - mdata->mode; - /* FALLTHROUGH */ - default: - return (ment->cmd); - } - } + mtmp.key = key; + mtmp.mode = mdata->mode; + if ((mbind = SPLAY_FIND(mode_key_tree, mdata->tree, &mtmp)) == NULL) { + if (mdata->mode != 0) + return (MODEKEY_NONE); + return (MODEKEY_OTHER); + } + + switch (mbind->cmd) { + case MODEKEYEDIT_SWITCHMODE: + case MODEKEYEDIT_SWITCHMODEAPPEND: + mdata->mode = 1 - mdata->mode; + /* FALLTHROUGH */ + default: + return (mbind->cmd); } - if (mode != 0) - return (MODEKEY_NONE); - return (MODEKEY_OTHER); } diff --git a/server.c b/server.c index ce793076..668a8251 100644 --- a/server.c +++ b/server.c @@ -160,6 +160,7 @@ server_start(char *path) ARRAY_INIT(&windows); ARRAY_INIT(&clients); ARRAY_INIT(&sessions); + mode_key_init_trees(); key_bindings_init(); utf8_build(); @@ -379,6 +380,7 @@ server_main(int srv_fd) } ARRAY_FREE(&clients); + mode_key_free_trees(); key_bindings_free(); close(srv_fd); diff --git a/status.c b/status.c index fef440eb..7fcf5e9b 100644 --- a/status.c +++ b/status.c @@ -609,9 +609,9 @@ status_prompt_set(struct client *c, const char *msg, keys = options_get_number(&c->session->options, "status-keys"); if (keys == MODEKEY_EMACS) - mode_key_init(&c->prompt_mdata, mode_key_emacs_edit); + mode_key_init(&c->prompt_mdata, &mode_key_tree_emacs_edit); else - mode_key_init(&c->prompt_mdata, mode_key_vi_edit); + mode_key_init(&c->prompt_mdata, &mode_key_tree_vi_edit); c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE); c->flags |= CLIENT_STATUS; diff --git a/tmux.1 b/tmux.1 index d3e8979d..1a3b1664 100644 --- a/tmux.1 +++ b/tmux.1 @@ -331,10 +331,29 @@ The following keys are supported as appropriate for the mode: .It Li "Cursor right" Ta "l" Ta "Right" .It Li "Start selection" Ta "Space" Ta "C-Space" .It Li "Cursor up" Ta "k" Ta "Up" -.It Li "Delete to end of line" Ta "D or C" Ta "C-k" +.It Li "Delete to end of line" Ta "D" Ta "C-k" .It Li "Paste buffer" Ta "p" Ta "C-y" .El .Pp +These key bindings are defined in a set of named tables: +.Em vi-edit +and +.Em emacs-edit +for keys used when line editing at the command prompt; +.Em vi-choice +and +.Em emacs-choice +for keys used when choosing from lists (such as produced by the +.Ic window-choose +command) or in output mode; and +.Em vi-copy +and +.Em emacs-copy +used in copy and scroll modes. +The tables may be viewed with the +.Ic list-keys +command. +.Pp The paste buffer key pastes the first line from the top paste buffer on the stack. .Sh BUFFERS @@ -847,13 +866,31 @@ List all clients attached to the server. List the syntax of all commands supported by .Nm . .It Xo Ic list-keys +.Op Ar Fl t Ar key-table .Xc .D1 (alias: Ic lsk ) List all key bindings. +Without +.Fl t +the primary key bindings - those executed when preceded by the prefix key - +are printed. Keys bound without the prefix key (see .Ic bind-key .Fl n ) are enclosed in square brackets. +.Pp +With +.Fl t , +the key bindings in +.Ar key-table +are listed; this may be one of: +.Em vi-edit , +.Em emacs-edit , +.Em vi-choice , +.Em emacs-choice , +.Em vi-copy +or +.Em emacs-copy . .It Xo Ic list-sessions .Xc .D1 (alias: Ic ls ) diff --git a/tmux.h b/tmux.h index af0fed59..9d5898ae 100644 --- a/tmux.h +++ b/tmux.h @@ -356,7 +356,7 @@ struct msg_unlock_data { char pass[PASS_MAX]; }; -/* Editing keys. */ +/* Mode key commands. */ enum mode_key_cmd { MODEKEY_NONE, MODEKEY_OTHER, @@ -404,6 +404,7 @@ enum mode_key_cmd { MODEKEYCOPY_UP, }; +/* Entry in the default mode key tables. */ struct mode_key_entry { int key; @@ -414,16 +415,42 @@ struct mode_key_entry { * keys to be bound in edit mode. */ int mode; - enum mode_key_cmd cmd; }; + +/* Data required while mode keys are in use. */ struct mode_key_data { - const struct mode_key_entry *table; - int mode; + struct mode_key_tree *tree; + int mode; }; #define MODEKEY_EMACS 0 #define MODEKEY_VI 1 +/* Binding between a key and a command. */ +struct mode_key_binding { + int key; + + int mode; + enum mode_key_cmd cmd; + + SPLAY_ENTRY(mode_key_binding) entry; +}; +SPLAY_HEAD(mode_key_tree, mode_key_binding); + +/* Command to string mapping. */ +struct mode_key_cmdstr { + enum mode_key_cmd cmd; + const char *name; +}; + +/* Named mode key table description. */ +struct mode_key_table { + const char *name; + struct mode_key_cmdstr *cmdstr; + struct mode_key_tree *tree; + const struct mode_key_entry *table; /* default entries */ +}; + /* Modes. */ #define MODE_CURSOR 0x1 #define MODE_INSERT 0x2 @@ -1061,14 +1088,19 @@ void sighandler(int); int load_cfg(const char *, char **x); /* mode-key.c */ -extern const struct mode_key_entry mode_key_vi_edit[]; -extern const struct mode_key_entry mode_key_vi_choice[]; -extern const struct mode_key_entry mode_key_vi_copy[]; -extern const struct mode_key_entry mode_key_emacs_edit[]; -extern const struct mode_key_entry mode_key_emacs_choice[]; -extern const struct mode_key_entry mode_key_emacs_copy[]; -void mode_key_init( - struct mode_key_data *, const struct mode_key_entry *); +extern const struct mode_key_table mode_key_tables[]; +extern struct mode_key_tree mode_key_tree_vi_edit; +extern struct mode_key_tree mode_key_tree_vi_choice; +extern struct mode_key_tree mode_key_tree_vi_copy; +extern struct mode_key_tree mode_key_tree_emacs_edit; +extern struct mode_key_tree mode_key_tree_emacs_choice; +extern struct mode_key_tree mode_key_tree_emacs_copy; +int mode_key_cmp(struct mode_key_binding *, struct mode_key_binding *); +SPLAY_PROTOTYPE(mode_key_tree, mode_key_binding, entry, mode_key_cmp); +const char *mode_key_tostring(struct mode_key_cmdstr *r, enum mode_key_cmd); +void mode_key_init_trees(void); +void mode_key_free_trees(void); +void mode_key_init(struct mode_key_data *, struct mode_key_tree *); enum mode_key_cmd mode_key_lookup(struct mode_key_data *, int); /* options.c */ diff --git a/window-choose.c b/window-choose.c index b1ae3bc5..e7d847de 100644 --- a/window-choose.c +++ b/window-choose.c @@ -127,9 +127,9 @@ window_choose_init(struct window_pane *wp) keys = options_get_number(&wp->window->options, "mode-keys"); if (keys == MODEKEY_EMACS) - mode_key_init(&data->mdata, mode_key_emacs_choice); + mode_key_init(&data->mdata, &mode_key_tree_emacs_choice); else - mode_key_init(&data->mdata, mode_key_vi_choice); + mode_key_init(&data->mdata, &mode_key_tree_vi_choice); return (s); } diff --git a/window-copy.c b/window-copy.c index 5fbfd0b8..8ea32126 100644 --- a/window-copy.c +++ b/window-copy.c @@ -108,9 +108,9 @@ window_copy_init(struct window_pane *wp) keys = options_get_number(&wp->window->options, "mode-keys"); if (keys == MODEKEY_EMACS) - mode_key_init(&data->mdata, mode_key_emacs_copy); + mode_key_init(&data->mdata, &mode_key_tree_emacs_copy); else - mode_key_init(&data->mdata, mode_key_vi_copy); + mode_key_init(&data->mdata, &mode_key_tree_vi_copy); s->cx = data->cx; s->cy = data->cy; diff --git a/window-more.c b/window-more.c index a113c4ba..a3418eb0 100644 --- a/window-more.c +++ b/window-more.c @@ -92,9 +92,9 @@ window_more_init(struct window_pane *wp) keys = options_get_number(&wp->window->options, "mode-keys"); if (keys == MODEKEY_EMACS) - mode_key_init(&data->mdata, mode_key_emacs_choice); + mode_key_init(&data->mdata, &mode_key_tree_emacs_choice); else - mode_key_init(&data->mdata, mode_key_vi_choice); + mode_key_init(&data->mdata, &mode_key_tree_vi_choice); return (s); } diff --git a/window-scroll.c b/window-scroll.c index d4215338..12249fc1 100644 --- a/window-scroll.c +++ b/window-scroll.c @@ -75,9 +75,9 @@ window_scroll_init(struct window_pane *wp) keys = options_get_number(&wp->window->options, "mode-keys"); if (keys == MODEKEY_EMACS) - mode_key_init(&data->mdata, mode_key_emacs_copy); + mode_key_init(&data->mdata, &mode_key_tree_emacs_copy); else - mode_key_init(&data->mdata, mode_key_vi_copy); + mode_key_init(&data->mdata, &mode_key_tree_vi_copy); screen_write_start(&ctx, NULL, s); for (i = 0; i < screen_size_y(s); i++)