On Mon, May 12, 2014 at 10:09:53PM -0700, Keith Amling wrote:
> > There should be no need for a prefix key anymore. Instead, the default
> > root table should have a binding for C-b to change the key table.
> > 
> > That way the prefix table is not special, the only special table is the
> > root table.
> > 
> > (Although we'll need some compatibility goo to keep the prefix option
> > and send-prefix working.)
> 
> I had considered that but turned it down to the [in my opinion]
> excessive amount of goo that would be required to deal with it.  In such
> a world there isn't a prefix key, there aren't two prefix keys (there's
> apparently already a "prefix2"!), there's just whatever the heck you
> decided to bind or unbind.  For what it's worth I think such a world is
> strictly superior architecturally but I just don't see it net worth it
> with the terrible backwards compatibility mess included.
> 
> This is by far to me the most unclear concern in terms of what to do
> about it.  I just don't see a way to translate an arbitrary collection
> of key bindings in to a legacy "prefix[2]" option view.  Are we to
> special case a read of the "prefix" option to return the first root key
> binding for "set-keytable prefix" it can find (and "prefix2" to return
> the 2nd)?  Presumably setting "prefix" will add the binding and delete
> the old value's binding (as long as "prefix2" isn't set to the same)?
> I'm just not seeing it.

I don't think it'd be that much compatibility code.

We could just maintain a flag for the "option-set" prefix in the root
table, and that can be used for prefix/send-prefix. The root table can
never be deleted so that isn't an issue.

prefix2 can go away once there is an alternative.

Still, if you want to make the other changes first I will look again and
we can decide on this later.

> 
> > - We should make set-keytable an argument to bind-key or (maybe better)
> >   switch-client rather than a new command.
> 
> I'm not sure I personally agree.  This seems like heading in the
> direction of git's "one command does fifteen barely-related things".
> That said it is your project and I'm happy to hack it that way if that's
> your final call.

Yes, we have too many commands and options already. I'd make it
switch-client -T.

> > - I think it would be better to point the client to a struct
> >   key_bindings, not to a string with the name. I know it won't save the
> >   lookup but it's neater than doing xstrdup/free all over the
> >   place. When a table is deleted you can walk the clients and reset them
> >   (or alternatively, reference count).
> 
> I agree xstrdup/free all over the place is a mess but keeping a pointer
> to an actual table seems like an accident waiting to happen in terms of
> safety.

You could say this about any pointer.

> Additionally it provides weirdness if e.g.  a user sets a
> keytable, then unbinds the last key in the keytable and rebinds it
> (causing the client to point to a stale empty keytable).

I don't see that this will be able to happen - when the user unbinds the
last key the table needs to be destroyed, at that point the client
should be fixed up (or the table destroy deferred until the client is
finished with it).

> Maybe move
> tablename getting/setting into functions to abstract it away?  Then we
> can even do something fancy like have canonical non-managed copies of
> the "ROOT" and "PREFIX" strings.

Yes if you want to try this first instead I can take a look.

> > - I don't like the table names in uppercase, "root" and "prefix" would
> >   be better.
> 
> As you wish.  While you're at it if you want them named something
> different entirely that would be reasonable, I pretty much just picked
> the two strings off the cuff.

I like the names, but I think lowercase is better.

> 
> > - I think it is safe to remove dead_key_bindings now but please do it as
> >   a separate diff. Likewise I would pull the mode-key renaming bits out
> >   separately.
> 
> Oh, yeah.  I had originally made the first a separate change on my local
> branch but then just smooshed it all to send without thinking about it.
> 
> I'm also curious what the history on that is, it wasn't clear to me why
> that was there in the first place.

Before cmdlists were reference counted, things like "bind x bind x lsk"
would cause a use-after-free.

> 
> > - You will save me a little time if you use "for (;;)" instead of "while
> >   (1)".
> 
> Uh, as you wish although I'm not sure I understand the subtext.  Just
> for my curiosity is it just that you prefer "for (;;)"?

Yes, if you leave it as "while (1)" I'll have to go and change it before
I apply the diff.

> 
> Keith
> 
> Thus spake Nicholas Marriott, at Tue, May 13, 2014 at 02:04:42AM +0100:
> > Date: Tue, 13 May 2014 02:04:42 +0100
> > From: Nicholas Marriott <nicholas.marri...@gmail.com>
> > To: Keith Amling <aml...@palantir.com>
> > CC: tmux-users@lists.sourceforge.net
> > Subject: Re: [PATCH] Allow custom key tables for e.g.  multiple keystroke
> >  bindings.
> > 
> > Thanks - I think this is the right idea, but not quite there.
> > 
> > There should be no need for a prefix key anymore. Instead, the default
> > root table should have a binding for C-b to change the key table.
> > 
> > That way the prefix table is not special, the only special table is the
> > root table.
> > 
> > (Although we'll need some compatibility goo to keep the prefix option
> > and send-prefix working.)
> > 
> > Also some specific comments on the diff:
> > 
> > - We should make set-keytable an argument to bind-key or (maybe better)
> >   switch-client rather than a new command.
> > 
> > - I think it would be better to point the client to a struct
> >   key_bindings, not to a string with the name. I know it won't save the
> >   lookup but it's neater than doing xstrdup/free all over the
> >   place. When a table is deleted you can walk the clients and reset them
> >   (or alternatively, reference count).
> > 
> > - I don't like the table names in uppercase, "root" and "prefix" would
> >   be better.
> > 
> > - I think it is safe to remove dead_key_bindings now but please do it as
> >   a separate diff. Likewise I would pull the mode-key renaming bits out
> >   separately.
> > 
> > - You will save me a little time if you use "for (;;)" instead of "while
> >   (1)".
> > 
> > 
> > On Mon, May 12, 2014 at 04:18:16PM -0700, Keith Amling wrote:
> > > Sorry if this isn't formatted as expected, I'm not at all familiar with
> > > the operation of git's e-mail tools.
> > > 
> > > Keith
> > > 
> > > ---
> > >  Makefile.am        |   1 +
> > >  cmd-bind-key.c     |  52 ++++++++++++++++---------
> > >  cmd-list-keys.c    |  80 +++++++++++++++++++-------------------
> > >  cmd-set-keytable.c |  44 +++++++++++++++++++++
> > >  cmd-unbind-key.c   |  48 +++++++++++++++++------
> > >  cmd.c              |   1 +
> > >  format.c           |   3 +-
> > >  key-bindings.c     |  95 ++++++++++++++++++++++++++++-----------------
> > >  server-client.c    | 110 
> > > +++++++++++++++++++++++++++++++----------------------
> > >  server.c           |   1 -
> > >  tmux.1             |  46 ++++++++++++++++++++--
> > >  tmux.h             |  26 +++++++++----
> > >  12 files changed, 341 insertions(+), 166 deletions(-)
> > >  create mode 100644 cmd-set-keytable.c
> > > 
> > > diff --git a/Makefile.am b/Makefile.am
> > > index 5502de86d276..eb9fc9ed60bb 100644
> > > --- a/Makefile.am
> > > +++ b/Makefile.am
> > > @@ -123,6 +123,7 @@ dist_tmux_SOURCES = \
> > >   cmd-send-keys.c \
> > >   cmd-set-buffer.c \
> > >   cmd-set-environment.c \
> > > + cmd-set-keytable.c \
> > >   cmd-set-option.c \
> > >   cmd-show-environment.c \
> > >   cmd-show-messages.c \
> > > diff --git a/cmd-bind-key.c b/cmd-bind-key.c
> > > index 4ff3ac8431bc..e472a5a067dc 100644
> > > --- a/cmd-bind-key.c
> > > +++ b/cmd-bind-key.c
> > > @@ -29,12 +29,14 @@
> > >  
> > >  enum cmd_retval   cmd_bind_key_exec(struct cmd *, struct cmd_q *);
> > >  
> > > -enum cmd_retval   cmd_bind_key_table(struct cmd *, struct cmd_q *, int);
> > > +enum cmd_retval   cmd_bind_mode_key_table(struct cmd *, struct cmd_q *, 
> > > int);
> > > +
> > > +enum cmd_retval   cmd_bind_key_table(struct cmd *, const char *, struct 
> > > cmd_q *, int);
> > >  
> > >  const struct cmd_entry cmd_bind_key_entry = {
> > >   "bind-key", "bind",
> > > - "cnrt:", 1, -1,
> > > - "[-cnr] [-t 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
> > > @@ -44,11 +46,9 @@ enum cmd_retval
> > >  cmd_bind_key_exec(struct cmd *self, struct cmd_q *cmdq)
> > >  {
> > >   struct args     *args = self->args;
> > > - char            *cause;
> > > - struct cmd_list *cmdlist;
> > >   int              key;
> > >  
> > > - if (args_has(args, 't')) {
> > > + if (args_has(args, 't') || args_has(args, 'T')) {
> > >           if (args->argc != 2 && args->argc != 3) {
> > >                   cmdq_error(cmdq, "not enough arguments");
> > >                   return (CMD_RETURN_ERROR);
> > > @@ -67,24 +67,19 @@ cmd_bind_key_exec(struct cmd *self, struct cmd_q 
> > > *cmdq)
> > >   }
> > >  
> > >   if (args_has(args, 't'))
> > > -         return (cmd_bind_key_table(self, cmdq, key));
> > > +         return (cmd_bind_mode_key_table(self, cmdq, key));
> > >  
> > > - cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0,
> > > -     &cause);
> > > - if (cmdlist == NULL) {
> > > -         cmdq_error(cmdq, "%s", cause);
> > > -         free(cause);
> > > -         return (CMD_RETURN_ERROR);
> > > - }
> > > + if (args_has(args, 'T'))
> > > +         return (cmd_bind_key_table(self, args_get(args, 'T'), cmdq, 
> > > key));
> > >  
> > > - if (!args_has(args, 'n'))
> > > -     key |= KEYC_PREFIX;
> > > - key_bindings_add(key, args_has(args, 'r'), cmdlist);
> > > - return (CMD_RETURN_NORMAL);
> > > + if (args_has(args, 'n'))
> > > +         return (cmd_bind_key_table(self, "ROOT", cmdq, key));
> > > +
> > > + return (cmd_bind_key_table(self, "PREFIX", cmdq, key));
> > >  }
> > >  
> > >  enum cmd_retval
> > > -cmd_bind_key_table(struct cmd *self, struct cmd_q *cmdq, int key)
> > > +cmd_bind_mode_key_table(struct cmd *self, struct cmd_q *cmdq, int key)
> > >  {
> > >   struct args                     *args = self->args;
> > >   const char                      *tablename;
> > > @@ -131,3 +126,22 @@ cmd_bind_key_table(struct cmd *self, struct cmd_q 
> > > *cmdq, int key)
> > >   mbind->arg = arg != NULL ? xstrdup(arg) : NULL;
> > >   return (CMD_RETURN_NORMAL);
> > >  }
> > > +
> > > +enum cmd_retval
> > > +cmd_bind_key_table(struct cmd *self, const char *tablename, struct cmd_q 
> > > *cmdq, int key)
> > > +{
> > > + struct args     *args = self->args;
> > > + char            *cause;
> > > + struct cmd_list *cmdlist;
> > > +
> > > + cmdlist = cmd_list_parse(args->argc - 1, args->argv + 1, NULL, 0,
> > > +     &cause);
> > > + if (cmdlist == NULL) {
> > > +         cmdq_error(cmdq, "%s", cause);
> > > +         free(cause);
> > > +         return (CMD_RETURN_ERROR);
> > > + }
> > > +
> > > + key_bindings_add(tablename, key, args_has(args, 'r'), cmdlist);
> > > + return (CMD_RETURN_NORMAL);
> > > +}
> > > diff --git a/cmd-list-keys.c b/cmd-list-keys.c
> > > index 615c5ce1fe67..20fc386286e9 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_binding_map_entry    *e;
> > > + 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(e, key_binding_map, &key_binding_map) {
> > > +         RB_FOREACH(bd, key_bindings, &(e->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(e->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(e, key_binding_map, &key_binding_map) {
> > > +         RB_FOREACH(bd, key_bindings, &(e->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, e->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-set-keytable.c b/cmd-set-keytable.c
> > > new file mode 100644
> > > index 000000000000..b009233101b4
> > > --- /dev/null
> > > +++ b/cmd-set-keytable.c
> > > @@ -0,0 +1,44 @@
> > > +/* $Id$ */
> > > +
> > > +/*
> > > + * Copyright (c) 2007 Nicholas Marriott <n...@users.sourceforge.net>
> > > + *
> > > + * Permission to use, copy, modify, and distribute this software for any
> > > + * purpose with or without fee is hereby granted, provided that the above
> > > + * copyright notice and this permission notice appear in all copies.
> > > + *
> > > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 
> > > WARRANTIES
> > > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> > > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE 
> > > FOR
> > > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> > > + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
> > > + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
> > > + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> > > + */
> > > +
> > > +#include <sys/types.h>
> > > +
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +
> > > +#include "tmux.h"
> > > +
> > > +void              cmd_set_keytable_key_binding(struct cmd *, int);
> > > +enum cmd_retval   cmd_set_keytable_exec(struct cmd *, struct cmd_q *);
> > > +
> > > +const struct cmd_entry cmd_set_keytable_entry = {
> > > + "set-keytable", "setkt",
> > > + "", 1, 1,
> > > + "table",
> > > + 0,
> > > + NULL,
> > > + cmd_set_keytable_exec
> > > +};
> > > +
> > > +enum cmd_retval
> > > +cmd_set_keytable_exec(struct cmd *self, struct cmd_q *cmdq)
> > > +{
> > > + free(cmdq->client->keytablename);
> > > + cmdq->client->keytablename = xstrdup(self->args->argv[0]);
> > > + return (CMD_RETURN_NORMAL);
> > > +}
> > > diff --git a/cmd-unbind-key.c b/cmd-unbind-key.c
> > > index cf6ad506195f..cbc4743f3265 100644
> > > --- a/cmd-unbind-key.c
> > > +++ b/cmd-unbind-key.c
> > > @@ -27,12 +27,13 @@
> > >   */
> > >  
> > >  enum cmd_retval   cmd_unbind_key_exec(struct cmd *, struct cmd_q *);
> > > -enum cmd_retval   cmd_unbind_key_table(struct cmd *, struct cmd_q *, 
> > > int);
> > > +enum cmd_retval   cmd_unbind_mode_key_table(struct cmd *, struct cmd_q 
> > > *, int);
> > > +void              cmd_unbind_key_wipe(const char *);
> > >  
> > >  const struct cmd_entry cmd_unbind_key_entry = {
> > >   "unbind-key", "unbind",
> > > - "acnt:", 0, 1,
> > > - "[-acn] [-t key-table] key",
> > > + "acnt:T:", 0, 1,
> > > + "[-acn] [-t mode-key-table] [-T key-table] key",
> > >   0,
> > >   NULL,
> > >   cmd_unbind_key_exec
> > > @@ -42,7 +43,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')) {
> > > @@ -64,24 +64,34 @@ cmd_unbind_key_exec(struct cmd *self, struct cmd_q 
> > > *cmdq)
> > >   }
> > >  
> > >   if (args_has(args, 't'))
> > > -         return (cmd_unbind_key_table(self, cmdq, key));
> > > +         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')) {
> > > +                 cmd_unbind_key_wipe(args_get(args, 't'));
> > > +                 return (CMD_RETURN_NORMAL);
> > >           }
> > > +         cmd_unbind_key_wipe("ROOT");
> > > +         cmd_unbind_key_wipe("PREFIX");
> > > +         return (CMD_RETURN_NORMAL);
> > > + }
> > > +
> > > + 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("PREFIX", key);
> > >           return (CMD_RETURN_NORMAL);
> > >   }
> > >  
> > > - if (!args_has(args, 'n'))
> > > -         key |= KEYC_PREFIX;
> > > - key_bindings_remove(key);
> > > + key_bindings_remove("ROOT", key);
> > >   return (CMD_RETURN_NORMAL);
> > >  }
> > >  
> > >  enum cmd_retval
> > > -cmd_unbind_key_table(struct cmd *self, struct cmd_q *cmdq, int key)
> > > +cmd_unbind_mode_key_table(struct cmd *self, struct cmd_q *cmdq, int key)
> > >  {
> > >   struct args                     *args = self->args;
> > >   const char                      *tablename;
> > > @@ -111,3 +121,17 @@ cmd_unbind_key_table(struct cmd *self, struct cmd_q 
> > > *cmdq, int key)
> > >   }
> > >   return (CMD_RETURN_NORMAL);
> > >  }
> > > +
> > > +void
> > > +cmd_unbind_key_wipe(const char *tablename) {
> > > + struct key_bindings     *key_bindings;
> > > + struct key_binding      *bd;
> > > +
> > > + while (1) {
> > > +         key_bindings = key_bindings_lookup_table(tablename, 0);
> > > +         if (key_bindings == NULL)
> > > +                 return;
> > > +         bd = RB_ROOT(key_bindings);
> > > +         key_bindings_remove(tablename, bd->key);
> > > + }
> > > +}
> > > diff --git a/cmd.c b/cmd.c
> > > index f2d88c050637..0c11a84766f7 100644
> > > --- a/cmd.c
> > > +++ b/cmd.c
> > > @@ -95,6 +95,7 @@ const struct cmd_entry *cmd_table[] = {
> > >   &cmd_server_info_entry,
> > >   &cmd_set_buffer_entry,
> > >   &cmd_set_environment_entry,
> > > + &cmd_set_keytable_entry,
> > >   &cmd_set_option_entry,
> > >   &cmd_set_window_option_entry,
> > >   &cmd_show_buffer_entry,
> > > diff --git a/format.c b/format.c
> > > index 6f988b9ac2cc..4d3fc057f246 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->keytablename, "ROOT") ? "1": 
> > > "0");
> > > + format_add(ft, "client_keytablename", "%s", c->keytablename);
> > >  
> > >   if (c->tty.flags & TTY_UTF8)
> > >           format_add(ft, "client_utf8", "%d", 1);
> > > diff --git a/key-bindings.c b/key-bindings.c
> > > index f725508bce62..e5da8c97cd1d 100644
> > > --- a/key-bindings.c
> > > +++ b/key-bindings.c
> > > @@ -25,72 +25,97 @@
> > >  #include "tmux.h"
> > >  
> > >  RB_GENERATE(key_bindings, key_binding, entry, key_bindings_cmp);
> > > +RB_GENERATE(key_binding_map, key_binding_map_entry, entry, 
> > > key_binding_map_entry_cmp);
> > >  
> > > -struct key_bindings      key_bindings;
> > > -struct key_bindings      dead_key_bindings;
> > > +struct key_binding_map   key_binding_map;
> > > +
> > > +int
> > > +key_binding_map_entry_cmp(struct key_binding_map_entry *e1, struct 
> > > key_binding_map_entry *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_bindings *
> > > +key_bindings_lookup_table(const char *name, int create)
> > > +{
> > > + struct key_binding_map_entry    e;
> > > + struct key_binding_map_entry    *e_found;
> > > +
> > > + e.name = name;
> > > + e_found = RB_FIND(key_binding_map, &key_binding_map, &e);
> > > + if (e_found)
> > > +         return &(e_found->key_bindings);
> > > +
> > > + if (!create)
> > > +         return NULL;
> > > +
> > > + e_found = xmalloc(sizeof *e_found);
> > > + e_found->name = xstrdup(name);
> > > + RB_INIT(&(e_found->key_bindings));
> > > + RB_INSERT(key_binding_map, &key_binding_map, e_found);
> > > +
> > > + return &(e_found->key_bindings);
> > >  }
> > >  
> > >  struct key_binding *
> > > -key_bindings_lookup(int key)
> > > +key_bindings_lookup(const char *name, int key)
> > >  {
> > > + struct key_bindings     *key_bindings;
> > >   struct key_binding      bd;
> > >  
> > > + key_bindings = key_bindings_lookup_table(name, 0);
> > > + if (!key_bindings)
> > > +         return NULL;
> > > +
> > >   bd.key = key;
> > > - return (RB_FIND(key_bindings, &key_bindings, &bd));
> > > + return (RB_FIND(key_bindings, key_bindings, &bd));
> > >  }
> > >  
> > >  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_bindings     *key_bindings;
> > >   struct key_binding      *bd;
> > >  
> > > - key_bindings_remove(key);
> > > + key_bindings_remove(name, key);
> > > +
> > > + key_bindings = key_bindings_lookup_table(name, 1);
> > >  
> > >   bd = xmalloc(sizeof *bd);
> > >   bd->key = key;
> > > - RB_INSERT(key_bindings, &key_bindings, bd);
> > > + RB_INSERT(key_bindings, 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_map_entry    e;
> > > + struct key_bindings     *key_bindings;
> > >   struct key_binding      *bd;
> > >  
> > > - if ((bd = key_bindings_lookup(key)) == NULL)
> > > + if ((bd = key_bindings_lookup(name, key)) == NULL)
> > >           return;
> > > - RB_REMOVE(key_bindings, &key_bindings, bd);
> > > - RB_INSERT(key_bindings, &dead_key_bindings, bd);
> > > -}
> > >  
> > > -void
> > > -key_bindings_clean(void)
> > > -{
> > > - struct key_binding      *bd;
> > > + key_bindings = key_bindings_lookup_table(name, 0);
> > > + if (!key_bindings)
> > > +         return;
> > > +
> > > + RB_REMOVE(key_bindings, key_bindings, bd);
> > > + cmd_list_free(bd->cmdlist);
> > > + free(bd);
> > >  
> > > - while (!RB_EMPTY(&dead_key_bindings)) {
> > > -         bd = RB_ROOT(&dead_key_bindings);
> > > -         RB_REMOVE(key_bindings, &dead_key_bindings, bd);
> > > -         cmd_list_free(bd->cmdlist);
> > > -         free(bd);
> > > + if (RB_EMPTY(key_bindings)) {
> > > +         e.name = name;
> > > +         RB_REMOVE(key_binding_map, &key_binding_map, &e);
> > >   }
> > >  }
> > >  
> > > @@ -180,7 +205,7 @@ key_bindings_init(void)
> > >   struct cmd      *cmd;
> > >   struct cmd_list *cmdlist;
> > >  
> > > - RB_INIT(&key_bindings);
> > > + RB_INIT(&key_binding_map);
> > >  
> > >   for (i = 0; i < nitems(table); i++) {
> > >           cmdlist = xcalloc(1, sizeof *cmdlist);
> > > @@ -196,7 +221,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..c853712d3f10 100644
> > > --- a/server-client.c
> > > +++ b/server-client.c
> > > @@ -98,6 +98,7 @@ server_client_create(int fd)
> > >   c->tty.mouse.flags = 0;
> > >  
> > >   c->flags |= CLIENT_FOCUSED;
> > > + c->keytablename = xstrdup("ROOT");
> > >  
> > >   evtimer_set(&c->repeat_timer, server_client_repeat_timer, c);
> > >  
> > > @@ -170,6 +171,8 @@ server_client_lost(struct client *c)
> > >  
> > >   evtimer_del(&c->repeat_timer);
> > >  
> > > + free(c->keytablename);
> > > +
> > >   if (event_initialized(&c->identify_timer))
> > >           evtimer_del(&c->identify_timer);
> > >  
> > > @@ -427,64 +430,78 @@ 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;
> > > + }
> > >  
> > > - /* No previous prefix key. */
> > > - if (!(c->flags & CLIENT_PREFIX)) {
> > > -         if (isprefix) {
> > > -                 c->flags |= CLIENT_PREFIX;
> > > + /* Try to see if we hit a key binding. */
> > > + while (1) {
> > > +         if ((bd = key_bindings_lookup(c->keytablename, key)) != 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 */
> > > +                         free(c->keytablename);
> > > +                         c->keytablename = xstrdup("ROOT");
> > > +                         c->flags &= ~CLIENT_REPEAT;
> > > +                         server_status_client(c);
> > > +                         continue;
> > > +                 }
> > > +                 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;
> > > +                         free(c->keytablename);
> > > +                         c->keytablename = xstrdup("ROOT");
> > > +                 }
> > >                   server_status_client(c);
> > > +                 key_bindings_dispatch(bd, c);
> > >                   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 */
> > > +                 free(c->keytablename);
> > > +                 c->keytablename = xstrdup("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->keytablename, "ROOT")) {
> > > +         free(c->keytablename);
> > > +         c->keytablename = xstrdup("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 */
> > > +         free(c->keytablename);
> > > +         c->keytablename = xstrdup("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 +717,10 @@ 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);
> > > +         free(c->keytablename);
> > > +         c->keytablename = xstrdup("ROOT");
> > > +         c->flags &= ~CLIENT_REPEAT;
> > > +         server_status_client(c);
> > >   }
> > >  }
> > >  
> > > diff --git a/server.c b/server.c
> > > index d3ac0f8b0cdd..5ae44df23eff 100644
> > > --- a/server.c
> > > +++ b/server.c
> > > @@ -209,7 +209,6 @@ server_loop(void)
> > >           server_window_loop();
> > >           server_client_loop();
> > >  
> > > -         key_bindings_clean();
> > >           server_clean_dead();
> > >   }
> > >  }
> > > diff --git a/tmux.1 b/tmux.1
> > > index c05eacfdbc53..3a89e26c20d3 100644
> > > --- a/tmux.1
> > > +++ b/tmux.1
> > > @@ -1842,7 +1842,8 @@ Commands related to key bindings are as follows:
> > >  .Bl -tag -width Ds
> > >  .It Xo Ic bind-key
> > >  .Op Fl cnr
> > > -.Op Fl t Ar key-table
> > > +.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 )
> > > @@ -1871,13 +1872,28 @@ If
> > >  is present,
> > >  .Ar key
> > >  is bound in
> > > -.Ar key-table :
> > > +.Ar mode-key-table :
> > >  the binding for command mode with
> > >  .Fl c
> > >  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 the
> > > +.Ic set-keytable
> > > +command.
> > >  .It Ic list-keys Op Fl t Ar key-table
> > >  .D1 (alias: Ic lsk )
> > >  List all key bindings.
> > > @@ -1927,9 +1943,15 @@ flag causes the terminal state to be reset.
> > >  Send the prefix key, or with
> > >  .Fl 2
> > >  the secondary prefix key, to a window as if it was pressed.
> > > +.It Xo Ic set-keytable
> > > +.Ar key-table
> > > +.Xc
> > > +Set the client's key table.  The next key from the client will be 
> > > interpretted from
> > > +.Ar key-table .
> > >  .It Xo Ic unbind-key
> > >  .Op Fl acn
> > > -.Op Fl t Ar key-table
> > > +.Op Fl t Ar mode-key-table
> > > +.Op Fl T Ar key-table
> > >  .Ar key
> > >  .Xc
> > >  .D1 (alias: Ic unbind )
> > > @@ -1951,10 +1973,26 @@ If
> > >  is present,
> > >  .Ar key
> > >  in
> > > -.Ar key-table
> > > +.Ar mode-key-table
> > >  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 the
> > > +.Ic set-keytable
> > > +command.
> > >  .El
> > >  .Sh OPTIONS
> > >  The appearance and behaviour of
> > > diff --git a/tmux.h b/tmux.h
> > > index fde94afc47b3..55463fcacce0 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
> > > +/* replaced by keytablename: #define CLIENT_PREFIX 0x2 */
> > >  #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;
> > > + char             *keytablename;
> > >  
> > >   struct event     identify_timer;
> > >  
> > > @@ -1444,6 +1444,13 @@ struct key_binding {
> > >   RB_ENTRY(key_binding) entry;
> > >  };
> > >  RB_HEAD(key_bindings, key_binding);
> > > +struct key_binding_map_entry {
> > > + const char *name;
> > > + struct key_bindings key_bindings;
> > > +
> > > + RB_ENTRY(key_binding_map_entry) entry;
> > > +};
> > > +RB_HEAD(key_binding_map, key_binding_map_entry);
> > >  
> > >  /*
> > >   * Option table entries. The option table is the user-visible part of the
> > > @@ -1818,6 +1825,7 @@ extern const struct cmd_entry cmd_send_prefix_entry;
> > >  extern const struct cmd_entry cmd_server_info_entry;
> > >  extern const struct cmd_entry cmd_set_buffer_entry;
> > >  extern const struct cmd_entry cmd_set_environment_entry;
> > > +extern const struct cmd_entry cmd_set_keytable_entry;
> > >  extern const struct cmd_entry cmd_set_option_entry;
> > >  extern const struct cmd_entry cmd_set_window_option_entry;
> > >  extern const struct cmd_entry cmd_show_buffer_entry;
> > > @@ -1865,13 +1873,15 @@ 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;
> > > +extern struct key_binding_map key_binding_map;
> > > +int       key_binding_map_entry_cmp(struct key_binding_map_entry *, 
> > > struct key_binding_map_entry *);
> > >  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);
> > > -void      key_bindings_clean(void);
> > > +RB_PROTOTYPE(key_binding_map, key_binding_map_entry, entry, 
> > > key_binding_map_entry_cmp);
> > > +struct key_bindings *key_bindings_lookup_table(const char *, int);
> > > +struct key_binding *key_bindings_lookup(const char *, int);
> > > +void      key_bindings_add(const char *, int, int, struct cmd_list *);
> > > +void      key_bindings_remove(const char *, int);
> > >  void      key_bindings_init(void);
> > >  void      key_bindings_dispatch(struct key_binding *, struct client *);
> > >  
> > > -- 
> > > 1.9.1
> > > 
> > > 
> > > ------------------------------------------------------------------------------
> > > "Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
> > > Instantly run your Selenium tests across 300+ browser/OS combos.
> > > Get unparalleled scalability from the best Selenium testing platform 
> > > available
> > > Simple to use. Nothing to install. Get started now for free."
> > > https://urldefense.proofpoint.com/v1/url?u=http://p.sf.net/sfu/SauceLabs&k=fDZpZZQMmYwf27OU23GmAQ%3D%3D%0A&r=BTdtcPIZXD8V7r6BhVE1Cy1S1ITG2lF8LZPYHbBpv%2B0%3D%0A&m=bi1uWxNPLiV%2BM52G7zv4qsFMczpysaL4AG0%2B1C7o1%2BA%3D%0A&s=c4a65022a4d6b8349d946735d01b625ad7de4b11b6efb75ba34521c9be5196a7
> > > _______________________________________________
> > > tmux-users mailing list
> > > tmux-users@lists.sourceforge.net
> > > https://urldefense.proofpoint.com/v1/url?u=https://lists.sourceforge.net/lists/listinfo/tmux-users&k=fDZpZZQMmYwf27OU23GmAQ%3D%3D%0A&r=BTdtcPIZXD8V7r6BhVE1Cy1S1ITG2lF8LZPYHbBpv%2B0%3D%0A&m=bi1uWxNPLiV%2BM52G7zv4qsFMczpysaL4AG0%2B1C7o1%2BA%3D%0A&s=27481cdf90a90d18d51c90ade176c49c5f38f3831394fe992fd0a0ad99b81223

------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users

Reply via email to