Add support for adding a note to a key binding (with bind-key -N) and

use this to add descriptions to the default key bindings. A new -N flag
to list-keys shows key bindings with notes rather than the default
bind-key command used to create them. Change the default ? binding to
use this to show a readable summary of keys.

Also extend command-prompt to return the name of the key pressed and add
a default binding (/) to show the note for the next key pressed

Suggested by Alex Tremblay in GitHub issue 2000.
This commit is contained in:
nicm 2020-01-27 08:53:13 +00:00
parent 2e39b621c9
commit d0b8d036be
7 changed files with 292 additions and 105 deletions

View File

@ -33,8 +33,8 @@ const struct cmd_entry cmd_bind_key_entry = {
.name = "bind-key",
.alias = "bind",
.args = { "cnrT:", 2, -1 },
.usage = "[-cnr] [-T key-table] key "
.args = { "cnrN:T:", 2, -1 },
.usage = "[-cnr] [-T key-table] [-N note] key "
"command [arguments]",
.flags = CMD_AFTERHOOK,
@ -46,10 +46,10 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = self->args;
key_code key;
const char *tablename;
const char *tablename, *note;
struct cmd_parse_result *pr;
char **argv = args->argv;
int argc = args->argc;
int argc = args->argc, repeat;
key = key_string_lookup_string(argv[0]);
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
@ -63,6 +63,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
tablename = "root";
else
tablename = "prefix";
repeat = args_has(args, 'r');
if (argc == 2)
pr = cmd_parse_from_string(argv[1], NULL);
@ -79,6 +80,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
case CMD_PARSE_SUCCESS:
break;
}
key_bindings_add(tablename, key, args_has(args, 'r'), pr->cmdlist);
note = args_get(args, 'N');
key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
return (CMD_RETURN_NORMAL);
}

View File

@ -40,8 +40,8 @@ const struct cmd_entry cmd_command_prompt_entry = {
.name = "command-prompt",
.alias = NULL,
.args = { "1iI:Np:t:", 0, 1 },
.usage = "[-1Ni] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " "
.args = { "1kiI:Np:t:", 0, 1 },
.usage = "[-1kiN] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " "
"[template]",
.flags = 0,
@ -122,6 +122,8 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata->flags |= PROMPT_NUMERIC;
else if (args_has(args, 'i'))
cdata->flags |= PROMPT_INCREMENTAL;
else if (args_has(args, 'k'))
cdata->flags |= PROMPT_KEY;
status_prompt_set(c, prompt, input, cmd_command_prompt_callback,
cmd_command_prompt_free, cdata, cdata->flags);
free(prompt);

View File

@ -36,8 +36,8 @@ const struct cmd_entry cmd_list_keys_entry = {
.name = "list-keys",
.alias = "lsk",
.args = { "T:", 0, 0 },
.usage = "[-T key-table]",
.args = { "1NP:T:", 0, 1 },
.usage = "[-1N] [-P prefix-string] [-T key-table] [key]",
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
.exec = cmd_list_keys_exec
@ -54,6 +54,88 @@ const struct cmd_entry cmd_list_commands_entry = {
.exec = cmd_list_keys_exec
};
static u_int
cmd_list_keys_get_width(const char *tablename, key_code only)
{
struct key_table *table;
struct key_binding *bd;
u_int width, keywidth = 0;
table = key_bindings_get_table(tablename, 0);
if (table == NULL)
return (0);
bd = key_bindings_first(table);
while (bd != NULL) {
if ((only != KEYC_UNKNOWN && bd->key != only) ||
KEYC_IS_MOUSE(bd->key) ||
bd->note == NULL) {
bd = key_bindings_next(table, bd);
continue;
}
width = utf8_cstrwidth(key_string_lookup_key(bd->key));
if (width > keywidth)
keywidth = width;
bd = key_bindings_next(table, bd);
}
return (keywidth);
}
static int
cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args,
const char *tablename, u_int keywidth, key_code only, const char *prefix)
{
struct client *c = cmd_find_client(item, NULL, 1);
struct key_table *table;
struct key_binding *bd;
const char *key;
char *tmp;
int found = 0;
table = key_bindings_get_table(tablename, 0);
if (table == NULL)
return (0);
bd = key_bindings_first(table);
while (bd != NULL) {
if ((only != KEYC_UNKNOWN && bd->key != only) ||
KEYC_IS_MOUSE(bd->key) ||
bd->note == NULL) {
bd = key_bindings_next(table, bd);
continue;
}
found = 1;
key = key_string_lookup_key(bd->key);
tmp = utf8_padcstr(key, keywidth + 1);
if (args_has(args, '1') && c != NULL)
status_message_set(c, "%s%s%s", prefix, tmp, bd->note);
else
cmdq_print(item, "%s%s%s", prefix, tmp, bd->note);
free(tmp);
if (args_has(args, '1'))
break;
bd = key_bindings_next(table, bd);
}
return (found);
}
static char *
cmd_list_keys_get_prefix(struct args *args, key_code *prefix)
{
char *s;
*prefix = options_get_number(global_s_options, "prefix");
if (!args_has(args, 'P')) {
if (*prefix != KEYC_NONE)
xasprintf(&s, "%s ", key_string_lookup_key(*prefix));
else
s = xstrdup("");
} else
s = xstrdup(args_get(args, 'P'));
return (s);
}
static enum cmd_retval
cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
{
@ -61,19 +143,63 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
struct key_table *table;
struct key_binding *bd;
const char *tablename, *r;
char *key, *cp, *tmp;
int repeat, width, tablewidth, keywidth;
char *key, *cp, *tmp, *start, *empty;
key_code prefix, only = KEYC_UNKNOWN;
int repeat, width, tablewidth, keywidth, found = 0;
size_t tmpsize, tmpused, cplen;
if (self->entry == &cmd_list_commands_entry)
return (cmd_list_keys_commands(self, item));
if (args->argc != 0) {
only = key_string_lookup_string(args->argv[0]);
if (only == KEYC_UNKNOWN) {
cmdq_error(item, "invalid key: %s", args->argv[0]);
return (CMD_RETURN_ERROR);
}
}
tablename = args_get(args, 'T');
if (tablename != NULL && key_bindings_get_table(tablename, 0) == NULL) {
cmdq_error(item, "table %s doesn't exist", tablename);
return (CMD_RETURN_ERROR);
}
if (args_has(args, 'N')) {
if (tablename == NULL) {
start = cmd_list_keys_get_prefix(args, &prefix);
keywidth = cmd_list_keys_get_width("root", only);
if (prefix != KEYC_NONE) {
width = cmd_list_keys_get_width("prefix", only);
if (width == 0)
prefix = KEYC_NONE;
else if (width > keywidth)
keywidth = width;
}
empty = utf8_padcstr("", utf8_cstrwidth(start));
found = cmd_list_keys_print_notes(item, args, "root",
keywidth, only, empty);
if (prefix != KEYC_NONE) {
if (cmd_list_keys_print_notes(item, args,
"prefix", keywidth, only, start))
found = 1;
}
free(empty);
} else {
if (args_has(args, 'P'))
start = xstrdup(args_get(args, 'P'));
else
start = xstrdup("");
keywidth = cmd_list_keys_get_width(tablename, only);
found = cmd_list_keys_print_notes(item, args, tablename,
keywidth, only, start);
}
free(start);
goto out;
}
repeat = 0;
tablewidth = keywidth = 0;
table = key_bindings_first_table ();
@ -84,6 +210,10 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
}
bd = key_bindings_first(table);
while (bd != NULL) {
if (only != KEYC_UNKNOWN && bd->key != only) {
bd = key_bindings_next(table, bd);
continue;
}
key = args_escape(key_string_lookup_key(bd->key));
if (bd->flags & KEY_BINDING_REPEAT)
@ -113,6 +243,11 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
}
bd = key_bindings_first(table);
while (bd != NULL) {
if (only != KEYC_UNKNOWN && bd->key != only) {
bd = key_bindings_next(table, bd);
continue;
}
found = 1;
key = args_escape(key_string_lookup_key(bd->key));
if (!repeat)
@ -162,13 +297,18 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
free(tmp);
out:
if (only != KEYC_UNKNOWN && !found) {
cmdq_error(item, "unknown key: %s", args->argv[0]);
return (CMD_RETURN_ERROR);
}
return (CMD_RETURN_NORMAL);
}
static enum cmd_retval
cmd_list_keys_commands(struct cmd *self, struct cmdq_item *item)
{
struct args *args = self->args;
struct args *args = self->args;
const struct cmd_entry **entryp;
const struct cmd_entry *entry;
struct format_tree *ft;

View File

@ -90,6 +90,7 @@ key_bindings_free(struct key_table *table, struct key_binding *bd)
{
RB_REMOVE(key_bindings, &table->key_bindings, bd);
cmd_list_free(bd->cmdlist);
free((void *)bd->note);
free(bd);
}
@ -163,7 +164,7 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd)
}
void
key_bindings_add(const char *name, key_code key, int repeat,
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
struct cmd_list *cmdlist)
{
struct key_table *table;
@ -177,6 +178,8 @@ key_bindings_add(const char *name, key_code key, int repeat,
bd = xcalloc(1, sizeof *bd);
bd->key = key;
if (note != NULL)
bd->note = xstrdup(note);
RB_INSERT(key_bindings, &table->key_bindings, bd);
if (repeat)
@ -226,87 +229,88 @@ void
key_bindings_init(void)
{
static const char *defaults[] = {
"bind C-b send-prefix",
"bind C-o rotate-window",
"bind C-z suspend-client",
"bind Space next-layout",
"bind ! break-pane",
"bind '\"' split-window",
"bind '#' list-buffers",
"bind '$' command-prompt -I'#S' \"rename-session -- '%%'\"",
"bind % split-window -h",
"bind & confirm-before -p\"kill-window #W? (y/n)\" kill-window",
"bind \"'\" command-prompt -pindex \"select-window -t ':%%'\"",
"bind ( switch-client -p",
"bind ) switch-client -n",
"bind , command-prompt -I'#W' \"rename-window -- '%%'\"",
"bind - delete-buffer",
"bind . command-prompt \"move-window -t '%%'\"",
"bind 0 select-window -t:=0",
"bind 1 select-window -t:=1",
"bind 2 select-window -t:=2",
"bind 3 select-window -t:=3",
"bind 4 select-window -t:=4",
"bind 5 select-window -t:=5",
"bind 6 select-window -t:=6",
"bind 7 select-window -t:=7",
"bind 8 select-window -t:=8",
"bind 9 select-window -t:=9",
"bind : command-prompt",
"bind \\; last-pane",
"bind = choose-buffer -Z",
"bind ? list-keys",
"bind D choose-client -Z",
"bind E select-layout -E",
"bind L switch-client -l",
"bind M select-pane -M",
"bind [ copy-mode",
"bind ] paste-buffer",
"bind c new-window",
"bind d detach-client",
"bind f command-prompt \"find-window -Z -- '%%'\"",
"bind i display-message",
"bind l last-window",
"bind m select-pane -m",
"bind n next-window",
"bind o select-pane -t:.+",
"bind p previous-window",
"bind q display-panes",
"bind r refresh-client",
"bind s choose-tree -Zs",
"bind t clock-mode",
"bind w choose-tree -Zw",
"bind x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane",
"bind z resize-pane -Z",
"bind '{' swap-pane -U",
"bind '}' swap-pane -D",
"bind '~' show-messages",
"bind PPage copy-mode -u",
"bind -r Up select-pane -U",
"bind -r Down select-pane -D",
"bind -r Left select-pane -L",
"bind -r Right select-pane -R",
"bind M-1 select-layout even-horizontal",
"bind M-2 select-layout even-vertical",
"bind M-3 select-layout main-horizontal",
"bind M-4 select-layout main-vertical",
"bind M-5 select-layout tiled",
"bind M-n next-window -a",
"bind M-o rotate-window -D",
"bind M-p previous-window -a",
"bind -r S-Up refresh-client -U 10",
"bind -r S-Down refresh-client -D 10",
"bind -r S-Left refresh-client -L 10",
"bind -r S-Right refresh-client -R 10",
"bind -r DC refresh-client -c",
"bind -r M-Up resize-pane -U 5",
"bind -r M-Down resize-pane -D 5",
"bind -r M-Left resize-pane -L 5",
"bind -r M-Right resize-pane -R 5",
"bind -r C-Up resize-pane -U",
"bind -r C-Down resize-pane -D",
"bind -r C-Left resize-pane -L",
"bind -r C-Right resize-pane -R",
"bind -N 'Send the prefix key' C-b send-prefix",
"bind -N 'Rotate through the panes' C-o rotate-window",
"bind -N 'Suspend the current client' C-z suspend-client",
"bind -N 'Select next layout' Space next-layout",
"bind -N 'Break pane to a new window' ! break-pane",
"bind -N 'Split window vertically' '\"' split-window",
"bind -N 'List all paste buffers' '#' list-buffers",
"bind -N 'Rename current session' '$' command-prompt -I'#S' \"rename-session -- '%%'\"",
"bind -N 'Split window horizontally' % split-window -h",
"bind -N 'Kill current window' & confirm-before -p\"kill-window #W? (y/n)\" kill-window",
"bind -N 'Prompt for window index to select' \"'\" command-prompt -pindex \"select-window -t ':%%'\"",
"bind -N 'Switch to previous client' ( switch-client -p",
"bind -N 'Switch to next client' ) switch-client -n",
"bind -N 'Rename current window' , command-prompt -I'#W' \"rename-window -- '%%'\"",
"bind -N 'Delete the most recent paste buffer' - delete-buffer",
"bind -N 'Move the current window' . command-prompt \"move-window -t '%%'\"",
"bind -N 'Describe key binding' '/' command-prompt -kpkey 'list-keys -1N \"%%%\"'",
"bind -N 'Select window 0' 0 select-window -t:=0",
"bind -N 'Select window 1' 1 select-window -t:=1",
"bind -N 'Select window 2' 2 select-window -t:=2",
"bind -N 'Select window 3' 3 select-window -t:=3",
"bind -N 'Select window 4' 4 select-window -t:=4",
"bind -N 'Select window 5' 5 select-window -t:=5",
"bind -N 'Select window 6' 6 select-window -t:=6",
"bind -N 'Select window 7' 7 select-window -t:=7",
"bind -N 'Select window 8' 8 select-window -t:=8",
"bind -N 'Select window 9' 9 select-window -t:=9",
"bind -N 'Prompt for a command' : command-prompt",
"bind -N 'Move to the previously active pane' \\; last-pane",
"bind -N 'Choose a paste buffer from a list' = choose-buffer -Z",
"bind -N 'List key bindings' ? list-keys -N",
"bind -N 'Choose a client from a list' D choose-client -Z",
"bind -N 'Spread panes out evenly' E select-layout -E",
"bind -N 'Switch to the last client' L switch-client -l",
"bind -N 'Clear the marked pane' M select-pane -M",
"bind -N 'Enter copy mode' [ copy-mode",
"bind -N 'Paste the most recent paste buffer' ] paste-buffer",
"bind -N 'Create a new window' c new-window",
"bind -N 'Detach the current client' d detach-client",
"bind -N 'Search for a pane' f command-prompt \"find-window -Z -- '%%'\"",
"bind -N 'Display window information' i display-message",
"bind -N 'Select the previously current window' l last-window",
"bind -N 'Toggle the marked pane' m select-pane -m",
"bind -N 'Select the next window' n next-window",
"bind -N 'Select the next pane' o select-pane -t:.+",
"bind -N 'Select the previous pane' p previous-window",
"bind -N 'Display pane numbers' q display-panes",
"bind -N 'Redraw the current client' r refresh-client",
"bind -N 'Choose a session from a list' s choose-tree -Zs",
"bind -N 'Show a clock' t clock-mode",
"bind -N 'Choose a window from a list' w choose-tree -Zw",
"bind -N 'Kill the active pane' x confirm-before -p\"kill-pane #P? (y/n)\" kill-pane",
"bind -N 'Zoom the active pane' z resize-pane -Z",
"bind -N 'Swap the active pane with the pane above' '{' swap-pane -U",
"bind -N 'Swap the active pane with the pane below' '}' swap-pane -D",
"bind -N 'Show messages' '~' show-messages",
"bind -N 'Enter copy mode and scroll up' PPage copy-mode -u",
"bind -N 'Select the pane above the active pane' -r Up select-pane -U",
"bind -N 'Select the pane below the active pane' -r Down select-pane -D",
"bind -N 'Select the pane to the left of the active pane' -r Left select-pane -L",
"bind -N 'Select the pane to the right of the active pane' -r Right select-pane -R",
"bind -N 'Set the even-horizontal layout' M-1 select-layout even-horizontal",
"bind -N 'Set the even-vertical layout' M-2 select-layout even-vertical",
"bind -N 'Set the main-horizontal layout' M-3 select-layout main-horizontal",
"bind -N 'Set the main-vertical layout' M-4 select-layout main-vertical",
"bind -N 'Select the tiled layout' M-5 select-layout tiled",
"bind -N 'Select the next window with an alert' M-n next-window -a",
"bind -N 'Rotate through the panes in reverse' M-o rotate-window -D",
"bind -N 'Select the previous window with an alert' M-p previous-window -a",
"bind -N 'Move the visible part of the window up' -r S-Up refresh-client -U 10",
"bind -N 'Move the visible part of the window down' -r S-Down refresh-client -D 10",
"bind -N 'Move the visible part of the window left' -r S-Left refresh-client -L 10",
"bind -N 'Move the visible part of the window right' -r S-Right refresh-client -R 10",
"bind -N 'Reset so the visible part of the window follows the cursor' -r DC refresh-client -c",
"bind -N 'Resize the pane up by 5' -r M-Up resize-pane -U 5",
"bind -N 'Resize the pane down by 5' -r M-Down resize-pane -D 5",
"bind -N 'Resize the pane left by 5' -r M-Left resize-pane -L 5",
"bind -N 'Resize the pane right by 5' -r M-Right resize-pane -R 5",
"bind -N 'Resize the pane up' -r C-Up resize-pane -U",
"bind -N 'Resize the pane down' -r C-Down resize-pane -D",
"bind -N 'Resize the pane left' -r C-Left resize-pane -L",
"bind -N 'Resize the pane right' -r C-Right resize-pane -R",
"bind -n MouseDown1Pane select-pane -t=\\; send-keys -M",
"bind -n MouseDrag1Border resize-pane -M",

View File

@ -915,11 +915,17 @@ status_prompt_key(struct client *c, key_code key)
{
struct options *oo = c->session->options;
char *s, *cp, word[64], prefix = '=';
const char *histstr, *ws = NULL;
const char *histstr, *ws = NULL, *keystring;
size_t size, n, off, idx, used;
struct utf8_data tmp, *first, *last, *ud;
int keys;
if (c->prompt_flags & PROMPT_KEY) {
keystring = key_string_lookup_key(key);
c->prompt_inputcb(c, c->prompt_data, keystring, 1);
status_prompt_clear(c);
return (0);
}
size = utf8_strlen(c->prompt_buffer);
if (c->prompt_flags & PROMPT_NUMERIC) {

48
tmux.1
View File

@ -551,7 +551,7 @@ Braces may be enclosed inside braces, for example:
.Bd -literal -offset indent
bind x if-shell "true" {
if-shell "true" {
display "true!"
display "true!"
}
}
.Ed
@ -1335,7 +1335,8 @@ is used,
option will not be applied.
.Pp
.Fl T
sets the client's key table; the next key from the client will be interpreted from
sets the client's key table; the next key from the client will be interpreted
from
.Ar key-table .
This may be used to configure multiple prefix keys, or to bind commands to
sequences of keys.
@ -2613,6 +2614,7 @@ Commands related to key bindings are as follows:
.Bl -tag -width Ds
.It Xo Ic bind-key
.Op Fl nr
.Op Fl N Ar note
.Op Fl T Ar key-table
.Ar key Ar command Op Ar arguments
.Xc
@ -2660,22 +2662,46 @@ The
flag indicates this key may repeat, see the
.Ic repeat-time
option.
.Fl N
attaches a note to the key (shown with
.Ic list-keys
.Fl N ) .
.Pp
To view the default bindings and possible commands, see the
.Ic list-keys
command.
.It Xo Ic list-keys
.Op Fl T Ar key-table
.Op Fl 1N
.Op Fl P Ar prefix-string Fl T Ar key-table
.Op key
.Xc
.D1 (alias: Ic lsk )
List all key bindings.
Without
.Fl T
all key tables are printed.
By default this shows all keys or any bindings for
.Ar key
in the syntax of the
.Ic bind-key
command.
.Fl N
instead show keys and attached notes, i
.Ar key-table
if given or in the
.Em root
and
.Em prefix
key tables by default.
.Fl P
specifies a prefix to print before each key.
With
.Fl 1
only the first matching key and note is shown.
.Pp
Without
.Fl N ,
.Fl T
only
.Ar key-table .
prints only keys in
.Ar key-table ,
otherwise all key tables are printed.
.It Xo Ic send-keys
.Op Fl FHlMRX
.Op Fl N Ar repeat-count
@ -4693,7 +4719,7 @@ session option.
Commands related to the status line are as follows:
.Bl -tag -width Ds
.It Xo Ic command-prompt
.Op Fl 1Ni
.Op Fl 1ikN
.Op Fl I Ar inputs
.Op Fl p Ar prompts
.Op Fl t Ar target-client
@ -4743,6 +4769,10 @@ but any quotation marks are escaped.
.Fl 1
makes the prompt only accept one key press, in this case the resulting input
is a single character.
.Fl k
is like
.Fl 1
but the key press is translated to a key name.
.Fl N
makes the prompt only accept numeric key presses.
.Fl i

5
tmux.h
View File

@ -1609,6 +1609,7 @@ struct client {
#define PROMPT_NUMERIC 0x2
#define PROMPT_INCREMENTAL 0x4
#define PROMPT_NOFORMAT 0x8
#define PROMPT_KEY 0x10
int prompt_flags;
struct session *session;
@ -1636,6 +1637,7 @@ TAILQ_HEAD(clients, client);
struct key_binding {
key_code key;
struct cmd_list *cmdlist;
const char *note;
int flags;
#define KEY_BINDING_REPEAT 0x1
@ -2147,7 +2149,8 @@ void key_bindings_unref_table(struct key_table *);
struct key_binding *key_bindings_get(struct key_table *, key_code);
struct key_binding *key_bindings_first(struct key_table *);
struct key_binding *key_bindings_next(struct key_table *, struct key_binding *);
void key_bindings_add(const char *, key_code, int, struct cmd_list *);
void key_bindings_add(const char *, key_code, const char *, int,
struct cmd_list *);
void key_bindings_remove(const char *, key_code);
void key_bindings_remove_table(const char *);
void key_bindings_init(void);