Support for multiple key tables to commands to be bound to sequences of
authornicm <nicm@openbsd.org>
Mon, 20 Apr 2015 15:34:56 +0000 (15:34 +0000)
committernicm <nicm@openbsd.org>
Mon, 20 Apr 2015 15:34:56 +0000 (15:34 +0000)
keys. The default key bindings become the "prefix" table and -n the
"root" table. Keys may be bound in new tables with bind -T and
switch-client -T used to specify the table in which the next key should
be looked up. Based on a diff from Keith Amling.

usr.bin/tmux/cmd-bind-key.c
usr.bin/tmux/cmd-list-keys.c
usr.bin/tmux/cmd-switch-client.c
usr.bin/tmux/cmd-unbind-key.c
usr.bin/tmux/format.c
usr.bin/tmux/key-bindings.c
usr.bin/tmux/server-client.c
usr.bin/tmux/tmux.1
usr.bin/tmux/tmux.h

index 8987db4..bc0c96c 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-bind-key.c,v 1.20 2015/04/10 16:00:08 nicm Exp $ */
+/* $OpenBSD: cmd-bind-key.c,v 1.21 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -33,8 +33,8 @@ enum cmd_retval        cmd_bind_key_mode_table(struct cmd *, struct cmd_q *, int);
 
 const struct cmd_entry cmd_bind_key_entry = {
        "bind-key", "bind",
-       "cnrt:", 1, -1,
-       "[-cnr] [-t mode-table] key command [arguments]",
+       "cnrt:T:", 1, -1,
+       "[-cnr] [-t mode-table] [-T key-table] key command [arguments]",
        0,
        cmd_bind_key_exec
 };
@@ -46,6 +46,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
        char            *cause;
        struct cmd_list *cmdlist;
        int              key;
+       const char      *tablename;
 
        if (args_has(args, 't')) {
                if (args->argc != 2 && args->argc != 3) {
@@ -68,6 +69,13 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
        if (args_has(args, 't'))
                return (cmd_bind_key_mode_table(self, cmdq, key));
 
+       if (args_has(args, 'T'))
+               tablename = args_get(args, 'T');
+       else if (args_has(args, 'n'))
+               tablename = "root";
+       else
+               tablename = "prefix";
+
        cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0,
            &cause);
        if (cmdlist == NULL) {
@@ -76,9 +84,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
                return (CMD_RETURN_ERROR);
        }
 
-       if (!args_has(args, 'n'))
-           key |= KEYC_PREFIX;
-       key_bindings_add(key, args_has(args, 'r'), cmdlist);
+       key_bindings_add(tablename, key, args_has(args, 'r'), cmdlist);
        return (CMD_RETURN_NORMAL);
 }
 
index e11cec4..fc6f19e 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-list-keys.c,v 1.25 2014/10/20 23:27:14 nicm Exp $ */
+/* $OpenBSD: cmd-list-keys.c,v 1.26 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -33,8 +33,8 @@ enum cmd_retval        cmd_list_keys_commands(struct cmd *, struct cmd_q *);
 
 const struct cmd_entry cmd_list_keys_entry = {
        "list-keys", "lsk",
-       "t:", 0, 0,
-       "[-t key-table]",
+       "t:T:", 0, 0,
+       "[-t mode-table] [-T key-table]",
        0,
        cmd_list_keys_exec
 };
@@ -51,11 +51,12 @@ enum cmd_retval
 cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq)
 {
        struct args             *args = self->args;
+       struct key_table        *table;
        struct key_binding      *bd;
-       const char              *key;
-       char                     tmp[BUFSIZ], flags[8];
+       const char              *key, *tablename, *r;
+       char                     tmp[BUFSIZ];
        size_t                   used;
-       int                      width, keywidth;
+       int                      repeat, width, tablewidth, keywidth;
 
        if (self->entry == &cmd_list_commands_entry)
                return (cmd_list_keys_commands(self, cmdq));
@@ -63,46 +64,57 @@ cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq)
        if (args_has(args, 't'))
                return (cmd_list_keys_table(self, cmdq));
 
-       width = 0;
+       tablename = args_get(args, 'T');
+       if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) {
+               cmdq_error(cmdq, "table %s doesn't exist", tablename);
+               return (CMD_RETURN_ERROR);
+       }
 
-       RB_FOREACH(bd, key_bindings, &key_bindings) {
-               key = key_string_lookup_key(bd->key & ~KEYC_PREFIX);
-               if (key == NULL)
+       repeat = 0;
+       tablewidth = keywidth = 0;
+       RB_FOREACH(table, key_tables, &key_tables) {
+               if (tablename != NULL && strcmp(table->name, tablename) != 0)
                        continue;
+               RB_FOREACH(bd, key_bindings, &table->key_bindings) {
+                       key = key_string_lookup_key(bd->key);
+                       if (key == NULL)
+                               continue;
 
-               keywidth = strlen(key);
-               if (!(bd->key & KEYC_PREFIX)) {
                        if (bd->can_repeat)
-                               keywidth += 4;
-                       else
-                               keywidth += 3;
-               } else if (bd->can_repeat)
-                       keywidth += 3;
-               if (keywidth > width)
-                       width = keywidth;
+                               repeat = 1;
+
+                       width = strlen(table->name);
+                       if (width > tablewidth)
+                               tablewidth =width;
+                       width = strlen(key);
+                       if (width > keywidth)
+                               keywidth = width;
+               }
        }
 
-       RB_FOREACH(bd, key_bindings, &key_bindings) {
-               key = key_string_lookup_key(bd->key & ~KEYC_PREFIX);
-               if (key == NULL)
+       RB_FOREACH(table, key_tables, &key_tables) {
+               if (tablename != NULL && strcmp(table->name, tablename) != 0)
                        continue;
-
-               *flags = '\0';
-               if (!(bd->key & KEYC_PREFIX)) {
-                       if (bd->can_repeat)
-                               xsnprintf(flags, sizeof flags, "-rn ");
+               RB_FOREACH(bd, key_bindings, &table->key_bindings) {
+                       key = key_string_lookup_key(bd->key);
+                       if (key == NULL)
+                               continue;
+
+                       if (!repeat)
+                               r = "";
+                       else if (bd->can_repeat)
+                               r = "-r ";
                        else
-                               xsnprintf(flags, sizeof flags, "-n ");
-               } else if (bd->can_repeat)
-                       xsnprintf(flags, sizeof flags, "-r ");
-
-               used = xsnprintf(tmp, sizeof tmp, "%s%*s ",
-                   flags, (int) (width - strlen(flags)), key);
-               if (used >= sizeof tmp)
-                       continue;
-
-               cmd_list_print(bd->cmdlist, tmp + used, (sizeof tmp) - used);
-               cmdq_print(cmdq, "bind-key %s", tmp);
+                               r = "   ";
+                       used = xsnprintf(tmp, sizeof tmp, "%s-T %-*s %-*s ", r,
+                           (int)tablewidth, table->name, (int)keywidth, key);
+                       if (used < sizeof tmp) {
+                               cmd_list_print(bd->cmdlist, tmp + used,
+                                   (sizeof tmp) - used);
+                       }
+
+                       cmdq_print(cmdq, "bind-key %s", tmp);
+               }
        }
 
        return (CMD_RETURN_NORMAL);
index 8b7b5be..ee29af6 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-switch-client.c,v 1.22 2014/10/20 22:29:25 nicm Exp $ */
+/* $OpenBSD: cmd-switch-client.c,v 1.23 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -31,8 +31,8 @@ enum cmd_retval        cmd_switch_client_exec(struct cmd *, struct cmd_q *);
 
 const struct cmd_entry cmd_switch_client_entry = {
        "switch-client", "switchc",
-       "lc:npt:r", 0, 0,
-       "[-lnpr] [-c target-client] [-t target-session]",
+       "lc:npt:rT:", 0, 0,
+       "[-lnpr] [-c target-client] [-t target-session] [-T key-table]",
        CMD_READONLY,
        cmd_switch_client_exec
 };
@@ -46,7 +46,8 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
        struct winlink          *wl = NULL;
        struct window           *w = NULL;
        struct window_pane      *wp = NULL;
-       const char              *tflag;
+       const char              *tflag, *tablename;
+       struct key_table        *table;
 
        if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL)
                return (CMD_RETURN_ERROR);
@@ -58,6 +59,18 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
                        c->flags |= CLIENT_READONLY;
        }
 
+       tablename = args_get(args, 'T');
+       if (tablename != NULL) {
+               table = key_bindings_get_table(tablename, 0);
+               if (table == NULL) {
+                       cmdq_error(cmdq, "table %s doesn't exist", tablename);
+                       return (CMD_RETURN_ERROR);
+               }
+               table->references++;
+               key_bindings_unref_table(c->keytable);
+               c->keytable = table;
+       }
+
        tflag = args_get(args, 't');
        if (args_has(args, 'n')) {
                if ((s = session_next_session(c->session)) == NULL) {
index 4298844..b56ea8f 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-unbind-key.c,v 1.20 2014/10/20 22:29:25 nicm Exp $ */
+/* $OpenBSD: cmd-unbind-key.c,v 1.21 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -31,8 +31,8 @@ enum cmd_retval        cmd_unbind_key_mode_table(struct cmd *, struct cmd_q *, int);
 
 const struct cmd_entry cmd_unbind_key_entry = {
        "unbind-key", "unbind",
-       "acnt:", 0, 1,
-       "[-acn] [-t mode-table] key",
+       "acnt:T:", 0, 1,
+       "[-acn] [-t mode-table] [-T key-table] key",
        0,
        cmd_unbind_key_exec
 };
@@ -40,9 +40,9 @@ const struct cmd_entry cmd_unbind_key_entry = {
 enum cmd_retval
 cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq)
 {
-       struct args             *args = self->args;
-       struct key_binding      *bd;
-       int                      key;
+       struct args     *args = self->args;
+       int              key;
+       const char      *tablename;
 
        if (!args_has(args, 'a')) {
                if (args->argc != 1) {
@@ -66,16 +66,31 @@ cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq)
                return (cmd_unbind_key_mode_table(self, cmdq, key));
 
        if (key == KEYC_NONE) {
-               while (!RB_EMPTY(&key_bindings)) {
-                       bd = RB_ROOT(&key_bindings);
-                       key_bindings_remove(bd->key);
+               tablename = args_get(args, 'T');
+               if (tablename == NULL) {
+                       key_bindings_remove_table("root");
+                       key_bindings_remove_table("prefix");
+                       return (CMD_RETURN_NORMAL);
                }
+               if (key_bindings_get_table(tablename, 0) == NULL) {
+                       cmdq_error(cmdq, "table %s doesn't exist", tablename);
+                       return (CMD_RETURN_ERROR);
+               }
+               key_bindings_remove_table(tablename);
                return (CMD_RETURN_NORMAL);
        }
 
-       if (!args_has(args, 'n'))
-               key |= KEYC_PREFIX;
-       key_bindings_remove(key);
+       if (args_has(args, 'T')) {
+               tablename = args_get(args, 'T');
+               if (key_bindings_get_table(tablename, 0) == NULL) {
+                       cmdq_error(cmdq, "table %s doesn't exist", tablename);
+                       return (CMD_RETURN_ERROR);
+               }
+       } else if (args_has(args, 'n'))
+               tablename = "root";
+       else
+               tablename = "prefix";
+       key_bindings_remove(tablename, key);
        return (CMD_RETURN_NORMAL);
 }
 
index ccf58f1..df93415 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: format.c,v 1.60 2015/03/31 17:58:36 nicm Exp $ */
+/* $OpenBSD: format.c,v 1.61 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2011 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -545,7 +545,11 @@ format_defaults_client(struct format_tree *ft, struct client *c)
        format_add(ft, "client_activity", "%lld", (long long) t);
        format_add(ft, "client_activity_string", "%s", format_time_string(t));
 
-       format_add(ft, "client_prefix", "%d", !!(c->flags & CLIENT_PREFIX));
+       if (strcmp(c->keytable->name, "root") == 0)
+               format_add(ft, "client_prefix", "%d", 0);
+       else
+               format_add(ft, "client_prefix", "%d", 1);
+       format_add(ft, "client_key_table", "%s", c->keytable->name);
 
        if (c->tty.flags & TTY_UTF8)
                format_add(ft, "client_utf8", "%d", 1);
index acea8ac..f5bd3b2 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: key-bindings.c,v 1.44 2015/04/19 21:34:21 nicm Exp $ */
+/* $OpenBSD: key-bindings.c,v 1.45 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
 #include "tmux.h"
 
 RB_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp);
+RB_GENERATE(key_tables, key_table, entry, key_table_cmp);
+struct key_tables key_tables = RB_INITIALIZER(&key_tables);
 
-struct key_bindings    key_bindings;
+int
+key_table_cmp(struct key_table *e1, struct key_table *e2)
+{
+       return (strcmp(e1->name, e2->name));
+}
 
 int
 key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
 {
-       int     key1, key2;
-
-       key1 = bd1->key & ~KEYC_PREFIX;
-       key2 = bd2->key & ~KEYC_PREFIX;
-       if (key1 != key2)
-               return (key1 - key2);
-
-       if (bd1->key & KEYC_PREFIX && !(bd2->key & KEYC_PREFIX))
-               return (-1);
-       if (bd2->key & KEYC_PREFIX && !(bd1->key & KEYC_PREFIX))
-               return (1);
-       return (0);
+       return (bd1->key - bd2->key);
 }
 
-struct key_binding *
-key_bindings_lookup(int key)
+struct key_table *
+key_bindings_get_table(const char *name, int create)
 {
-       struct key_binding      bd;
+       struct key_table        table_find, *table;
+
+       table_find.name = name;
+       table = RB_FIND(key_tables, &key_tables, &table_find);
+       if (table != NULL || !create)
+               return (table);
 
-       bd.key = key;
-       return (RB_FIND(key_bindings, &key_bindings, &bd));
+       table = xmalloc(sizeof *table);
+       table->name = xstrdup(name);
+       RB_INIT(&table->key_bindings);
+
+       table->references = 1; /* one reference in key_tables */
+       RB_INSERT(key_tables, &key_tables, table);
+
+       return (table);
 }
 
 void
-key_bindings_add(int key, int can_repeat, struct cmd_list *cmdlist)
+key_bindings_unref_table(struct key_table *table)
 {
        struct key_binding      *bd;
 
-       key_bindings_remove(key);
+       if (--table->references != 0)
+               return;
+
+       while (!RB_EMPTY(&table->key_bindings)) {
+               bd = RB_ROOT(&table->key_bindings);
+               RB_REMOVE(key_bindings, &table->key_bindings, bd);
+               cmd_list_free(bd->cmdlist);
+               free(bd);
+       }
+
+       free((void *)table->name);
+       free(table);
+}
+
+void
+key_bindings_add(const char *name, int key, int can_repeat,
+    struct cmd_list *cmdlist)
+{
+       struct key_table        *table;
+       struct key_binding       bd_find, *bd;
+
+       table = key_bindings_get_table(name, 1);
+
+       bd_find.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
+       if (bd != NULL) {
+               RB_REMOVE(key_bindings, &table->key_bindings, bd);
+               cmd_list_free(bd->cmdlist);
+               free(bd);
+       }
 
        bd = xmalloc(sizeof *bd);
        bd->key = key;
-       RB_INSERT(key_bindings, &key_bindings, bd);
+       RB_INSERT(key_bindings, &table->key_bindings, bd);
 
        bd->can_repeat = can_repeat;
        bd->cmdlist = cmdlist;
 }
 
 void
-key_bindings_remove(int key)
+key_bindings_remove(const char *name, int key)
 {
-       struct key_binding      *bd;
+       struct key_table        *table;
+       struct key_binding       bd_find, *bd;
+
+       table = key_bindings_get_table(name, 0);
+       if (table == NULL)
+               return;
 
-       if ((bd = key_bindings_lookup(key)) == NULL)
+       bd_find.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
+       if (bd == NULL)
                return;
-       RB_REMOVE(key_bindings, &key_bindings, bd);
+
+       RB_REMOVE(key_bindings, &table->key_bindings, bd);
        cmd_list_free(bd->cmdlist);
        free(bd);
+
+       if (RB_EMPTY(&table->key_bindings)) {
+               RB_REMOVE(key_tables, &key_tables, table);
+               key_bindings_unref_table(table);
+       }
+}
+
+void
+key_bindings_remove_table(const char *name)
+{
+       struct key_table        *table;
+
+       table = key_bindings_get_table(name, 0);
+       if (table != NULL) {
+               RB_REMOVE(key_tables, &key_tables, table);
+               key_bindings_unref_table(table);
+       }
 }
 
 void
@@ -169,8 +229,6 @@ key_bindings_init(void)
        int              error;
        struct cmd_q    *cmdq;
 
-       RB_INIT(&key_bindings);
-
        cmdq = cmdq_new(NULL);
        for (i = 0; i < nitems(defaults); i++) {
                error = cmd_string_parse(defaults[i], &cmdlist,
index 4c9d40a..45a9379 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: server-client.c,v 1.131 2015/04/19 21:34:21 nicm Exp $ */
+/* $OpenBSD: server-client.c,v 1.132 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -30,6 +30,7 @@
 
 #include "tmux.h"
 
+void   server_client_key_table(struct client *, const char *);
 void   server_client_check_focus(struct window_pane *);
 void   server_client_check_resize(struct window_pane *);
 int    server_client_check_mouse(struct client *);
@@ -45,6 +46,15 @@ void server_client_msg_command(struct client *, struct imsg *);
 void   server_client_msg_identify(struct client *, struct imsg *);
 void   server_client_msg_shell(struct client *);
 
+/* Set client key table. */
+void
+server_client_key_table(struct client *c, const char *name)
+{
+       key_bindings_unref_table(c->keytable);
+       c->keytable = key_bindings_get_table(name, 1);
+       c->keytable->references++;
+}
+
 /* Create a new client. */
 void
 server_client_create(int fd)
@@ -93,6 +103,9 @@ server_client_create(int fd)
 
        c->flags |= CLIENT_FOCUSED;
 
+       c->keytable = key_bindings_get_table("root", 1);
+       c->keytable->references++;
+
        evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
 
        for (i = 0; i < ARRAY_LENGTH(&clients); i++) {
@@ -164,6 +177,8 @@ server_client_lost(struct client *c)
 
        evtimer_del(&c->repeat_timer);
 
+       key_bindings_unref_table(c->keytable);
+
        if (event_initialized(&c->identify_timer))
                evtimer_del(&c->identify_timer);
 
@@ -527,16 +542,19 @@ void
 server_client_handle_key(struct client *c, int key)
 {
        struct mouse_event      *m = &c->tty.mouse;
-       struct session          *s;
+       struct session          *s = c->session;
        struct window           *w;
        struct window_pane      *wp;
        struct timeval           tv;
-       struct key_binding      *bd;
-       int                      xtimeout, isprefix, ispaste;
+       struct key_table        *table = c->keytable;
+       struct key_binding       bd_find, *bd;
+       int                      xtimeout;
 
        /* Check the client is good to accept input. */
-       if ((c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
+       if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
                return;
+       w = s->curw->window;
+       wp = w->active;
 
        /* No session, do nothing. */
        if (c->session == NULL)
@@ -552,7 +570,7 @@ server_client_handle_key(struct client *c, int key)
            sizeof s->last_activity_time);
        memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time);
 
-       /* Special case: number keys jump to pane in identify mode. */
+       /* Number keys jump to pane in identify mode. */
        if (c->flags & CLIENT_IDENTIFY && key >= '0' && key <= '9') {
                if (c->flags & CLIENT_READONLY)
                        return;
@@ -593,74 +611,88 @@ server_client_handle_key(struct client *c, int key)
        } else
                m->valid = 0;
 
-       /* Is this a prefix key? */
-       if (key == options_get_number(&s->options, "prefix"))
-               isprefix = 1;
-       else if (key == options_get_number(&s->options, "prefix2"))
-               isprefix = 1;
-       else
-               isprefix = 0;
-
-       /* Treat prefix as a regular key when pasting is detected. */
-       ispaste = server_client_assume_paste(s);
-       if (ispaste)
-               isprefix = 0;
+       /* Treat everything as a regular key when pasting is detected. */
+       if (server_client_assume_paste(s)) {
+               if (!(c->flags & CLIENT_READONLY))
+                       window_pane_key(wp, c, s, key, m);
+               return;
+       }
 
-       /* No previous prefix key. */
-       if (!(c->flags & CLIENT_PREFIX)) {
-               if (isprefix) {
-                       c->flags |= CLIENT_PREFIX;
+retry:
+       /* Try to see if there is a key binding in the current table. */
+       bd_find.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
+       if (bd != NULL) {
+               /*
+                * Key was matched in this table. If currently repeating but a
+                * non-repeating binding was found, stop repeating and try
+                * again in the root table.
+                */
+               if ((c->flags & CLIENT_REPEAT) && !bd->can_repeat) {
+                       server_client_key_table(c, "root");
+                       c->flags &= ~CLIENT_REPEAT;
                        server_status_client(c);
-                       return;
+                       goto retry;
                }
 
-               /* Try as a non-prefix key binding. */
-               if (ispaste || (bd = key_bindings_lookup(key)) == NULL) {
-                       if (!(c->flags & CLIENT_READONLY))
-                               window_pane_key(wp, c, s, key, m);
-               } else
-                       key_bindings_dispatch(bd, c, m);
-               return;
-       }
-
-       /* Prefix key already pressed. Reset prefix and lookup key. */
-       c->flags &= ~CLIENT_PREFIX;
-       server_status_client(c);
-       if ((bd = key_bindings_lookup(key | KEYC_PREFIX)) == NULL) {
-               /* If repeating, treat this as a key, else ignore. */
-               if (c->flags & CLIENT_REPEAT) {
+               /*
+                * Take a reference to this table to make sure the key binding
+                * doesn't disappear.
+                */
+               table->references++;
+
+               /*
+                * If this is a repeating key, start the timer. Otherwise reset
+                * the client back to the root table.
+                */
+               xtimeout = options_get_number(&s->options, "repeat-time");
+               if (xtimeout != 0 && bd->can_repeat) {
+                       c->flags |= CLIENT_REPEAT;
+
+                       tv.tv_sec = xtimeout / 1000;
+                       tv.tv_usec = (xtimeout % 1000) * 1000L;
+                       evtimer_del(&c->repeat_timer);
+                       evtimer_add(&c->repeat_timer, &tv);
+               } else {
                        c->flags &= ~CLIENT_REPEAT;
-                       if (isprefix)
-                               c->flags |= CLIENT_PREFIX;
-                       else if (!(c->flags & CLIENT_READONLY))
-                               window_pane_key(wp, c, s, key, m);
+                       server_client_key_table(c, "root");
                }
+               server_status_client(c);
+
+               /* Dispatch the key binding. */
+               key_bindings_dispatch(bd, c, m);
+               key_bindings_unref_table(table);
                return;
        }
 
-       /* If already repeating, but this key can't repeat, skip it. */
-       if (c->flags & CLIENT_REPEAT && !bd->can_repeat) {
+       /*
+        * No match in this table. If repeating, switch the client back to the
+        * root table and try again.
+        */
+       if (c->flags & CLIENT_REPEAT) {
+               server_client_key_table(c, "root");
                c->flags &= ~CLIENT_REPEAT;
-               if (isprefix)
-                       c->flags |= CLIENT_PREFIX;
-               else if (!(c->flags & CLIENT_READONLY))
-                       window_pane_key(wp, c, s, key, m);
-               return;
+               server_status_client(c);
+               goto retry;
        }
 
-       /* If this key can repeat, reset the repeat flags and timer. */
-       xtimeout = options_get_number(&s->options, "repeat-time");
-       if (xtimeout != 0 && bd->can_repeat) {
-               c->flags |= CLIENT_PREFIX|CLIENT_REPEAT;
-
-               tv.tv_sec = xtimeout / 1000;
-               tv.tv_usec = (xtimeout % 1000) * 1000L;
-               evtimer_del(&c->repeat_timer);
-               evtimer_add(&c->repeat_timer, &tv);
+       /* If no match and we're not in the root table, that's it. */
+       if (strcmp(c->keytable->name, "root") != 0) {
+               server_client_key_table(c, "root");
+               server_status_client(c);
+               return;
        }
 
-       /* Dispatch the command. */
-       key_bindings_dispatch(bd, c, m);
+       /*
+        * No match, but in the root table. Prefix switches to the prefix table
+        * and everything else is passed through.
+        */
+       if (key == options_get_number(&s->options, "prefix") ||
+           key == options_get_number(&s->options, "prefix2")) {
+               server_client_key_table(c, "prefix");
+               server_status_client(c);
+       } else if (!(c->flags & CLIENT_READONLY))
+               window_pane_key(wp, c, s, key, m);
 }
 
 /* Client functions that need to happen every loop. */
@@ -848,9 +880,9 @@ server_client_repeat_timer(unused int fd, unused short events, void *data)
        struct client   *c = data;
 
        if (c->flags & CLIENT_REPEAT) {
-               if (c->flags & CLIENT_PREFIX)
-                       server_status_client(c);
-               c->flags &= ~(CLIENT_PREFIX|CLIENT_REPEAT);
+               server_client_key_table(c, "root");
+               c->flags &= ~CLIENT_REPEAT;
+               server_status_client(c);
        }
 }
 
index b6fe3b8..108479e 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: tmux.1,v 1.420 2015/04/19 22:10:30 jmc Exp $
+.\" $OpenBSD: tmux.1,v 1.421 2015/04/20 15:34:56 nicm Exp $
 .\"
 .\" Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
 .\"
@@ -14,7 +14,7 @@
 .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: April 19 2015 $
+.Dd $Mdocdate: April 20 2015 $
 .Dt TMUX 1
 .Os
 .Sh NAME
@@ -838,6 +838,7 @@ Suspend a client by sending
 .Op Fl lnpr
 .Op Fl c Ar target-client
 .Op Fl t Ar target-session
+.Op Fl T Ar key-table
 .Xc
 .D1 (alias: Ic switchc )
 Switch the current session for client
@@ -855,6 +856,22 @@ respectively.
 toggles whether a client is read-only (see the
 .Ic attach-session
 command).
+.Pp
+.Fl T
+sets the client's key table; the next key from the client will be interpreted from
+.Ar key-table .
+This may be used to configure multiple prefix keys, or to bind commands to
+sequences of keys.
+For example, to make typing
+.Ql abc
+run the
+.Ic list-keys
+command:
+.Bd -literal -offset indent
+bind-key -Ttable2 c list-keys
+bind-key -Ttable1 b switch-client -Ttable2
+bind-key -Troot   a switch-client -Ttable1
+.Ed
 .El
 .Sh WINDOWS AND PANES
 A
@@ -1931,6 +1948,7 @@ Commands related to key bindings are as follows:
 .It Xo Ic bind-key
 .Op Fl cnr
 .Op Fl t Ar mode-table
+.Op Fl T Ar key-table
 .Ar key Ar command Op Ar arguments
 .Xc
 .D1 (alias: Ic bind )
@@ -1938,16 +1956,40 @@ Bind key
 .Ar key
 to
 .Ar command .
-By default (without
-.Fl t )
-the primary key bindings are modified (those normally activated with the prefix
-key); in this case, if
-.Fl n
-is specified, it is not necessary to use the prefix key,
-.Ar command
+Keys are bound in a key table.
+By default (without -T), the key is bound in
+the
+.Em prefix
+key table.
+This table is used for keys pressed after the prefix key (for example,
+by default
+.Ql c
 is bound to
-.Ar key
-alone.
+.Ic new-window
+in the
+.Em prefix
+table, so
+.Ql C-b c
+creates a new window).
+The
+.Em root
+table is used for keys pressed without the prefix key: binding
+.Ql c
+to
+.Ic new-window
+in the
+.Em root
+table (not recommended) means a plain
+.Ql c
+will create a new window.
+.Fl n
+is an alias
+for
+.Fl T Ar root .
+Keys may also be bound in custom key tables and the
+.Ic switch-client
+.Fl T
+command used to switch to them from a key binding.
 The
 .Fl r
 flag indicates this key may repeat, see the
@@ -1962,22 +2004,33 @@ is bound in
 .Ar mode-table :
 the binding for command mode with
 .Fl c
-or for normal mode without.
+or for normal mode without. See the
+.Sx WINDOWS AND PANES
+section and the
+.Ic list-keys
+command for information on mode key bindings.
+.Pp
 To view the default bindings and possible commands, see the
 .Ic list-keys
 command.
-.It Ic list-keys Op Fl t Ar key-table
+.It Xo Ic list-keys
+.Op Fl t Ar mode-table
+.Op 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.
+.Fl T
+all key tables are printed.
+With
+.Fl T
+only
+.Ar key-table .
 .Pp
 With
 .Fl t ,
 the key bindings in
-.Ar key-table
+.Ar mode-table
 are listed; this may be one of:
 .Em vi-edit ,
 .Em emacs-edit ,
@@ -2022,31 +2075,22 @@ the secondary prefix key, to a window as if it was pressed.
 .It Xo Ic unbind-key
 .Op Fl acn
 .Op Fl t Ar mode-table
+.Op Fl T Ar key-table
 .Ar key
 .Xc
 .D1 (alias: Ic unbind )
 Unbind the command bound to
 .Ar key .
-Without
+.Fl c ,
+.Fl n ,
+.Fl T
+and
 .Fl t
-the primary key bindings are modified; in this case, if
-.Fl n
-is specified, the command bound to
-.Ar key
-without a prefix (if any) is removed.
+are the same as for
+.Ic bind-key .
 If
 .Fl a
 is present, all key bindings are removed.
-.Pp
-If
-.Fl t
-is present,
-.Ar key
-in
-.Ar mode-table
-is unbound: the binding for command mode with
-.Fl c
-or for normal mode without.
 .El
 .Sh OPTIONS
 The appearance and behaviour of
index 34c602f..87184a6 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tmux.h,v 1.492 2015/04/19 21:34:21 nicm Exp $ */
+/* $OpenBSD: tmux.h,v 1.493 2015/04/20 15:34:56 nicm Exp $ */
 
 /*
  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
@@ -89,10 +89,9 @@ extern char   **environ;
 #define KEYC_ESCAPE 0x2000
 #define KEYC_CTRL 0x4000
 #define KEYC_SHIFT 0x8000
-#define KEYC_PREFIX 0x10000
 
 /* Mask to obtain key w/o modifiers. */
-#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT|KEYC_PREFIX)
+#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT)
 #define KEYC_MASK_KEY (~KEYC_MASK_MOD)
 
 /* Is this a mouse key? */
@@ -1301,7 +1300,7 @@ struct client {
        struct screen    status;
 
 #define CLIENT_TERMINAL 0x1
-#define CLIENT_PREFIX 0x2
+/* 0x2 unused */
 #define CLIENT_EXIT 0x4
 #define CLIENT_REDRAW 0x8
 #define CLIENT_STATUS 0x10
@@ -1320,6 +1319,7 @@ struct client {
 #define CLIENT_256COLOURS 0x20000
 #define CLIENT_IDENTIFIED 0x40000
        int              flags;
+       struct key_table *keytable;
 
        struct event     identify_timer;
 
@@ -1440,15 +1440,24 @@ struct cmd_entry {
        enum cmd_retval  (*exec)(struct cmd *, struct cmd_q *);
 };
 
-/* Key binding. */
+/* Key binding and key table. */
 struct key_binding {
-       int              key;
-       struct cmd_list *cmdlist;
-       int              can_repeat;
+       int                      key;
+       struct cmd_list         *cmdlist;
+       int                      can_repeat;
 
-       RB_ENTRY(key_binding) entry;
+       RB_ENTRY(key_binding)    entry;
 };
 RB_HEAD(key_bindings, key_binding);
+struct key_table {
+       const char               *name;
+       struct key_bindings      key_bindings;
+
+       u_int                    references;
+
+       RB_ENTRY(key_table)      entry;
+};
+RB_HEAD(key_tables, key_table);
 
 /*
  * Option table entries. The option table is the user-visible part of the
@@ -1876,12 +1885,16 @@ void    cmd_wait_for_flush(void);
 int    client_main(int, char **, int);
 
 /* key-bindings.c */
-extern struct key_bindings key_bindings;
-int     key_bindings_cmp(struct key_binding *, struct key_binding *);
 RB_PROTOTYPE(key_bindings, key_binding, entry, key_bindings_cmp);
-struct key_binding *key_bindings_lookup(int);
-void    key_bindings_add(int, int, struct cmd_list *);
-void    key_bindings_remove(int);
+RB_PROTOTYPE(key_tables, key_table, entry, key_table_cmp);
+extern struct key_tables key_tables;
+int     key_table_cmp(struct key_table *, struct key_table *);
+int     key_bindings_cmp(struct key_binding *, struct key_binding *);
+struct          key_table *key_bindings_get_table(const char *, int);
+void    key_bindings_unref_table(struct key_table *);
+void    key_bindings_add(const char *, int, int, struct cmd_list *);
+void    key_bindings_remove(const char *, int);
+void    key_bindings_remove_table(const char *);
 void    key_bindings_init(void);
 void    key_bindings_dispatch(struct key_binding *, struct client *,
             struct mouse_event *);