From 2e39b621c9b29b58b4bfe761989b14f0f13d07b6 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 27 Jan 2020 08:23:42 +0000
Subject: [PATCH 1/3] Change so that assignments may be specified alone - a
 command isn't required. GitHub issue 2062.

---
 cmd-parse.y | 34 ++++++++++++++++++++++++----------
 1 file changed, 24 insertions(+), 10 deletions(-)

diff --git a/cmd-parse.y b/cmd-parse.y
index 97b50f57..41bcdaa8 100644
--- a/cmd-parse.y
+++ b/cmd-parse.y
@@ -133,7 +133,12 @@ statements	: statement '\n'
 			free($2);
 		}
 
-statement	: condition
+statement	: /* empty */
+		{
+			$$ = xmalloc (sizeof *$$);
+			TAILQ_INIT($$);
+		}
+		| condition
 		{
 			struct cmd_parse_state	*ps = &parse_state;
 
@@ -144,11 +149,6 @@ statement	: condition
 				cmd_parse_free_commands($1);
 			}
 		}
-		| assignment
-		{
-			$$ = xmalloc (sizeof *$$);
-			TAILQ_INIT($$);
-		}
 		| commands
 		{
 			struct cmd_parse_state	*ps = &parse_state;
@@ -194,8 +194,10 @@ expanded	: format
 			free($1);
 		}
 
-assignment	: /* empty */
-		| EQUALS
+optional_assignment	: /* empty */
+			| assignment
+
+assignment	: EQUALS
 		{
 			struct cmd_parse_state	*ps = &parse_state;
 			int			 flags = ps->input->flags;
@@ -372,7 +374,15 @@ commands	: command
 			$$ = $1;
 		}
 
-command		: assignment TOKEN
+command		: assignment
+		{
+			struct cmd_parse_state	*ps = &parse_state;
+
+			$$ = xcalloc(1, sizeof *$$);
+			$$->name = NULL;
+			$$->line = ps->input->line;
+		}
+		| optional_assignment TOKEN
 		{
 			struct cmd_parse_state	*ps = &parse_state;
 
@@ -381,7 +391,7 @@ command		: assignment TOKEN
 			$$->line = ps->input->line;
 
 		}
-		| assignment TOKEN arguments
+		| optional_assignment TOKEN arguments
 		{
 			struct cmd_parse_state	*ps = &parse_state;
 
@@ -631,6 +641,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
 	 * command list.
 	 */
 	TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
+		if (cmd->name == NULL)
+			continue;
 		alias = cmd_get_alias(cmd->name);
 		if (alias == NULL)
 			continue;
@@ -676,6 +688,8 @@ cmd_parse_build_commands(struct cmd_parse_commands *cmds,
 	 */
 	result = cmd_list_new();
 	TAILQ_FOREACH(cmd, cmds, entry) {
+		if (cmd->name == NULL)
+			continue;
 		log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
 		cmd_log_argv(cmd->argc, cmd->argv, __func__);
 

From d0b8d036be97efc885f879aa63ce9bbb0efd8222 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 27 Jan 2020 08:53:13 +0000
Subject: [PATCH 2/3] 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.
---
 cmd-bind-key.c       |  12 ++--
 cmd-command-prompt.c |   6 +-
 cmd-list-keys.c      | 150 ++++++++++++++++++++++++++++++++++++--
 key-bindings.c       | 168 ++++++++++++++++++++++---------------------
 status.c             |   8 ++-
 tmux.1               |  48 ++++++++++---
 tmux.h               |   5 +-
 7 files changed, 292 insertions(+), 105 deletions(-)

diff --git a/cmd-bind-key.c b/cmd-bind-key.c
index 2af15d29..27f75dd0 100644
--- a/cmd-bind-key.c
+++ b/cmd-bind-key.c
@@ -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);
 }
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 603ddb0a..9f0ea19f 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -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);
diff --git a/cmd-list-keys.c b/cmd-list-keys.c
index 8636b70a..e9e75a72 100644
--- a/cmd-list-keys.c
+++ b/cmd-list-keys.c
@@ -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;
diff --git a/key-bindings.c b/key-bindings.c
index 94110a49..4387c011 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -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",
diff --git a/status.c b/status.c
index 0f96f0d3..33f6c47a 100644
--- a/status.c
+++ b/status.c
@@ -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) {
diff --git a/tmux.1 b/tmux.1
index eb5087be..4a618186 100644
--- a/tmux.1
+++ b/tmux.1
@@ -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
diff --git a/tmux.h b/tmux.h
index ac555be5..093f17a4 100644
--- a/tmux.h
+++ b/tmux.h
@@ -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);

From 2c38e01b548553aa162f9f126147b5ed64fd1700 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 27 Jan 2020 09:04:47 +0000
Subject: [PATCH 3/3] Expand description of start-server.

---
 tmux.1 | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/tmux.1 b/tmux.1
index 4a618186..11bdf4bc 100644
--- a/tmux.1
+++ b/tmux.1
@@ -1287,6 +1287,17 @@ shows the parsed commands and line numbers if possible.
 Start the
 .Nm
 server, if not already running, without creating any sessions.
+.Pp
+Note that as by default the
+.Nm
+server will exit with no sessions, this is only useful if a session is created in
+.Pa ~/.tmux.conf ,
+.Ic exit-empty
+is turned off, or another command is run as part of the same command sequence.
+For example:
+.Bd -literal -offset indent
+$ tmux start \\; show -g
+.Ed
 .It Xo Ic suspend-client
 .Op Fl t Ar target-client
 .Xc