Hi

Sorry for the delay.

Here is an updated version of the diff with some changes, mostly style
changes but a few bigger:

- Added -T to lsk. It should possibly grow -n as well.

- I don't see a need for a for (;;) loop and break/continue in
  server_client_handle_key - it looks like we can only loop with
  CLIENT_REPEAT set and every branch clears it, so a simple goto is
  simpler.

- Also in server_client_handle_key I simplified it by reordering the
  branches. It'd be great if you can look and see if the logic still
  makes sense. I think there is also no need for isprefix and ispaste
  anymore.

I like this feature a lot, but I'm still not wild about the implicit
bindings for the prefix and prefix2, it just seems confusing that there
is nothing in the root table by default. But maybe it is ok, I need to
think about it some more.

Also I think the manpage could do with some more explanation of this.

Index: cmd-bind-key.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/cmd-bind-key.c,v
retrieving revision 1.18
diff -u -p -r1.18 cmd-bind-key.c
--- cmd-bind-key.c      14 May 2014 06:45:35 -0000      1.18
+++ cmd-bind-key.c      27 May 2014 14:27:50 -0000
@@ -33,8 +33,8 @@ enum cmd_retval        cmd_bind_key_mode_table
 
 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,
        NULL,
        cmd_bind_key_exec
@@ -47,6 +47,7 @@ cmd_bind_key_exec(struct cmd *self, stru
        char            *cause;
        struct cmd_list *cmdlist;
        int              key;
+       const char      *tablename;
 
        if (args_has(args, 't')) {
                if (args->argc != 2 && args->argc != 3) {
@@ -69,6 +70,13 @@ cmd_bind_key_exec(struct cmd *self, stru
        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) {
@@ -77,9 +85,7 @@ cmd_bind_key_exec(struct cmd *self, stru
                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: cmd-list-keys.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/cmd-list-keys.c,v
retrieving revision 1.22
diff -u -p -r1.22 cmd-list-keys.c
--- cmd-list-keys.c     10 Oct 2013 12:00:20 -0000      1.22
+++ cmd-list-keys.c     27 May 2014 14:27:50 -0000
@@ -31,8 +31,8 @@ enum cmd_retval        cmd_list_keys_table(str
 
 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,
        NULL,
        cmd_list_keys_exec
@@ -42,55 +42,62 @@ 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 (args_has(args, 't'))
                return (cmd_list_keys_table(self, cmdq));
+       tablename = args_get(args, 'T');
 
-       width = 0;
-
-       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;
+                               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);
+                       }
 
-               cmd_list_print(bd->cmdlist, tmp + used, (sizeof tmp) - used);
-               cmdq_print(cmdq, "bind-key %s", tmp);
+                       cmdq_print(cmdq, "bind-key %s", tmp);
+               }
        }
 
        return (CMD_RETURN_NORMAL);
Index: cmd-switch-client.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/cmd-switch-client.c,v
retrieving revision 1.21
diff -u -p -r1.21 cmd-switch-client.c
--- cmd-switch-client.c 17 Apr 2014 07:55:43 -0000      1.21
+++ cmd-switch-client.c 27 May 2014 14:27:50 -0000
@@ -32,8 +32,8 @@ enum cmd_retval        cmd_switch_client_exec(
 
 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_key_binding,
        cmd_switch_client_exec
@@ -65,7 +65,8 @@ cmd_switch_client_exec(struct cmd *self,
        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);
@@ -75,6 +76,18 @@ cmd_switch_client_exec(struct cmd *self,
                        c->flags &= ~CLIENT_READONLY;
                else
                        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');
Index: cmd-unbind-key.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/cmd-unbind-key.c,v
retrieving revision 1.19
diff -u -p -r1.19 cmd-unbind-key.c
--- cmd-unbind-key.c    14 May 2014 06:45:35 -0000      1.19
+++ cmd-unbind-key.c    27 May 2014 14:27:50 -0000
@@ -31,8 +31,8 @@ enum cmd_retval        cmd_unbind_key_mode_tab
 
 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,
        NULL,
        cmd_unbind_key_exec
@@ -41,9 +41,9 @@ const struct cmd_entry cmd_unbind_key_en
 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) {
@@ -67,16 +67,23 @@ cmd_unbind_key_exec(struct cmd *self, st
                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);
+               if (args_has(args, 'T')) {
+                       key_bindings_remove_table(args_get(args, 'T'));
+                       return (CMD_RETURN_NORMAL);
                }
+               key_bindings_remove_table("root");
+               key_bindings_remove_table("prefix");
                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');
+       else if (args_has(args, 'n'))
+               tablename = "root";
+       else
+               tablename = "prefix";
+
+       key_bindings_remove(tablename, key);
        return (CMD_RETURN_NORMAL);
 }
 
Index: format.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/format.c,v
retrieving revision 1.47
diff -u -p -r1.47 format.c
--- format.c    27 May 2014 12:49:36 -0000      1.47
+++ format.c    27 May 2014 14:27:51 -0000
@@ -435,7 +435,11 @@ format_client(struct format_tree *ft, st
        *strchr(tim, '\n') = '\0';
        format_add(ft, "client_activity_string", "%s", tim);
 
-       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: key-bindings.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/key-bindings.c,v
retrieving revision 1.41
diff -u -p -r1.41 key-bindings.c
--- key-bindings.c      14 May 2014 06:21:19 -0000      1.41
+++ key-bindings.c      27 May 2014 14:27:51 -0000
@@ -25,60 +25,121 @@
 #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;
+       return (bd1->key - bd2->key);
+}
+
+struct key_table *
+key_bindings_get_table(const char *name, int create)
+{
+       struct key_table        table_search, *table;
+
+       table_search.name = name;
+       table = RB_FIND(key_tables, &key_tables, &table_search);
+       if (table != NULL || !create)
+               return (table);
 
-       key1 = bd1->key & ~KEYC_PREFIX;
-       key2 = bd2->key & ~KEYC_PREFIX;
-       if (key1 != key2)
-               return (key1 - key2);
+       table = xmalloc(sizeof *table);
+       table->name = xstrdup(name);
+       RB_INIT(&table->key_bindings);
 
-       if (bd1->key & KEYC_PREFIX && !(bd2->key & KEYC_PREFIX))
-               return (-1);
-       if (bd2->key & KEYC_PREFIX && !(bd1->key & KEYC_PREFIX))
-               return (1);
-       return (0);
+       table->references = 1; /* one reference in key_tables */
+       RB_INSERT(key_tables, &key_tables, table);
+
+       return (table);
 }
 
-struct key_binding *
-key_bindings_lookup(int key)
+void
+key_bindings_unref_table(struct key_table *table)
 {
-       struct key_binding      bd;
+       struct key_binding      *bd;
 
-       bd.key = key;
-       return (RB_FIND(key_bindings, &key_bindings, &bd));
+       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(int key, int can_repeat, struct cmd_list *cmdlist)
+key_bindings_add(const char *name, int key, int can_repeat,
+    struct cmd_list *cmdlist)
 {
-       struct key_binding      *bd;
+       struct key_table        *table;
+       struct key_binding       bd_search, *bd;
 
-       key_bindings_remove(key);
+       table = key_bindings_get_table(name, 1);
+
+       bd_search.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_search);
+       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_search, *bd;
+
+       table = key_bindings_get_table(name, 0);
+       if (table == NULL)
+               return;
 
-       if ((bd = key_bindings_lookup(key)) == NULL)
+       bd_search.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_search);
+       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)
+               return;
+
+       RB_REMOVE(key_tables, &key_tables, table);
+       key_bindings_unref_table(table);
 }
 
 void
@@ -167,8 +228,6 @@ key_bindings_init(void)
        struct cmd      *cmd;
        struct cmd_list *cmdlist;
 
-       RB_INIT(&key_bindings);
-
        for (i = 0; i < nitems(table); i++) {
                cmdlist = xcalloc(1, sizeof *cmdlist);
                cmdlist->references = 1;
@@ -182,8 +241,8 @@ key_bindings_init(void)
                        cmd->args = args_create(0);
                TAILQ_INSERT_HEAD(&cmdlist->list, cmd, qentry);
 
-               key_bindings_add(
-                   table[i].key | KEYC_PREFIX, table[i].can_repeat, cmdlist);
+               key_bindings_add("prefix", table[i].key, table[i].can_repeat,
+                   cmdlist);
        }
 }
 
Index: server-client.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/server-client.c,v
retrieving revision 1.120
diff -u -p -r1.120 server-client.c
--- server-client.c     16 Apr 2014 08:02:31 -0000      1.120
+++ server-client.c     27 May 2014 14:27:51 -0000
@@ -100,6 +100,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++) {
@@ -171,6 +174,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);
 
@@ -361,33 +366,28 @@ server_client_assume_paste(struct sessio
 void
 server_client_handle_key(struct client *c, int key)
 {
-       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_search, *bd;
+       int                      xtimeout;
 
        /* Check the client is good to accept input. */
-       if ((c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
-               return;
-
-       if (c->session == NULL)
+       if (s == NULL || (c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
                return;
-       s = c->session;
+       w = c->session->curw->window;
+       wp = w->active;
 
        /* Update the activity timer. */
        if (gettimeofday(&c->activity_time, NULL) != 0)
                fatal("gettimeofday failed");
-
        memcpy(&s->last_activity_time, &s->activity_time,
            sizeof s->last_activity_time);
        memcpy(&s->activity_time, &c->activity_time, sizeof s->activity_time);
 
-       w = c->session->curw->window;
-       wp = w->active;
-
-       /* 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;
@@ -418,74 +418,89 @@ server_client_handle_key(struct client *
                return;
        }
 
-       /* 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 everything as a regular key when pasting is detected. */
+       if (server_client_assume_paste(s)) {
+               if (!(c->flags & CLIENT_READONLY))
+                       window_pane_key(wp, s, key);
+               return;
+       }
 
-       /* Treat prefix as a regular key when pasting is detected. */
-       ispaste = server_client_assume_paste(s);
-       if (ispaste)
-               isprefix = 0;
-
-       /* 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_search.key = key;
+       bd = RB_FIND(key_bindings, &table->key_bindings, &bd_search);
+       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_set_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, s, key);
-               } else
-                       key_bindings_dispatch(bd, c);
-               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, s, key);
+                       server_set_key_table(c, "root");
                }
+               server_status_client(c);
+
+               /* Dispatch the key binding. */
+               key_bindings_dispatch(bd, c);
+               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_set_key_table(c, "root");
                c->flags &= ~CLIENT_REPEAT;
-               if (isprefix)
-                       c->flags |= CLIENT_PREFIX;
-               else if (!(c->flags & CLIENT_READONLY))
-                       window_pane_key(wp, s, key);
-               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_set_key_table(c, "root");
+               server_status_client(c);
+               return;
        }
 
-       /* Dispatch the command. */
-       key_bindings_dispatch(bd, c);
+       /*
+        * 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_set_key_table(c, "prefix");
+               server_status_client(c);
+       } else if (!(c->flags & CLIENT_READONLY))
+               window_pane_key(wp, s, key);
 }
 
 /* Client functions that need to happen every loop. */
@@ -691,9 +706,9 @@ server_client_repeat_timer(unused int fd
        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_set_key_table(c, "root");
+               c->flags &= ~CLIENT_REPEAT;
+               server_status_client(c);
        }
 }
 
Index: server-fn.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/server-fn.c,v
retrieving revision 1.76
diff -u -p -r1.76 server-fn.c
--- server-fn.c 17 Apr 2014 14:45:49 -0000      1.76
+++ server-fn.c 27 May 2014 14:27:51 -0000
@@ -101,6 +101,14 @@ server_status_client(struct client *c)
 }
 
 void
+server_set_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++;
+}
+
+void
 server_redraw_session(struct session *s)
 {
        struct client   *c;
Index: tmux.1
===================================================================
RCS file: /cvs/src/usr.bin/tmux/tmux.1,v
retrieving revision 1.396
diff -u -p -r1.396 tmux.1
--- tmux.1      27 May 2014 12:49:36 -0000      1.396
+++ tmux.1      27 May 2014 14:27:52 -0000
@@ -829,6 +829,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
@@ -846,6 +847,9 @@ respectively.
 toggles whether a client is read-only (see the
 .Ic attach-session
 command).
+.Fl T
+sets the client's key table; the next key from the client will be interpretted 
from
+.Ar key-table .
 .El
 .Sh WINDOWS AND PANES
 A
@@ -1862,6 +1866,7 @@ Commands related to key bindings are as 
 .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 )
@@ -1897,18 +1902,41 @@ or for normal mode without.
 To view the default bindings and possible commands, see the
 .Ic list-keys
 command.
-.It Ic list-keys Op Fl t Ar key-table
+.Pp
+If
+.Fl T
+is present,
+.Ar key
+is bound in
+.Ar key-table :
+.Em prefix
+corresponds to the default,
+.Em root
+corresponds to
+.Fl n ,
+and custom values may be used with
+.Ic switch-client
+.Fl T .
+.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.
+or
+.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 ,
@@ -1949,6 +1977,7 @@ the secondary prefix key, to a window as
 .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 )
@@ -1974,6 +2003,22 @@ in
 is unbound: the binding for command mode with
 .Fl c
 or for normal mode without.
+.Pp
+If
+.Fl T
+is present,
+.Ar key
+in
+.Ar key-table
+is unbound:
+.Em prefix
+corresponds to the default,
+.Em root
+corresponds to
+.Fl n ,
+and custom values may be used with
+.Ic switch-client
+.Fl T .
 .El
 .Sh OPTIONS
 The appearance and behaviour of
Index: tmux.h
===================================================================
RCS file: /cvs/src/usr.bin/tmux/tmux.h,v
retrieving revision 1.464
diff -u -p -r1.464 tmux.h
--- tmux.h      14 May 2014 06:21:19 -0000      1.464
+++ tmux.h      27 May 2014 14:27:53 -0000
@@ -168,10 +168,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)
 
 /* Other key codes. */
@@ -1314,7 +1313,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
@@ -1333,6 +1332,7 @@ struct client {
 #define CLIENT_256COLOURS 0x20000
 #define CLIENT_IDENTIFIED 0x40000
        int              flags;
+       struct key_table *keytable;
 
        struct event     identify_timer;
 
@@ -1451,15 +1451,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
@@ -1883,12 +1892,16 @@ int     cmd_string_parse(const char *, struc
 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 *);
 
@@ -1924,6 +1937,7 @@ void       server_write_session(struct sessio
             size_t);
 void    server_redraw_client(struct client *);
 void    server_status_client(struct client *);
+void    server_set_key_table(struct client *, const char *);
 void    server_redraw_session(struct session *);
 void    server_redraw_session_group(struct session *);
 void    server_status_session(struct session *);




On Thu, May 15, 2014 at 11:17:00AM -0700, Keith Amling wrote:
> > > + format_add(ft, "client_keytablename", "%s", c->keytable->name);
> > 
> > Let's call it client_key_table to fit the style of most of the others.
> 
> Done.
> 
> > > +struct key_table *
> > > +key_bindings_lookup_table(const char *name, int create)
> > 
> > I don't think it is intuitive for a lookup function to take a
> > reference. I would do without this function and just do lookup followed
> > by table->references++ in the caller.
> > 
> > Personally I would also split this into two functions, one for lookup
> > and one for create and do this in the caller if create is needed:
> > 
> >     t = key_bindings_lookup_table("foo");
> >     if (t == NULL)
> >             t = key_bindings_create_table("foo");
> > 
> > But if you prefer a create flag that is probably okay, although in that
> > case I'd rather not call the function _lookup_table. Maybe _get_table
> > instead?
> 
> There are so many places that use create = 1 which really make me want
> to not inline the check-and-create if.  Inlined the with-reference
> version and renamed the without-reference version to _get_table.
> 
> > > +key_bindings_unreference_table(struct key_table *table)
> > 
> > I'd do s/unreference/unref/ here to make this a bit less of a mouthful.
> 
> Done.
> 
> > > +         /* This represents key_tables's reference */
> > 
> > I don't think we need these comments. If you feel it needs to be
> > documented that the tree holds a reference, put a comment once either
> > with the references member in the struct in tmux.h or somewhere around
> > where the tree is declared.
> 
> It's probably clearer now that there aren't two unrefs in a row.
> Deleted (x2).
> 
> > >  void
> > > +server_keytable_client(struct client *c, const char *name)
> > > +{
> > > + key_bindings_unreference_table(c->keytable);
> > > + c->keytable = key_bindings_lookup_table(name, 1);
> > 
> > I'm not convinced we need this helper function for two lines.
> 
> It's 3 lines now that it has to do its own references++ and it's called
> in 6 places, 5 of which are in the already very long and complicated
> server_client_handle_key.  Are you sure this is what you want?
> 
> > > +/* replaced by keytablename: #define CLIENT_PREFIX 0x2 */
> > 
> > Just remove this and either renumber the rest or put a comment /* 0x2
> > unused */.
> 
> Comment changed.
> 
> > > + /*
> > > +  * Search needs to be able to use const char * and free needs to be
> > > +  * able to free the name we allocated when we put it in the tree for
> > > +  * real.  Search and keying is done by name, free is done with
> > > +  * name_for_free.
> > > +  */
> > > + const char               *name;
> > > + char                     *name_for_free;
> > 
> > Don't do this, it's confusing. Just use a const char * and cast it to
> > void * when you pass it to free.
> 
> Done.
> 
> Keith
> 
> ---
>  cmd-bind-key.c      |  16 +++++--
>  cmd-list-keys.c     |  80 ++++++++++++++++----------------
>  cmd-switch-client.c |  16 ++++++-
>  cmd-unbind-key.c    |  27 +++++++----
>  format.c            |   3 +-
>  key-bindings.c      | 119 +++++++++++++++++++++++++++++++++++++-----------
>  server-client.c     | 128 
> +++++++++++++++++++++++++++++++---------------------
>  server-fn.c         |   8 ++++
>  tmux.1              |  37 +++++++++++++++
>  tmux.h              |  30 ++++++++----
>  10 files changed, 320 insertions(+), 144 deletions(-)
> 
> diff --git a/cmd-bind-key.c b/cmd-bind-key.c
> index 27a03ce11ad7..1d815850db6c 100644
> --- a/cmd-bind-key.c
> +++ b/cmd-bind-key.c
> @@ -33,8 +33,8 @@ enum cmd_retval      cmd_bind_mode_key_table(struct cmd *, 
> struct cmd_q *, int);
>  
>  const struct cmd_entry cmd_bind_key_entry = {
>       "bind-key", "bind",
> -     "cnrt:", 1, -1,
> -     "[-cnr] [-t mode-key-table] key command [arguments]",
> +     "cnrt:T:", 1, -1,
> +     "[-cnr] [-t mode-key-table] [-T key-table] key command [arguments]",
>       0,
>       NULL,
>       cmd_bind_key_exec
> @@ -47,6 +47,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
>       char            *cause;
>       struct cmd_list *cmdlist;
>       int              key;
> +     const char      *table;
>  
>       if (args_has(args, 't')) {
>               if (args->argc != 2 && args->argc != 3) {
> @@ -69,6 +70,13 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
>       if (args_has(args, 't'))
>               return (cmd_bind_mode_key_table(self, cmdq, key));
>  
> +     if (args_has(args, 'T'))
> +             table = args_get(args, 'T');
> +     else if (args_has(args, 'n'))
> +             table = "root";
> +     else
> +             table = "prefix";
> +
>       cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0,
>           &cause);
>       if (cmdlist == NULL) {
> @@ -77,9 +85,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(table, key, args_has(args, 'r'), cmdlist);
>       return (CMD_RETURN_NORMAL);
>  }
>  
> diff --git a/cmd-list-keys.c b/cmd-list-keys.c
> index 615c5ce1fe67..7e4472283e33 100644
> --- a/cmd-list-keys.c
> +++ b/cmd-list-keys.c
> @@ -41,56 +41,56 @@ const struct cmd_entry cmd_list_keys_entry = {
>  enum cmd_retval
>  cmd_list_keys_exec(struct cmd *self, struct cmd_q *cmdq)
>  {
> -     struct args             *args = self->args;
> -     struct key_binding      *bd;
> -     const char              *key;
> -     char                     tmp[BUFSIZ], flags[8];
> -     size_t                   used;
> -     int                      width, keywidth;
> +     struct args                     *args = self->args;
> +     struct key_table                *table;
> +     struct key_binding              *bd;
> +     const char                      *key;
> +     char                             tmp[BUFSIZ];
> +     size_t                           used;
> +     int                              hasrepeat, maxtablewidth, tablewidth, 
> maxkeywidth, keywidth;
>  
>       if (args_has(args, 't'))
>               return (cmd_list_keys_table(self, cmdq));
>  
> -     width = 0;
> +     hasrepeat = 0;
> +     maxtablewidth = 0;
> +     maxkeywidth = 0;
>  
> -     RB_FOREACH(bd, key_bindings, &key_bindings) {
> -             key = key_string_lookup_key(bd->key & ~KEYC_PREFIX);
> -             if (key == NULL)
> -                     continue;
> +     RB_FOREACH(table, key_tables, &key_tables) {
> +             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;
> -     }
> +                             hasrepeat = 1;
>  
> -     RB_FOREACH(bd, key_bindings, &key_bindings) {
> -             key = key_string_lookup_key(bd->key & ~KEYC_PREFIX);
> -             if (key == NULL)
> -                     continue;
> +                     tablewidth = strlen(table->name);
> +                     if (tablewidth > maxtablewidth)
> +                             maxtablewidth = tablewidth;
>  
> -             *flags = '\0';
> -             if (!(bd->key & KEYC_PREFIX)) {
> -                     if (bd->can_repeat)
> -                             xsnprintf(flags, sizeof flags, "-rn ");
> -                     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;
> +                     keywidth = strlen(key);
> +                     if (keywidth > maxkeywidth)
> +                             maxkeywidth = keywidth;
> +             }
> +     }
>  
> -             cmd_list_print(bd->cmdlist, tmp + used, (sizeof tmp) - used);
> -             cmdq_print(cmdq, "bind-key %s", tmp);
> +     RB_FOREACH(table, key_tables, &key_tables) {
> +             RB_FOREACH(bd, key_bindings, &(table->key_bindings)) {
> +                     key = key_string_lookup_key(bd->key);
> +                     if (key == NULL)
> +                             continue;
> +
> +                     used = xsnprintf(tmp, sizeof tmp, "%s-T %-*s %-*s ",
> +                             (hasrepeat ? (bd->can_repeat ? "-r " : "   ") : 
> ""),
> +                             (int) maxtablewidth, table->name,
> +                             (int) maxkeywidth, key);
> +                     if (used >= sizeof tmp)
> +                             continue;
> +
> +                     cmd_list_print(bd->cmdlist, tmp + used, (sizeof tmp) - 
> used);
> +                     cmdq_print(cmdq, "bind-key %s", tmp);
> +             }
>       }
>  
>       return (CMD_RETURN_NORMAL);
> diff --git a/cmd-switch-client.c b/cmd-switch-client.c
> index 9e7967cd4e3d..05dfc7b36844 100644
> --- a/cmd-switch-client.c
> +++ b/cmd-switch-client.c
> @@ -32,8 +32,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_key_binding,
>       cmd_switch_client_exec
> @@ -66,6 +66,7 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q *cmdq)
>       struct window           *w = NULL;
>       struct window_pane      *wp = NULL;
>       const char              *tflag;
> +     struct key_table        *table;
>  
>       if ((c = cmd_find_client(cmdq, args_get(args, 'c'), 0)) == NULL)
>               return (CMD_RETURN_ERROR);
> @@ -77,6 +78,17 @@ cmd_switch_client_exec(struct cmd *self, struct cmd_q 
> *cmdq)
>                       c->flags |= CLIENT_READONLY;
>       }
>  
> +     if (args_has(args, 'T')) {
> +             table = key_bindings_get_table(args_get(args, 'T'), 0);
> +             if (!table) {
> +                     cmdq_error(cmdq, "table %s doesn't exist", 
> args_get(args, 'T'));
> +                     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) {
> diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c
> index 1c4decb46b98..0adb41de531e 100644
> --- a/cmd-unbind-key.c
> +++ b/cmd-unbind-key.c
> @@ -31,8 +31,8 @@ enum cmd_retval      cmd_unbind_mode_key_table(struct cmd 
> *, struct cmd_q *, int);
>  
>  const struct cmd_entry cmd_unbind_key_entry = {
>       "unbind-key", "unbind",
> -     "acnt:", 0, 1,
> -     "[-acn] [-t mode-key-table] key",
> +     "acnt:T:", 0, 1,
> +     "[-acn] [-t mode-key-table] [-T key-table] key",
>       0,
>       NULL,
>       cmd_unbind_key_exec
> @@ -42,7 +42,6 @@ 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;
>  
>       if (!args_has(args, 'a')) {
> @@ -67,16 +66,26 @@ cmd_unbind_key_exec(struct cmd *self, struct cmd_q *cmdq)
>               return (cmd_unbind_mode_key_table(self, cmdq, key));
>  
>       if (key == KEYC_NONE) {
> -             while (!RB_EMPTY(&key_bindings)) {
> -                     bd = RB_ROOT(&key_bindings);
> -                     key_bindings_remove(bd->key);
> +             if (args_has(args, 'T')) {
> +                     key_bindings_remove_table(args_get(args, 'T'));
> +                     return (CMD_RETURN_NORMAL);
>               }
> +             key_bindings_remove_table("root");
> +             key_bindings_remove_table("prefix");
>               return (CMD_RETURN_NORMAL);
>       }
>  
> -     if (!args_has(args, 'n'))
> -             key |= KEYC_PREFIX;
> -     key_bindings_remove(key);
> +     if (args_has(args, 'T')) {
> +             key_bindings_remove(args_get(args, 'T'), key);
> +             return (CMD_RETURN_NORMAL);
> +     }
> +
> +     if (args_has(args, 'n')) {
> +             key_bindings_remove("root", key);
> +             return (CMD_RETURN_NORMAL);
> +     }
> +
> +     key_bindings_remove("prefix", key);
>       return (CMD_RETURN_NORMAL);
>  }
>  
> diff --git a/format.c b/format.c
> index 6f988b9ac2cc..77cb243dbba4 100644
> --- a/format.c
> +++ b/format.c
> @@ -435,7 +435,8 @@ format_client(struct format_tree *ft, struct client *c)
>       *strchr(tim, '\n') = '\0';
>       format_add(ft, "client_activity_string", "%s", tim);
>  
> -     format_add(ft, "client_prefix", "%d", !!(c->flags & CLIENT_PREFIX));
> +     format_add(ft, "client_prefix", strcmp(c->keytable->name, "root") ? 
> "1": "0");
> +     format_add(ft, "client_key_table", "%s", c->keytable->name);
>  
>       if (c->tty.flags & TTY_UTF8)
>               format_add(ft, "client_utf8", "%d", 1);
> diff --git a/key-bindings.c b/key-bindings.c
> index 58be0c6fe896..2d28425c4566 100644
> --- a/key-bindings.c
> +++ b/key-bindings.c
> @@ -25,60 +25,125 @@
>  #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_bindings  key_bindings;
> +struct key_tables    key_tables;
> +
> +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_search;
> +     struct key_table        *table;
> +
> +     table_search.name = name;
> +     table = RB_FIND(key_tables, &key_tables, &table_search);
> +     if (table)
> +             return table;
> +
> +     if (!create)
> +             return NULL;
> +
> +     table = xmalloc(sizeof *table);
> +     table->name = xstrdup(name);
> +     RB_INIT(&(table->key_bindings));
> +     /* for key_tables */
> +     table->references = 1;
> +     RB_INSERT(key_tables, &key_tables, table);
>  
> -     bd.key = key;
> -     return (RB_FIND(key_bindings, &key_bindings, &bd));
> +     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) {
> +             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_search;
> +     struct key_binding              *bd;
> +
> +     table = key_bindings_get_table(name, 1);
> +
> +     bd_search.key = key;
> +     bd = RB_FIND(key_bindings, &(table->key_bindings), &bd_search);
> +     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_search;
> +     struct key_binding              *bd;
> +
> +     table = key_bindings_get_table(name, 0);
> +     if (!table)
> +             return;
>  
> -     if ((bd = key_bindings_lookup(key)) == NULL)
> +     bd_search.key = key;
> +     bd = RB_FIND(key_bindings, &(table->key_bindings), &bd_search);
> +     if (!bd)
>               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)
> +             return;
> +
> +     RB_REMOVE(key_tables, &key_tables, table);
> +     key_bindings_unref_table(table);
>  }
>  
>  void
> @@ -167,7 +232,7 @@ key_bindings_init(void)
>       struct cmd      *cmd;
>       struct cmd_list *cmdlist;
>  
> -     RB_INIT(&key_bindings);
> +     RB_INIT(&key_tables);
>  
>       for (i = 0; i < nitems(table); i++) {
>               cmdlist = xcalloc(1, sizeof *cmdlist);
> @@ -183,7 +248,7 @@ key_bindings_init(void)
>               TAILQ_INSERT_HEAD(&cmdlist->list, cmd, qentry);
>  
>               key_bindings_add(
> -                 table[i].key | KEYC_PREFIX, table[i].can_repeat, cmdlist);
> +                 "prefix", table[i].key, table[i].can_repeat, cmdlist);
>       }
>  }
>  
> diff --git a/server-client.c b/server-client.c
> index e225de309b5f..154508cbc8ad 100644
> --- a/server-client.c
> +++ b/server-client.c
> @@ -98,6 +98,8 @@ server_client_create(int fd)
>       c->tty.mouse.flags = 0;
>  
>       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);
>  
> @@ -170,6 +172,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);
>  
> @@ -360,12 +364,14 @@ server_client_assume_paste(struct session *s)
>  void
>  server_client_handle_key(struct client *c, int key)
>  {
> -     struct session          *s;
> -     struct window           *w;
> -     struct window_pane      *wp;
> -     struct timeval           tv;
> -     struct key_binding      *bd;
> -     int                      xtimeout, isprefix, ispaste;
> +     struct session                  *s;
> +     struct window                   *w;
> +     struct window_pane              *wp;
> +     struct timeval                   tv;
> +     struct key_table                *table;
> +     struct key_binding               bd_search;
> +     struct key_binding              *bd;
> +     int                              xtimeout, isprefix, ispaste;
>  
>       /* Check the client is good to accept input. */
>       if ((c->flags & (CLIENT_DEAD|CLIENT_SUSPENDED)) != 0)
> @@ -427,64 +433,82 @@ server_client_handle_key(struct client *c, int key)
>  
>       /* Treat prefix as a regular key when pasting is detected. */
>       ispaste = server_client_assume_paste(s);
> -     if (ispaste)
> -             isprefix = 0;
> +     if (ispaste) {
> +             if (!(c->flags & CLIENT_READONLY))
> +                     window_pane_key(wp, s, key);
> +             return;
> +     }
> +
> +     /* Try to see if we hit a key binding. */
> +     for (;;) {
> +             bd_search.key = key;
> +             if ((bd = RB_FIND(key_bindings, &(c->keytable->key_bindings), 
> &bd_search)) != NULL) {
> +                     if ((c->flags & CLIENT_REPEAT) && !bd->can_repeat) {
> +                             /* We don't honor repeating into a non-repeat 
> binding, fall back to root and try again */
> +                             server_keytable_client(c, "root");
> +                             c->flags &= ~CLIENT_REPEAT;
> +                             server_status_client(c);
> +                             continue;
> +                     }
> +
> +                     /* Hold a reference to this table to make sure the key 
> binding doesn't disappear */
> +                     table = c->keytable;
> +                     table->references++;
> +
> +                     xtimeout = options_get_number(&s->options, 
> "repeat-time");
> +                     if (xtimeout != 0 && bd->can_repeat) {
> +                             /* Now repeating in same keytable */
> +                             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 {
> +                             /* "Stop" (or don't start) repeating */
> +                             c->flags &= ~CLIENT_REPEAT;
> +                             server_keytable_client(c, "root");
> +                     }
>  
> -     /* No previous prefix key. */
> -     if (!(c->flags & CLIENT_PREFIX)) {
> -             if (isprefix) {
> -                     c->flags |= CLIENT_PREFIX;
>                       server_status_client(c);
> +                     key_bindings_dispatch(bd, c);
> +
> +                     key_bindings_unref_table(table);
>                       return;
>               }
>  
> -             /* Try as a non-prefix key binding. */
> -             if (ispaste || (bd = key_bindings_lookup(key)) == NULL) {
> -                     if (!(c->flags & CLIENT_READONLY))
> -                             window_pane_key(wp, s, key);
> -             } else
> -                     key_bindings_dispatch(bd, c);
> -             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) {
> +                     /* We missed, but we were in repeat, fall back to root 
> and try again */
> +                     server_keytable_client(c, "root");
>                       c->flags &= ~CLIENT_REPEAT;
> -                     if (isprefix)
> -                             c->flags |= CLIENT_PREFIX;
> -                     else if (!(c->flags & CLIENT_READONLY))
> -                             window_pane_key(wp, s, key);
> +                     server_status_client(c);
> +                     continue;
>               }
> -             return;
> +
> +             /* Actual miss */
> +             break;
>       }
>  
> -     /* If already repeating, but this key can't repeat, skip it. */
> -     if (c->flags & CLIENT_REPEAT && !bd->can_repeat) {
> -             c->flags &= ~CLIENT_REPEAT;
> -             if (isprefix)
> -                     c->flags |= CLIENT_PREFIX;
> -             else if (!(c->flags & CLIENT_READONLY))
> -                     window_pane_key(wp, s, key);
> +     /* A miss in a non-root keytable fails out to root */
> +     if (strcmp(c->keytable->name, "root")) {
> +             server_keytable_client(c, "root");
> +             server_status_client(c);
>               return;
>       }
>  
> -     /* 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);
> +     /* A prefix miss in root switched to prefix */
> +     if (isprefix) {
> +             /* Prefix key switches to prefix table */
> +             server_keytable_client(c, "prefix");
> +             server_status_client(c);
> +             return;
>       }
>  
> -     /* Dispatch the command. */
> -     key_bindings_dispatch(bd, c);
> +     /* Anything else in root is straight through */
> +     if (!(c->flags & CLIENT_READONLY)) {
> +             window_pane_key(wp, s, key);
> +     }
>  }
>  
>  /* Client functions that need to happen every loop. */
> @@ -700,9 +724,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_keytable_client(c, "root");
> +             c->flags &= ~CLIENT_REPEAT;
> +             server_status_client(c);
>       }
>  }
>  
> diff --git a/server-fn.c b/server-fn.c
> index e0859f707034..a076b42154a5 100644
> --- a/server-fn.c
> +++ b/server-fn.c
> @@ -101,6 +101,14 @@ server_status_client(struct client *c)
>  }
>  
>  void
> +server_keytable_client(struct client *c, const char *name)
> +{
> +     key_bindings_unref_table(c->keytable);
> +     c->keytable = key_bindings_get_table(name, 1);
> +     c->keytable->references++;
> +}
> +
> +void
>  server_redraw_session(struct session *s)
>  {
>       struct client   *c;
> diff --git a/tmux.1 b/tmux.1
> index 7fd26cd7a199..e19904a2c8cc 100644
> --- a/tmux.1
> +++ b/tmux.1
> @@ -808,6 +808,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
> @@ -825,6 +826,9 @@ respectively.
>  toggles whether a client is read-only (see the
>  .Ic attach-session
>  command).
> +.Fl T
> +sets the client's key table; the next key from the client will be 
> interpretted from
> +.Ar key-table .
>  .El
>  .Sh WINDOWS AND PANES
>  A
> @@ -1843,6 +1847,7 @@ Commands related to key bindings are as follows:
>  .It Xo Ic bind-key
>  .Op Fl cnr
>  .Op Fl t Ar mode-key-table
> +.Op Fl T Ar key-table
>  .Ar key Ar command Op Ar arguments
>  .Xc
>  .D1 (alias: Ic bind )
> @@ -1878,6 +1883,21 @@ or for normal mode without.
>  To view the default bindings and possible commands, see the
>  .Ic list-keys
>  command.
> +.Pp
> +If
> +.Fl T
> +is present,
> +.Ar key
> +is bound in
> +.Ar key-table :
> +.Em prefix
> +corresponds to the default,
> +.Em root
> +corresponds to
> +.Fl n ,
> +and custom values may be used with
> +.Ic switch-client
> +.Fl T .
>  .It Ic list-keys Op Fl t Ar key-table
>  .D1 (alias: Ic lsk )
>  List all key bindings.
> @@ -1930,6 +1950,7 @@ 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-key-table
> +.Op Fl T Ar key-table
>  .Ar key
>  .Xc
>  .D1 (alias: Ic unbind )
> @@ -1955,6 +1976,22 @@ in
>  is unbound: the binding for command mode with
>  .Fl c
>  or for normal mode without.
> +.Pp
> +If
> +.Fl T
> +is present,
> +.Ar key
> +in
> +.Ar key-table
> +is unbound:
> +.Em prefix
> +corresponds to the default,
> +.Em root
> +corresponds to
> +.Fl n ,
> +and custom values may be used with
> +.Ic switch-client
> +.Fl T .
>  .El
>  .Sh OPTIONS
>  The appearance and behaviour of
> diff --git a/tmux.h b/tmux.h
> index f9d6087ab714..a4b3bbe30042 100644
> --- a/tmux.h
> +++ b/tmux.h
> @@ -164,10 +164,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)
>  
>  /* Other key codes. */
> @@ -1298,7 +1297,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
> @@ -1317,6 +1316,7 @@ struct client {
>  #define CLIENT_256COLOURS 0x20000
>  #define CLIENT_IDENTIFIED 0x40000
>       int              flags;
> +     struct key_table *keytable;
>  
>       struct event     identify_timer;
>  
> @@ -1444,6 +1444,15 @@ struct key_binding {
>       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
> @@ -1865,12 +1874,16 @@ int   cmd_string_parse(const char *, struct cmd_list 
> **, const char *,
>  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 *);
>  
> @@ -1906,6 +1919,7 @@ void     server_write_session(struct session *, enum 
> msgtype, const void *,
>            size_t);
>  void  server_redraw_client(struct client *);
>  void  server_status_client(struct client *);
> +void  server_keytable_client(struct client *, const char *);
>  void  server_redraw_session(struct session *);
>  void  server_redraw_session_group(struct session *);
>  void  server_status_session(struct session *);
> -- 
> 1.9.1
> 

------------------------------------------------------------------------------
The best possible search technologies are now affordable for all companies.
Download your FREE open source Enterprise Search Engine today!
Our experts will assist you in its installation for $59/mo, no commitment.
Test it for FREE on our Cloud platform anytime!
http://pubads.g.doubleclick.net/gampad/clk?id=145328191&iu=/4140/ostg.clktrk
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users

Reply via email to