From e8224fb0d123ca9042b0703a35e1a33f0787d327 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Fri, 12 Mar 2021 08:39:17 +0000
Subject: [PATCH 01/17] Fix so tmux correctly sends the cvvis (cursor very
 visible) capability rather than sending it and then immediately undoing it
 with cnorm. Also turn it off when the cursor shape is changed like xterm.

---
 screen.c |  4 +++-
 tty.c    | 38 +++++++++++++++++++++++++-------------
 2 files changed, 28 insertions(+), 14 deletions(-)

diff --git a/screen.c b/screen.c
index 2cdbec62..97ccdeec 100644
--- a/screen.c
+++ b/screen.c
@@ -154,8 +154,10 @@ screen_reset_tabs(struct screen *s)
 void
 screen_set_cursor_style(struct screen *s, u_int style)
 {
-	if (style <= 6)
+	if (style <= 6) {
 		s->cstyle = style;
+		s->mode &= ~MODE_BLINKING;
+	}
 }
 
 /* Set screen cursor colour. */
diff --git a/tty.c b/tty.c
index 399bbae8..e8a8cbaa 100644
--- a/tty.c
+++ b/tty.c
@@ -670,19 +670,10 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s)
 	if (changed != 0)
 		log_debug("%s: update mode %x to %x", c->name, tty->mode, mode);
 
-	if (changed & MODE_BLINKING) {
-		if (tty_term_has(tty->term, TTYC_CVVIS))
-			tty_putcode(tty, TTYC_CVVIS);
-		else
-			tty_putcode(tty, TTYC_CNORM);
-		changed |= MODE_CURSOR;
-	}
-	if (changed & MODE_CURSOR) {
-		if (mode & MODE_CURSOR)
-			tty_putcode(tty, TTYC_CNORM);
-		else
-			tty_putcode(tty, TTYC_CIVIS);
-	}
+	/*
+	 * The cursor blinking flag can be reset by setting the cursor style, so
+	 * set the style first.
+	 */
 	if (s != NULL && tty->cstyle != s->cstyle) {
 		if (tty_term_has(tty->term, TTYC_SS)) {
 			if (s->cstyle == 0 && tty_term_has(tty->term, TTYC_SE))
@@ -691,7 +682,28 @@ tty_update_mode(struct tty *tty, int mode, struct screen *s)
 				tty_putcode1(tty, TTYC_SS, s->cstyle);
 		}
 		tty->cstyle = s->cstyle;
+		changed |= (MODE_CURSOR|MODE_BLINKING);
 	}
+
+	/*
+	 * Cursor invisible (RM ?25) overrides cursor blinking (SM ?12 or RM
+	 * 34), and we need to be careful not send cnorm after cvvis since it
+	 * can undo it.
+	 */
+	if (changed & (MODE_CURSOR|MODE_BLINKING)) {
+		log_debug("%s: cursor %s, %sblinking", __func__,
+		    (mode & MODE_CURSOR) ? "on" : "off",
+		    (mode & MODE_BLINKING) ? "" : "not ");
+		if (~mode & MODE_CURSOR)
+			tty_putcode(tty, TTYC_CIVIS);
+		else if (mode & MODE_BLINKING) {
+			tty_putcode(tty, TTYC_CNORM);
+			if (tty_term_has(tty->term, TTYC_CVVIS))
+				tty_putcode(tty, TTYC_CVVIS);
+		} else
+			tty_putcode(tty, TTYC_CNORM);
+	}
+
 	if ((changed & ALL_MOUSE_MODES) &&
 	    tty_term_has(tty->term, TTYC_KMOUS)) {
 		/*

From 9017af23556abdc31ba37c925adf6e0f1bb12671 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 15 Mar 2021 13:06:33 +0000
Subject: [PATCH 02/17] Do not crash if there is no item to show command error,
 from Anindya Mukherjee.

---
 cmd-run-shell.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/cmd-run-shell.c b/cmd-run-shell.c
index 4f30d05d..73ed79f4 100644
--- a/cmd-run-shell.c
+++ b/cmd-run-shell.c
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/wait.h>
 
+#include <ctype.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -190,8 +191,12 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
 			    &error);
 		}
 		if (status == CMD_PARSE_ERROR) {
-		       cmdq_error(cdata->item, "%s", error);
-		       free(error);
+			if (cdata->item == NULL) {
+				*error = toupper((u_char)*error);
+				status_message_set(c, -1, 1, "%s", error);
+			} else
+				cmdq_error(cdata->item, "%s", error);
+			free(error);
 		}
 	}
 

From 8b800b41c98c37a270cea61e57d1f2702fd75293 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Tue, 16 Mar 2021 09:14:58 +0000
Subject: [PATCH 03/17] Add client-detached notification in control mode, from
 Mohsin Kaleem.

---
 control-notify.c | 11 +++++++++++
 notify.c         |  2 ++
 tmux.1           |  2 ++
 tmux.h           |  1 +
 4 files changed, 16 insertions(+)

diff --git a/control-notify.c b/control-notify.c
index cc706ac2..6ff0e436 100644
--- a/control-notify.c
+++ b/control-notify.c
@@ -171,6 +171,17 @@ control_notify_client_session_changed(struct client *cc)
 	}
 }
 
+void
+control_notify_client_detached(struct client *cc)
+{
+	struct client	*c;
+
+	TAILQ_FOREACH(c, &clients, entry) {
+		if (CONTROL_SHOULD_NOTIFY_CLIENT(c))
+			control_write(c, "%%client-detached %s", cc->name);
+	}
+}
+
 void
 control_notify_session_renamed(struct session *s)
 {
diff --git a/notify.c b/notify.c
index a50eb264..c645657f 100644
--- a/notify.c
+++ b/notify.c
@@ -126,6 +126,8 @@ notify_callback(struct cmdq_item *item, void *data)
 		control_notify_window_renamed(ne->window);
 	if (strcmp(ne->name, "client-session-changed") == 0)
 		control_notify_client_session_changed(ne->client);
+	if (strcmp(ne->name, "client-detached") == 0)
+		control_notify_client_detached(ne->client);
 	if (strcmp(ne->name, "session-renamed") == 0)
 		control_notify_session_renamed(ne->session);
 	if (strcmp(ne->name, "session-created") == 0)
diff --git a/tmux.1 b/tmux.1
index 5c516fdb..ecbbc9fc 100644
--- a/tmux.1
+++ b/tmux.1
@@ -6107,6 +6107,8 @@ A notification will never occur inside an output block.
 .Pp
 The following notifications are defined:
 .Bl -tag -width Ds
+.It Ic %client-detached Ar client
+The client has detached.
 .It Ic %client-session-changed Ar client session-id name
 The client is now attached to the session with ID
 .Ar session-id ,
diff --git a/tmux.h b/tmux.h
index 1fc27b53..e50f9fe7 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2945,6 +2945,7 @@ void	control_notify_window_unlinked(struct session *, struct window *);
 void	control_notify_window_linked(struct session *, struct window *);
 void	control_notify_window_renamed(struct window *);
 void	control_notify_client_session_changed(struct client *);
+void	control_notify_client_detached(struct client *);
 void	control_notify_session_renamed(struct session *);
 void	control_notify_session_created(struct session *);
 void	control_notify_session_closed(struct session *);

From 4208641de7045299a10f51209bec4623cbea6744 Mon Sep 17 00:00:00 2001
From: Nicholas Marriott <nicholas.marriott@gmail.com>
Date: Sun, 28 Mar 2021 10:16:17 +0100
Subject: [PATCH 04/17] Remove queue.h, from Simon Holesch.

---
 proc.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/proc.c b/proc.c
index a1a2b36a..958a9483 100644
--- a/proc.c
+++ b/proc.c
@@ -17,7 +17,6 @@
  */
 
 #include <sys/types.h>
-#include <sys/queue.h>
 #include <sys/socket.h>
 #include <sys/uio.h>
 #include <sys/utsname.h>

From a4b9b5a1e5326b4ac4dfb439627b735d831b8992 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Wed, 31 Mar 2021 08:37:48 +0000
Subject: [PATCH 05/17] Do not exit if cannot write to normal log file, GitHub
 issue 2630.

---
 log.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/log.c b/log.c
index 44b24c67..3387ab0c 100644
--- a/log.c
+++ b/log.c
@@ -111,15 +111,16 @@ log_vwrite(const char *msg, va_list ap)
 		return;
 
 	if (vasprintf(&fmt, msg, ap) == -1)
-		exit(1);
-	if (stravis(&out, fmt, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL) == -1)
-		exit(1);
+		return;
+	if (stravis(&out, fmt, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL) == -1) {
+		free(fmt);
+		return;
+	}
 
 	gettimeofday(&tv, NULL);
 	if (fprintf(log_file, "%lld.%06d %s\n", (long long)tv.tv_sec,
-	    (int)tv.tv_usec, out) == -1)
-		exit(1);
-	fflush(log_file);
+	    (int)tv.tv_usec, out) != -1)
+		fflush(log_file);
 
 	free(out);
 	free(fmt);

From 6c98f222e90468106533d5c78a2acb0a5f5876d7 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Thu, 1 Apr 2021 06:37:46 +0000
Subject: [PATCH 06/17] Missing commas, from Vipul Kumar.

---
 tmux.1 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/tmux.1 b/tmux.1
index ecbbc9fc..d76cadd3 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3149,8 +3149,8 @@ The appearance and behaviour of
 may be modified by changing the value of various options.
 There are four types of option:
 .Em server options ,
-.Em session options
-.Em window options
+.Em session options ,
+.Em window options ,
 and
 .Em pane options .
 .Pp

From 28cd956729c13f8f66e26d438592df9c9930e7b8 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Thu, 1 Apr 2021 06:46:12 +0000
Subject: [PATCH 07/17] Change search-again with vi keys to work like actual
 vi(1), also some other fixes. From Aaron Jensen with help from Anindya
 Mukherjee.

---
 window-copy.c | 211 ++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 161 insertions(+), 50 deletions(-)

diff --git a/window-copy.c b/window-copy.c
index 3fc7ad3e..6276c8f9 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -64,6 +64,8 @@ static int	window_copy_search_rl(struct grid *, struct grid *, u_int *,
 static int	window_copy_last_regex(struct grid *, u_int, u_int, u_int,
 		    u_int, u_int *, u_int *, const char *, const regex_t *,
 		    int);
+static int	window_copy_search_mark_at(struct window_copy_mode_data *,
+		    u_int, u_int, u_int *);
 static char    *window_copy_stringify(struct grid *, u_int, u_int, u_int,
 		    char *, u_int *);
 static void	window_copy_cstrtocellpos(struct grid *, u_int, u_int *,
@@ -71,16 +73,15 @@ static void	window_copy_cstrtocellpos(struct grid *, u_int, u_int *,
 static int	window_copy_search_marks(struct window_mode_entry *,
 		    struct screen *, int, int);
 static void	window_copy_clear_marks(struct window_mode_entry *);
-static void	window_copy_move_left(struct screen *, u_int *, u_int *, int);
 static int	window_copy_is_lowercase(const char *);
 static void	window_copy_search_back_overlap(struct grid *, regex_t *,
 		    u_int *, u_int *, u_int *, u_int);
 static int	window_copy_search_jump(struct window_mode_entry *,
 		    struct grid *, struct grid *, u_int, u_int, u_int, int, int,
-		    int, int, u_int *);
-static int	window_copy_search(struct window_mode_entry *, int, int, int);
-static int	window_copy_search_up(struct window_mode_entry *, int, int);
-static int	window_copy_search_down(struct window_mode_entry *, int, int);
+		    int, int);
+static int	window_copy_search(struct window_mode_entry *, int, int);
+static int	window_copy_search_up(struct window_mode_entry *, int);
+static int	window_copy_search_down(struct window_mode_entry *, int);
 static void	window_copy_goto_line(struct window_mode_entry *, const char *);
 static void	window_copy_update_cursor(struct window_mode_entry *, u_int,
 		    u_int);
@@ -275,6 +276,7 @@ struct window_copy_mode_data {
 	int		 showmark;
 
 	int		 searchtype;
+	int		 searchdirection;
 	int		 searchregex;
 	char		*searchstr;
 	u_char		*searchmark;
@@ -1718,10 +1720,10 @@ window_copy_cmd_search_again(struct window_copy_cmd_state *cs)
 
 	if (data->searchtype == WINDOW_COPY_SEARCHUP) {
 		for (; np != 0; np--)
-			window_copy_search_up(wme, data->searchregex, 1);
+			window_copy_search_up(wme, data->searchregex);
 	} else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
 		for (; np != 0; np--)
-			window_copy_search_down(wme, data->searchregex, 1);
+			window_copy_search_down(wme, data->searchregex);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -1735,10 +1737,10 @@ window_copy_cmd_search_reverse(struct window_copy_cmd_state *cs)
 
 	if (data->searchtype == WINDOW_COPY_SEARCHUP) {
 		for (; np != 0; np--)
-			window_copy_search_down(wme, data->searchregex, 1);
+			window_copy_search_down(wme, data->searchregex);
 	} else if (data->searchtype == WINDOW_COPY_SEARCHDOWN) {
 		for (; np != 0; np--)
-			window_copy_search_up(wme, data->searchregex, 1);
+			window_copy_search_up(wme, data->searchregex);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -2042,7 +2044,7 @@ window_copy_cmd_search_backward(struct window_copy_cmd_state *cs)
 		data->searchregex = 1;
 		data->timeout = 0;
 		for (; np != 0; np--)
-			window_copy_search_up(wme, 1, 0);
+			window_copy_search_up(wme, 1);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -2062,7 +2064,7 @@ window_copy_cmd_search_backward_text(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		data->timeout = 0;
 		for (; np != 0; np--)
-			window_copy_search_up(wme, 0, 0);
+			window_copy_search_up(wme, 0);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -2082,7 +2084,7 @@ window_copy_cmd_search_forward(struct window_copy_cmd_state *cs)
 		data->searchregex = 1;
 		data->timeout = 0;
 		for (; np != 0; np--)
-			window_copy_search_down(wme, 1, 0);
+			window_copy_search_down(wme, 1);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -2102,7 +2104,7 @@ window_copy_cmd_search_forward_text(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		data->timeout = 0;
 		for (; np != 0; np--)
-			window_copy_search_down(wme, 0, 0);
+			window_copy_search_down(wme, 0);
 	}
 	return (WINDOW_COPY_CMD_NOTHING);
 }
@@ -2143,7 +2145,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		free(data->searchstr);
 		data->searchstr = xstrdup(argument);
-		if (!window_copy_search_up(wme, 0, 1)) {
+		if (!window_copy_search_up(wme, 0)) {
 			window_copy_clear_marks(wme);
 			return (WINDOW_COPY_CMD_REDRAW);
 		}
@@ -2153,7 +2155,7 @@ window_copy_cmd_search_backward_incremental(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		free(data->searchstr);
 		data->searchstr = xstrdup(argument);
-		if (!window_copy_search_down(wme, 0, 0)) {
+		if (!window_copy_search_down(wme, 0)) {
 			window_copy_clear_marks(wme);
 			return (WINDOW_COPY_CMD_REDRAW);
 		}
@@ -2198,7 +2200,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		free(data->searchstr);
 		data->searchstr = xstrdup(argument);
-		if (!window_copy_search_down(wme, 0, 1)) {
+		if (!window_copy_search_down(wme, 0)) {
 			window_copy_clear_marks(wme);
 			return (WINDOW_COPY_CMD_REDRAW);
 		}
@@ -2208,7 +2210,7 @@ window_copy_cmd_search_forward_incremental(struct window_copy_cmd_state *cs)
 		data->searchregex = 0;
 		free(data->searchstr);
 		data->searchstr = xstrdup(argument);
-		if (!window_copy_search_up(wme, 0, 1)) {
+		if (!window_copy_search_up(wme, 0)) {
 			window_copy_clear_marks(wme);
 			return (WINDOW_COPY_CMD_REDRAW);
 		}
@@ -2917,6 +2919,23 @@ window_copy_move_left(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
 		*fx = *fx - 1;
 }
 
+static void
+window_copy_move_right(struct screen *s, u_int *fx, u_int *fy, int wrapflag)
+{
+	if (*fx == screen_size_x(s) - 1) { /* right */
+		if (*fy == screen_hsize(s) + screen_size_y(s) - 1) { /* bottom */
+			if (wrapflag) {
+				*fx = 0;
+				*fy = 0;
+			}
+			return;
+		}
+		*fx = 0;
+		*fy = *fy + 1;
+	} else
+		*fx = *fx + 1;
+}
+
 static int
 window_copy_is_lowercase(const char *ptr)
 {
@@ -2979,7 +2998,7 @@ window_copy_search_back_overlap(struct grid *gd, regex_t *preg, u_int *ppx,
 static int
 window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd,
     struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap,
-    int direction, int regex, u_int *foundlen)
+    int direction, int regex)
 {
 	u_int	 i, px, sx, ssize = 1;
 	int	 found = 0, cflags = REG_EXTENDED;
@@ -3004,20 +3023,15 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd,
 			if (regex) {
 				found = window_copy_search_lr_regex(gd,
 				    &px, &sx, i, fx, gd->sx, &reg);
-				if (found)
-					*foundlen = sx;
 			} else {
 				found = window_copy_search_lr(gd, sgd,
 				    &px, i, fx, gd->sx, cis);
-				if (found)
-					*foundlen = sgd->sx;
 			}
 			if (found)
 				break;
 			fx = 0;
 		}
 	} else {
-		*foundlen = 0;
 		for (i = fy + 1; endline < i; i--) {
 			if (regex) {
 				found = window_copy_search_rl_regex(gd,
@@ -3048,18 +3062,40 @@ window_copy_search_jump(struct window_mode_entry *wme, struct grid *gd,
 		return (window_copy_search_jump(wme, gd, sgd,
 		    direction ? 0 : gd->sx - 1,
 		    direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0,
-		    direction, regex, foundlen));
+		    direction, regex));
 	}
 	return (0);
 }
 
+static void
+window_copy_move_after_search_mark(struct window_copy_mode_data *data,
+    u_int *fx, u_int *fy, int wrapflag)
+{
+	struct screen  *s = data->backing;
+	u_int		at, start;
+
+	if (window_copy_search_mark_at(data, *fx, *fy, &start) == 0 &&
+	    data->searchmark[start] != 0) {
+		while (window_copy_search_mark_at(data, *fx, *fy, &at) == 0) {
+			if (data->searchmark[at] != data->searchmark[start])
+				break;
+			/* Stop if not wrapping and at the end of the grid. */
+			if (!wrapflag &&
+			    *fx == screen_size_x(s) - 1 &&
+			    *fy == screen_hsize(s) + screen_size_y(s) - 1)
+				break;
+
+			window_copy_move_right(s, fx, fy, wrapflag);
+		}
+	}
+}
+
 /*
  * Search in for text searchstr. If direction is 0 then search up, otherwise
  * down.
  */
 static int
-window_copy_search(struct window_mode_entry *wme, int direction, int regex,
-    int again)
+window_copy_search(struct window_mode_entry *wme, int direction, int regex)
 {
 	struct window_pane		*wp = wme->wp;
 	struct window_copy_mode_data	*data = wme->data;
@@ -3067,12 +3103,15 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex,
 	struct screen_write_ctx		 ctx;
 	struct grid			*gd = s->grid;
 	const char			*str = data->searchstr;
-	u_int				 fx, fy, endline, i, foundlen;
-	int				 wrapflag, cis, found, visible_only;
+	u_int				 at, endline, fx, fy, start;
+	int				 cis, found, keys, visible_only;
+	int				 wrapflag;
 
 	if (regex && str[strcspn(str, "^$*+()?[].\\")] == '\0')
 		regex = 0;
 
+	data->searchdirection = direction;
+
 	if (data->timeout)
 		return (0);
 
@@ -3097,27 +3136,94 @@ window_copy_search(struct window_mode_entry *wme, int direction, int regex,
 	wrapflag = options_get_number(wp->window->options, "wrap-search");
 	cis = window_copy_is_lowercase(str);
 
-	if (direction)
+	keys = options_get_number(wp->window->options, "mode-keys");
+
+	if (direction) {
+		/*
+		 * Behave according to mode-keys. If it is emacs, search forward
+		 * leaves the cursor after the match. If it is vi, the cursor
+		 * remains at the beginning of the match, regardless of
+		 * direction, which means that we need to start the next search
+		 * after the term the cursor is currently on when searching
+		 * forward.
+		 */
+		if (keys == MODEKEY_VI) {
+			if (data->searchmark != NULL)
+				window_copy_move_after_search_mark(data, &fx,
+				    &fy, wrapflag);
+			else {
+				/*
+				 * When there are no search marks, start the
+				 * search after the current cursor position.
+				 */
+				window_copy_move_right(s, &fx, &fy, wrapflag);
+			}
+		}
 		endline = gd->hsize + gd->sy - 1;
+	}
 	else {
-		if (again)
-			window_copy_move_left(s, &fx, &fy, wrapflag);
+		window_copy_move_left(s, &fx, &fy, wrapflag);
 		endline = 0;
 	}
 
 	found = window_copy_search_jump(wme, gd, ss.grid, fx, fy, endline, cis,
-	    wrapflag, direction, regex, &foundlen);
+	    wrapflag, direction, regex);
 	if (found) {
 		window_copy_search_marks(wme, &ss, regex, visible_only);
-		if (foundlen != 0) {
-			/* Adjust for wrapped lines eating one right. */
-			i = data->cx + foundlen;
-			while (i > gd->sx - 1) {
-				i -= gd->sx;
-				window_copy_cursor_right(wme, 1);
+		fx = data->cx;
+		fy = screen_hsize(data->backing) - data->oy + data->cy;
+
+		/*
+		 * When searching forward, if the cursor is not at the beginning
+		 * of the mark, search again.
+		 */
+		if (direction &&
+		    window_copy_search_mark_at(data, fx, fy, &at) == 0 &&
+		    at > 0 &&
+		    data->searchmark[at] == data->searchmark[at - 1]) {
+			window_copy_move_after_search_mark(data, &fx, &fy,
+			    wrapflag);
+			window_copy_search_jump(wme, gd, ss.grid, fx,
+			    fy, endline, cis, wrapflag, direction,
+			    regex);
+			fx = data->cx;
+			fy = screen_hsize(data->backing) - data->oy + data->cy;
+		}
+
+		if (direction) {
+			/*
+			 * When in Emacs mode, position the cursor just after
+			 * the mark.
+			 */
+			if (keys == MODEKEY_EMACS) {
+				window_copy_move_after_search_mark(data, &fx,
+				    &fy, wrapflag);
+				data->cx = fx;
+				data->cy = fy - screen_hsize(data->backing) +
+				    data-> oy;
+			}
+		}
+		else {
+			/*
+			 * When searching backward, position the cursor at the
+			 * beginning of the mark.
+			 */
+			if (window_copy_search_mark_at(data, fx, fy,
+			        &start) == 0) {
+				while (window_copy_search_mark_at(data, fx, fy,
+				           &at) == 0 &&
+				       data->searchmark[at] ==
+				           data->searchmark[start]) {
+					data->cx = fx;
+					data->cy = fy -
+					    screen_hsize(data->backing) +
+					    data-> oy;
+					if (at == 0)
+						break;
+
+					window_copy_move_left(s, &fx, &fy, 0);
+				}
 			}
-			for (i = 0; i < foundlen; i++)
-				window_copy_cursor_right(wme, 1);
 		}
 	}
 	window_copy_redraw_screen(wme);
@@ -3302,15 +3408,15 @@ window_copy_clear_marks(struct window_mode_entry *wme)
 }
 
 static int
-window_copy_search_up(struct window_mode_entry *wme, int regex, int again)
+window_copy_search_up(struct window_mode_entry *wme, int regex)
 {
-	return (window_copy_search(wme, 0, regex, again));
+	return (window_copy_search(wme, 0, regex));
 }
 
 static int
-window_copy_search_down(struct window_mode_entry *wme, int regex, int again)
+window_copy_search_down(struct window_mode_entry *wme, int regex)
 {
-	return (window_copy_search(wme, 1, regex, again));
+	return (window_copy_search(wme, 1, regex));
 }
 
 static void
@@ -3396,9 +3502,11 @@ window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy,
     struct grid_cell *gc, const struct grid_cell *mgc,
     const struct grid_cell *cgc, const struct grid_cell *mkgc)
 {
+	struct window_pane		*wp = wme->wp;
 	struct window_copy_mode_data	*data = wme->data;
 	u_int				 mark, start, end, cy, cursor, current;
 	int				 inv = 0, found = 0;
+	int				 keys;
 
 	if (data->showmark && fy == data->my) {
 		gc->attr = mkgc->attr;
@@ -3425,13 +3533,16 @@ window_copy_update_style(struct window_mode_entry *wme, u_int fx, u_int fy,
 
 	cy = screen_hsize(data->backing) - data->oy + data->cy;
 	if (window_copy_search_mark_at(data, data->cx, cy, &cursor) == 0) {
-		if (data->searchmark[cursor] == mark)
-			found = 1;
-		else if (cursor != 0) {
-			cursor--;
-			if (data->searchmark[cursor] == mark)
+		keys = options_get_number(wp->window->options, "mode-keys");
+		if (cursor != 0 &&
+		    keys == MODEKEY_EMACS &&
+		    data->searchdirection) {
+			if (data->searchmark[cursor - 1] == mark) {
+				cursor--;
 				found = 1;
-		}
+			}
+		} else if (data->searchmark[cursor] == mark)
+			found = 1;
 		if (found) {
 			window_copy_match_start_end(data, cursor, &start, &end);
 			if (current >= start && current <= end) {

From 5900b164a47689620db962e69f6d040bb82885b0 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 5 Apr 2021 08:43:48 +0000
Subject: [PATCH 08/17] Fix a couple of edge cases with the jump-back-xxx
 commands, and also update back-to-indentation to use grid_reader, thereby
 fixing line wrapping issues. From Anindya Mukherjee, GitHub issue 2633.

---
 grid-reader.c | 28 ++++++++++++++++++++++++++--
 tmux.h        |  3 ++-
 window-copy.c | 46 ++++++++++++++++++----------------------------
 3 files changed, 46 insertions(+), 31 deletions(-)

diff --git a/grid-reader.c b/grid-reader.c
index ae2f4d2b..89fe90fb 100644
--- a/grid-reader.c
+++ b/grid-reader.c
@@ -71,7 +71,7 @@ grid_reader_cursor_right(struct grid_reader *gr, int wrap, int all)
 
 /* Move cursor back one position. */
 void
-grid_reader_cursor_left(struct grid_reader *gr)
+grid_reader_cursor_left(struct grid_reader *gr, int wrap)
 {
 	struct grid_cell	gc;
 
@@ -81,7 +81,9 @@ grid_reader_cursor_left(struct grid_reader *gr)
 			break;
 		gr->cx--;
 	}
-	if (gr->cx == 0 && gr->cy > 0) {
+	if (gr->cx == 0 && gr->cy > 0 &&
+	    (wrap ||
+	     grid_get_line(gr->gd, gr->cy - 1)->flags & GRID_LINE_WRAPPED)) {
 		grid_reader_cursor_up(gr);
 		grid_reader_cursor_end_of_line(gr, 0, 0);
 	} else if (gr->cx > 0)
@@ -363,3 +365,25 @@ grid_reader_cursor_jump_back(struct grid_reader *gr, const struct utf8_data *jc)
 	}
 	return 0;
 }
+
+/* Jump back to the first non-blank character of the line. */
+void
+grid_reader_cursor_back_to_indentation(struct grid_reader *gr)
+{
+	struct grid_cell	gc;
+	u_int			px, py, xx, yy;
+
+	yy = gr->gd->hsize + gr->gd->sy - 1;
+	grid_reader_cursor_start_of_line(gr, 1);
+
+	for (py = gr->cy; py <= yy; py++) {
+		xx = grid_line_length(gr->gd, py);
+		for (px = 0; px < xx; px++) {
+			grid_get_cell(gr->gd, px, py, &gc);
+			if (gc.data.size != 1 || *gc.data.data != ' ')
+				break;
+		}
+		if (~grid_get_line(gr->gd, py)->flags & GRID_LINE_WRAPPED)
+			break;
+	}
+}
diff --git a/tmux.h b/tmux.h
index e50f9fe7..0c7636f8 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2583,7 +2583,7 @@ void	 grid_reader_get_cursor(struct grid_reader *, u_int *, u_int *);
 u_int	 grid_reader_line_length(struct grid_reader *);
 int	 grid_reader_in_set(struct grid_reader *, const char *);
 void	 grid_reader_cursor_right(struct grid_reader *, int, int);
-void	 grid_reader_cursor_left(struct grid_reader *);
+void	 grid_reader_cursor_left(struct grid_reader *, int);
 void	 grid_reader_cursor_down(struct grid_reader *);
 void	 grid_reader_cursor_up(struct grid_reader *);
 void	 grid_reader_cursor_start_of_line(struct grid_reader *, int);
@@ -2596,6 +2596,7 @@ int	 grid_reader_cursor_jump(struct grid_reader *,
 	     const struct utf8_data *);
 int	 grid_reader_cursor_jump_back(struct grid_reader *,
 	     const struct utf8_data *);
+void	 grid_reader_cursor_back_to_indentation(struct grid_reader *);
 
 /* grid-view.c */
 void	 grid_view_get_cell(struct grid *, u_int, u_int, struct grid_cell *);
diff --git a/window-copy.c b/window-copy.c
index 6276c8f9..423cce8f 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -4291,23 +4291,19 @@ static void
 window_copy_cursor_back_to_indentation(struct window_mode_entry *wme)
 {
 	struct window_copy_mode_data	*data = wme->data;
-	u_int				 px, py, xx;
-	struct grid_cell		 gc;
+	struct screen			*back_s = data->backing;
+	struct grid_reader		 gr;
+	u_int				 px, py, oldy, hsize;
 
-	px = 0;
-	py = screen_hsize(data->backing) + data->cy - data->oy;
-	xx = window_copy_find_length(wme, py);
+	px = data->cx;
+	hsize = screen_hsize(back_s);
+	py = hsize + data->cy - data->oy;
+	oldy = data->cy;
 
-	while (px < xx) {
-		grid_get_cell(data->backing->grid, px, py, &gc);
-		if (gc.data.size != 1 || *gc.data.data != ' ')
-			break;
-		px++;
-	}
-
-	window_copy_update_cursor(wme, px, data->cy);
-	if (window_copy_update_selection(wme, 1, 0))
-		window_copy_redraw_lines(wme, data->cy, 1);
+	grid_reader_start(&gr, back_s->grid, px, py);
+	grid_reader_cursor_back_to_indentation(&gr);
+	grid_reader_get_cursor(&gr, &px, &py);
+	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
 }
 
 static void
@@ -4398,7 +4394,7 @@ window_copy_cursor_left(struct window_mode_entry *wme)
 	oldy = data->cy;
 
 	grid_reader_start(&gr, back_s->grid, px, py);
-	grid_reader_cursor_left(&gr);
+	grid_reader_cursor_left(&gr, 1);
 	grid_reader_get_cursor(&gr, &px, &py);
 	window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px, py);
 }
@@ -4583,10 +4579,8 @@ window_copy_cursor_jump_back(struct window_mode_entry *wme)
 	py = hsize + data->cy - data->oy;
 	oldy = data->cy;
 
-	if (px > 0)
-		px--;
-
 	grid_reader_start(&gr, back_s->grid, px, py);
+	grid_reader_cursor_left(&gr, 0);
 	if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
 		grid_reader_get_cursor(&gr, &px, &py);
 		window_copy_acquire_cursor_up(wme, hsize, data->oy, oldy, px,
@@ -4609,7 +4603,7 @@ window_copy_cursor_jump_to(struct window_mode_entry *wme)
 
 	grid_reader_start(&gr, back_s->grid, px, py);
 	if (grid_reader_cursor_jump(&gr, data->jumpchar)) {
-		grid_reader_cursor_left(&gr);
+		grid_reader_cursor_left(&gr, 1);
 		grid_reader_get_cursor(&gr, &px, &py);
 		window_copy_acquire_cursor_down(wme, hsize,
 		    screen_size_y(back_s), data->oy, oldy, px, py, 0);
@@ -4629,13 +4623,9 @@ window_copy_cursor_jump_to_back(struct window_mode_entry *wme)
 	py = hsize + data->cy - data->oy;
 	oldy = data->cy;
 
-	if (px > 0)
-		px--;
-
-	if (px > 0)
-		px--;
-
 	grid_reader_start(&gr, back_s->grid, px, py);
+	grid_reader_cursor_left(&gr, 0);
+	grid_reader_cursor_left(&gr, 0);
 	if (grid_reader_cursor_jump_back(&gr, data->jumpchar)) {
 		grid_reader_cursor_right(&gr, 1, 0);
 		grid_reader_get_cursor(&gr, &px, &py);
@@ -4688,7 +4678,7 @@ window_copy_cursor_next_word_end_pos(struct window_mode_entry *wme,
 		grid_reader_cursor_right(&gr, 0, 0);
 	grid_reader_cursor_next_word_end(&gr, separators);
 	if (keys == MODEKEY_VI)
-		grid_reader_cursor_left(&gr);
+		grid_reader_cursor_left(&gr, 1);
 	grid_reader_get_cursor(&gr, &px, &py);
 	*ppx = px;
 	*ppy = py;
@@ -4718,7 +4708,7 @@ window_copy_cursor_next_word_end(struct window_mode_entry *wme,
 		grid_reader_cursor_right(&gr, 0, 0);
 	grid_reader_cursor_next_word_end(&gr, separators);
 	if (keys == MODEKEY_VI)
-		grid_reader_cursor_left(&gr);
+		grid_reader_cursor_left(&gr, 1);
 	grid_reader_get_cursor(&gr, &px, &py);
 	window_copy_acquire_cursor_down(wme, hsize, screen_size_y(back_s),
 	    data->oy, oldy, px, py, no_reset);

From 10470cea67eeb2ff34acbb37aac292bc6468ba07 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 5 Apr 2021 14:11:05 +0000
Subject: [PATCH 09/17] Move client-detached into server_client_lost so it is
 fired even if a client is closed unexpectedly.

---
 server-client.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/server-client.c b/server-client.c
index adeea5dd..219fdf3a 100644
--- a/server-client.c
+++ b/server-client.c
@@ -299,6 +299,9 @@ server_client_lost(struct client *c)
 	TAILQ_REMOVE(&clients, c, entry);
 	log_debug("lost client %p", c);
 
+	if (c->flags & CLIENT_ATTACHED)
+		notify_client("client-detached", c);
+
 	if (c->flags & CLIENT_CONTROL)
 		control_stop(c);
 	if (c->flags & CLIENT_TERMINAL)
@@ -1769,9 +1772,6 @@ server_client_check_exit(struct client *c)
 		if (EVBUFFER_LENGTH(cf->buffer) != 0)
 			return;
 	}
-
-	if (c->flags & CLIENT_ATTACHED)
-		notify_client("client-detached", c);
 	c->flags |= CLIENT_EXITED;
 
 	switch (c->exit_type) {

From ba999966768b3ede648a361dd9b17ca81bdda173 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Wed, 7 Apr 2021 07:30:02 +0000
Subject: [PATCH 10/17] Fixes for extended keys: 1) allow C-x and C-X to be
 bound separately since some terminals report them differently 2) use the
 "backspace" option to translate backspace 3) map ctrl which are have the ctrl
 implied (such as C-x) properly when the terminal reports both the key and the
 modifier.

Note that any key bindings for C-X where C-x is meant must now be
changed.
---
 key-string.c | 18 +++++++++---------
 tty-keys.c   | 53 ++++++++++++++++++++++++++++++++++++++--------------
 2 files changed, 48 insertions(+), 23 deletions(-)

diff --git a/key-string.c b/key-string.c
index 194fdef2..0633ed22 100644
--- a/key-string.c
+++ b/key-string.c
@@ -45,9 +45,9 @@ static const struct {
 	{ "F11",	KEYC_F11|KEYC_IMPLIED_META },
 	{ "F12",	KEYC_F12|KEYC_IMPLIED_META },
 	{ "IC",		KEYC_IC|KEYC_IMPLIED_META },
-	{ "Insert",     KEYC_IC|KEYC_IMPLIED_META },
+	{ "Insert",	KEYC_IC|KEYC_IMPLIED_META },
 	{ "DC",		KEYC_DC|KEYC_IMPLIED_META },
-	{ "Delete",     KEYC_DC|KEYC_IMPLIED_META },
+	{ "Delete",	KEYC_DC|KEYC_IMPLIED_META },
 	{ "Home",	KEYC_HOME|KEYC_IMPLIED_META },
 	{ "End",	KEYC_END|KEYC_IMPLIED_META },
 	{ "NPage",	KEYC_NPAGE|KEYC_IMPLIED_META },
@@ -70,7 +70,7 @@ static const struct {
 	{ "Right",	KEYC_RIGHT|KEYC_CURSOR|KEYC_IMPLIED_META },
 
 	/* Numeric keypad. */
-	{ "KP/", 	KEYC_KP_SLASH|KEYC_KEYPAD },
+	{ "KP/",	KEYC_KP_SLASH|KEYC_KEYPAD },
 	{ "KP*",	KEYC_KP_STAR|KEYC_KEYPAD },
 	{ "KP-",	KEYC_KP_MINUS|KEYC_KEYPAD },
 	{ "KP7",	KEYC_KP_SEVEN|KEYC_KEYPAD },
@@ -164,7 +164,7 @@ key_string_get_modifiers(const char **string)
 key_code
 key_string_lookup_string(const char *string)
 {
-	static const char	*other = "!#()+,-.0123456789:;<=>'\r\t";
+	static const char	*other = "!#()+,-.0123456789:;<=>'\r\t\177";
 	key_code		 key, modifiers;
 	u_int			 u, i;
 	struct utf8_data	 ud, *udp;
@@ -181,8 +181,8 @@ key_string_lookup_string(const char *string)
 
 	/* Is this a hexadecimal value? */
 	if (string[0] == '0' && string[1] == 'x') {
-	        if (sscanf(string + 2, "%x", &u) != 1)
-	                return (KEYC_UNKNOWN);
+		if (sscanf(string + 2, "%x", &u) != 1)
+			return (KEYC_UNKNOWN);
 		mlen = wctomb(m, u);
 		if (mlen <= 0 || mlen > MB_LEN_MAX)
 			return (KEYC_UNKNOWN);
@@ -238,11 +238,11 @@ key_string_lookup_string(const char *string)
 	}
 
 	/* Convert the standard control keys. */
-	if (key < KEYC_BASE && (modifiers & KEYC_CTRL) && !strchr(other, key)) {
+	if (key < KEYC_BASE && (modifiers & KEYC_CTRL) &&
+	    strchr(other, key) == NULL &&
+	    (key < 64 || key > 95)) {
 		if (key >= 97 && key <= 122)
 			key -= 96;
-		else if (key >= 64 && key <= 95)
-			key -= 64;
 		else if (key == 32)
 			key = 0;
 		else if (key == 63)
diff --git a/tty-keys.c b/tty-keys.c
index 59426772..16cd09f2 100644
--- a/tty-keys.c
+++ b/tty-keys.c
@@ -61,7 +61,7 @@ static int	tty_keys_extended_device_attributes(struct tty *, const char *,
 /* Default raw keys. */
 struct tty_default_key_raw {
 	const char	       *string;
-	key_code	 	key;
+	key_code		key;
 };
 static const struct tty_default_key_raw tty_default_raw_keys[] = {
 	/* Application escape. */
@@ -262,7 +262,7 @@ static const key_code tty_default_xterm_modifiers[] = {
  */
 struct tty_default_key_code {
 	enum tty_code_code	code;
-	key_code	 	key;
+	key_code		key;
 };
 static const struct tty_default_key_code tty_default_code_keys[] = {
 	/* Function keys. */
@@ -420,7 +420,7 @@ tty_keys_add(struct tty *tty, const char *s, key_code key)
 {
 	struct tty_key	*tk;
 	size_t		 size;
-	const char     	*keystr;
+	const char	*keystr;
 
 	keystr = key_string_lookup_key(key, 1);
 	if ((tk = tty_keys_find(tty, s, strlen(s), &size)) == NULL) {
@@ -477,7 +477,7 @@ tty_keys_build(struct tty *tty)
 	const struct tty_default_key_raw	*tdkr;
 	const struct tty_default_key_xterm	*tdkx;
 	const struct tty_default_key_code	*tdkc;
-	u_int		 			 i, j;
+	u_int					 i, j;
 	const char				*s;
 	struct options_entry			*o;
 	struct options_array_item		*a;
@@ -869,6 +869,8 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
 	size_t		 end;
 	u_int		 number, modifiers;
 	char		 tmp[64];
+	cc_t		 bspace;
+	key_code	 nkey;
 
 	*size = 0;
 
@@ -911,38 +913,61 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
 	}
 	*size = end + 1;
 
-	/* Store the key and modifiers. */
-	*key = number;
+	/* Store the key. */
+	bspace = tty->tio.c_cc[VERASE];
+	if (bspace != _POSIX_VDISABLE && number == bspace)
+		nkey = KEYC_BSPACE;
+	else
+		nkey = number;
+
+	/* Update the modifiers. */
 	switch (modifiers) {
 	case 2:
-		(*key) |= KEYC_SHIFT;
+		nkey |= KEYC_SHIFT;
 		break;
 	case 3:
-		(*key) |= (KEYC_META|KEYC_IMPLIED_META);
+		nkey |= (KEYC_META|KEYC_IMPLIED_META);
 		break;
 	case 4:
-		(*key) |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META);
+		nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META);
 		break;
 	case 5:
-		(*key) |= KEYC_CTRL;
+		nkey |= KEYC_CTRL;
 		break;
 	case 6:
-		(*key) |= (KEYC_SHIFT|KEYC_CTRL);
+		nkey |= (KEYC_SHIFT|KEYC_CTRL);
 		break;
 	case 7:
-		(*key) |= (KEYC_META|KEYC_CTRL);
+		nkey |= (KEYC_META|KEYC_CTRL);
 		break;
 	case 8:
-		(*key) |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL);
+		nkey |= (KEYC_SHIFT|KEYC_META|KEYC_IMPLIED_META|KEYC_CTRL);
 		break;
 	default:
 		*key = KEYC_NONE;
 		break;
 	}
+
+    /* Don't allow both KEYC_CTRL and implied. */
+	if ((nkey & KEYC_CTRL) && (nkey & KEYC_MASK_KEY) < 32)
+		nkey &= ~KEYC_CTRL;
+	if ((nkey & KEYC_MASK_MODIFIERS) == KEYC_CTRL) {
+		nkey &= KEYC_MASK_KEY;
+		if (nkey >= 97 && nkey <= 122)
+			nkey -= 96;
+		else if (nkey == 32)
+			nkey = 0;
+		else if (nkey == 63)
+			nkey = 127;
+		else
+			nkey |= KEYC_CTRL;
+	}
+
 	if (log_get_level() != 0) {
 		log_debug("%s: extended key %.*s is %llx (%s)", c->name,
-		    (int)*size, buf, *key, key_string_lookup_key(*key, 1));
+		    (int)*size, buf, nkey, key_string_lookup_key(nkey, 1));
 	}
+	*key = nkey;
 	return (0);
 }
 

From 1ac47400d296ad3a56e9740f9249175bb59953d5 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Wed, 7 Apr 2021 12:49:33 +0000
Subject: [PATCH 11/17] When display-message used in config file, show the
 message after the config file finishes. GitHub issue 2637.

---
 cmd-display-message.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/cmd-display-message.c b/cmd-display-message.c
index fc9c4851..301c1a5e 100644
--- a/cmd-display-message.c
+++ b/cmd-display-message.c
@@ -43,7 +43,7 @@ const struct cmd_entry cmd_display_message_entry = {
 	.usage = "[-aIpv] [-c target-client] [-d delay] [-F format] "
 		 CMD_TARGET_PANE_USAGE " [message]",
 
-	.target = { 't', CMD_FIND_PANE, 0 },
+	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
 
 	.flags = CMD_AFTERHOOK|CMD_CLIENT_CFLAG|CMD_CLIENT_CANFAIL,
 	.exec = cmd_display_message_exec
@@ -73,6 +73,8 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
 	int			 flags;
 
 	if (args_has(args, 'I')) {
+		if (wp == NULL)
+			return (CMD_RETURN_NORMAL);
 		if (window_pane_start_input(wp, item, &cause) != 0) {
 			cmdq_error(item, "%s", cause);
 			free(cause);
@@ -109,8 +111,10 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
 	 */
 	if (tc != NULL && tc->session == s)
 		c = tc;
-	else
+	else if (s != NULL)
 		c = cmd_find_best_client(s);
+	else
+		c = NULL;
 	if (args_has(args, 'v'))
 		flags = FORMAT_VERBOSE;
 	else
@@ -124,7 +128,9 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
 	}
 
 	msg = format_expand_time(ft, template);
-	if (args_has(args, 'p'))
+	if (cmdq_get_client(item) == NULL)
+		cmdq_error(item, "%s", msg);
+	else if (args_has(args, 'p'))
 		cmdq_print(item, "%s", msg);
 	else if (tc != NULL)
 		status_message_set(tc, delay, 0, "%s", msg);

From 71fc9f3ee8753a2bb163c85c784936a8d6d3e0ac Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Wed, 7 Apr 2021 12:50:12 +0000
Subject: [PATCH 12/17] Add a current_file format for the config file being
 parsed. Originally suggested by kn@, also GitHub issue 2638.

---
 cfg.c       | 20 ++++++++++++++++++--
 cmd-queue.c |  2 +-
 tmux.1      |  1 +
 3 files changed, 20 insertions(+), 3 deletions(-)

diff --git a/cfg.c b/cfg.c
index 55c91bc4..cf6117f4 100644
--- a/cfg.c
+++ b/cfg.c
@@ -103,6 +103,7 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags,
 	struct cmd_parse_input	 pi;
 	struct cmd_parse_result	*pr;
 	struct cmdq_item	*new_item0;
+	struct cmdq_state	*state;
 
 	if (new_item != NULL)
 		*new_item = NULL;
@@ -136,12 +137,19 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags,
 		return (0);
 	}
 
-	new_item0 = cmdq_get_command(pr->cmdlist, NULL);
+	if (item != NULL)
+		state = cmdq_copy_state(cmdq_get_state(item));
+	else
+		state = cmdq_new_state(NULL, NULL, 0);
+	cmdq_add_format(state, "current_file", "%s", pi.file);
+
+	new_item0 = cmdq_get_command(pr->cmdlist, state);
 	if (item != NULL)
 		new_item0 = cmdq_insert_after(item, new_item0);
 	else
 		new_item0 = cmdq_append(NULL, new_item0);
 	cmd_list_free(pr->cmdlist);
+	cmdq_free_state(state);
 
 	if (new_item != NULL)
 		*new_item = new_item0;
@@ -156,6 +164,7 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
 	struct cmd_parse_input	 pi;
 	struct cmd_parse_result	*pr;
 	struct cmdq_item	*new_item0;
+	struct cmdq_state	*state;
 
 	if (new_item != NULL)
 		*new_item = NULL;
@@ -182,12 +191,19 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
 		return (0);
 	}
 
-	new_item0 = cmdq_get_command(pr->cmdlist, NULL);
+	if (item != NULL)
+		state = cmdq_copy_state(cmdq_get_state(item));
+	else
+		state = cmdq_new_state(NULL, NULL, 0);
+	cmdq_add_format(state, "current_file", "%s", pi.file);
+
+	new_item0 = cmdq_get_command(pr->cmdlist, state);
 	if (item != NULL)
 		new_item0 = cmdq_insert_after(item, new_item0);
 	else
 		new_item0 = cmdq_append(NULL, new_item0);
 	cmd_list_free(pr->cmdlist);
+	cmdq_free_state(state);
 
 	if (new_item != NULL)
 		*new_item = new_item0;
diff --git a/cmd-queue.c b/cmd-queue.c
index 05f439f5..a0d80c34 100644
--- a/cmd-queue.c
+++ b/cmd-queue.c
@@ -276,7 +276,7 @@ cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft)
 	const struct cmd_entry	*entry;
 
 	if (item->cmd != NULL) {
-		entry = cmd_get_entry (item->cmd);
+		entry = cmd_get_entry(item->cmd);
 		format_add(ft, "command", "%s", entry->name);
 	}
 	if (item->state->formats != NULL)
diff --git a/tmux.1 b/tmux.1
index d76cadd3..00b5cd84 100644
--- a/tmux.1
+++ b/tmux.1
@@ -4859,6 +4859,7 @@ The following variables are available, where appropriate:
 .It Li "copy_cursor_word" Ta "" Ta "Word under cursor in copy mode"
 .It Li "copy_cursor_x" Ta "" Ta "Cursor X position in copy mode"
 .It Li "copy_cursor_y" Ta "" Ta "Cursor Y position in copy mode"
+.It Li "current_file" Ta "" Ta "Current configuration file"
 .It Li "cursor_character" Ta "" Ta "Character at cursor in pane"
 .It Li "cursor_flag" Ta "" Ta "Pane cursor flag"
 .It Li "cursor_x" Ta "" Ta "Cursor X position in pane"

From efb5e58c381a5faf7751d916a60f256ede19c0e8 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Wed, 7 Apr 2021 15:46:12 +0000
Subject: [PATCH 13/17] Restore previous behaviour so that C-X remains the same
 as C-x. Instead, translate incoming extended keys so that they are
 consistent.

---
 key-string.c |  5 +++--
 tty-keys.c   | 34 +++++++++++++++++++++-------------
 2 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/key-string.c b/key-string.c
index 0633ed22..8d60f132 100644
--- a/key-string.c
+++ b/key-string.c
@@ -239,10 +239,11 @@ key_string_lookup_string(const char *string)
 
 	/* Convert the standard control keys. */
 	if (key < KEYC_BASE && (modifiers & KEYC_CTRL) &&
-	    strchr(other, key) == NULL &&
-	    (key < 64 || key > 95)) {
+	    strchr(other, key) == NULL) {
 		if (key >= 97 && key <= 122)
 			key -= 96;
+		else if (key >= 64 && key <= 95)
+                       key -= 64;
 		else if (key == 32)
 			key = 0;
 		else if (key == 63)
diff --git a/tty-keys.c b/tty-keys.c
index 16cd09f2..e88ff227 100644
--- a/tty-keys.c
+++ b/tty-keys.c
@@ -871,6 +871,7 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
 	char		 tmp[64];
 	cc_t		 bspace;
 	key_code	 nkey;
+	key_code	 onlykey;
 
 	*size = 0;
 
@@ -948,19 +949,26 @@ tty_keys_extended_key(struct tty *tty, const char *buf, size_t len,
 		break;
 	}
 
-    /* Don't allow both KEYC_CTRL and implied. */
-	if ((nkey & KEYC_CTRL) && (nkey & KEYC_MASK_KEY) < 32)
-		nkey &= ~KEYC_CTRL;
-	if ((nkey & KEYC_MASK_MODIFIERS) == KEYC_CTRL) {
-		nkey &= KEYC_MASK_KEY;
-		if (nkey >= 97 && nkey <= 122)
-			nkey -= 96;
-		else if (nkey == 32)
-			nkey = 0;
-		else if (nkey == 63)
-			nkey = 127;
-		else
-			nkey |= KEYC_CTRL;
+	/*
+	 * Don't allow both KEYC_CTRL and as an implied modifier. Also convert
+	 * C-X into C-x and so on.
+	 */
+	if (nkey & KEYC_CTRL){
+		onlykey = (nkey & KEYC_MASK_KEY);
+		if (onlykey < 32)
+			onlykey = (nkey & ~KEYC_CTRL);
+		else {
+			if (onlykey >= 97 && onlykey <= 122)
+				onlykey -= 96;
+			else if (onlykey >= 64 && onlykey <= 95)
+				onlykey -= 64;
+			else if (onlykey == 32)
+				onlykey = 0;
+			else if (onlykey == 63)
+				onlykey = 127;
+			onlykey |= ((nkey & KEYC_MASK_MODIFIERS) & ~KEYC_CTRL);
+		}
+		nkey = onlykey;
 	}
 
 	if (log_get_level() != 0) {

From 30fb6283886cdccf183bb480a2c1976ba1a7e60e Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Thu, 8 Apr 2021 14:16:12 +0000
Subject: [PATCH 14/17] Log the key written to the terminal as well as tmux's
 idea of what it is.

---
 input-keys.c | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/input-keys.c b/input-keys.c
index 5cd43dc3..40567a49 100644
--- a/input-keys.c
+++ b/input-keys.c
@@ -429,6 +429,14 @@ input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
 	return (input_key(wp->screen, wp->event, key));
 }
 
+static void
+input_key_write(const char *from, struct bufferevent *bev, const void *data,
+    size_t size)
+{
+	log_debug("%s: %.*s", from, (int)size, data);
+	bufferevent_write(bev, data, size);
+}
+
 /* Translate a key code into an output key sequence. */
 int
 input_key(struct screen *s, struct bufferevent *bev, key_code key)
@@ -445,7 +453,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
 	/* Literal keys go as themselves (can't be more than eight bits). */
 	if (key & KEYC_LITERAL) {
 		ud.data[0] = (u_char)key;
-		bufferevent_write(bev, &ud.data[0], 1);
+		input_key_write(__func__, bev, &ud.data[0], 1);
 		return (0);
 	}
 
@@ -464,16 +472,16 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
 	justkey = (key & ~(KEYC_META|KEYC_IMPLIED_META));
 	if (justkey <= 0x7f) {
 		if (key & KEYC_META)
-			bufferevent_write(bev, "\033", 1);
+			input_key_write(__func__, bev, "\033", 1);
 		ud.data[0] = justkey;
-		bufferevent_write(bev, &ud.data[0], 1);
+		input_key_write(__func__, bev, &ud.data[0], 1);
 		return (0);
 	}
 	if (justkey > 0x7f && justkey < KEYC_BASE) {
 		if (key & KEYC_META)
-			bufferevent_write(bev, "\033", 1);
+			input_key_write(__func__, bev, "\033", 1);
 		utf8_to_data(justkey, &ud);
-		bufferevent_write(bev, ud.data, ud.size);
+		input_key_write(__func__, bev, ud.data, ud.size);
 		return (0);
 	}
 
@@ -495,8 +503,8 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
 	if (ike != NULL) {
 		log_debug("found key 0x%llx: \"%s\"", key, ike->data);
 		if ((key & KEYC_META) && (~key & KEYC_IMPLIED_META))
-			bufferevent_write(bev, "\033", 1);
-		bufferevent_write(bev, ike->data, strlen(ike->data));
+			input_key_write(__func__, bev, "\033", 1);
+		input_key_write(__func__, bev, ike->data, strlen(ike->data));
 		return (0);
 	}
 
@@ -561,7 +569,7 @@ input_key(struct screen *s, struct bufferevent *bev, key_code key)
 		goto missing;
 	}
 	xsnprintf(tmp, sizeof tmp, "\033[%llu;%cu", outkey, modifier);
-	bufferevent_write(bev, tmp, strlen(tmp));
+	input_key_write(__func__, bev, tmp, strlen(tmp));
 	return (0);
 
 missing:
@@ -657,5 +665,5 @@ input_key_mouse(struct window_pane *wp, struct mouse_event *m)
 	if (!input_key_get_mouse(s, m, x, y, &buf, &len))
 		return;
 	log_debug("writing mouse %.*s to %%%u", (int)len, buf, wp->id);
-	bufferevent_write(wp->event, buf, len);
+	input_key_write(__func__, wp->event, buf, len);
 }

From 73cbe46f8d871b70310735276f34d9c207412587 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Fri, 9 Apr 2021 07:02:00 +0000
Subject: [PATCH 15/17] Change a type to fix a warning with some compilers.

---
 input-keys.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/input-keys.c b/input-keys.c
index 40567a49..ab7d2212 100644
--- a/input-keys.c
+++ b/input-keys.c
@@ -430,7 +430,7 @@ input_key_pane(struct window_pane *wp, key_code key, struct mouse_event *m)
 }
 
 static void
-input_key_write(const char *from, struct bufferevent *bev, const void *data,
+input_key_write(const char *from, struct bufferevent *bev, const char *data,
     size_t size)
 {
 	log_debug("%s: %.*s", from, (int)size, data);

From cd208c9d72df79a34024df6b8eb8f984613de8ef Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 12 Apr 2021 06:50:25 +0000
Subject: [PATCH 16/17] Permit shortcut keys in buffer, client, tree modes to
 be configured with a format; the default remains the line number. GitHub
 issue 2636.

---
 cmd-choose-tree.c  | 18 ++++++------
 format.c           | 34 +++++++++++++++++------
 mode-tree.c        | 68 ++++++++++++++++++++++++++++++++--------------
 tmux.1             | 34 ++++++++++++++++++++---
 tmux.h             |  3 +-
 window-buffer.c    | 59 ++++++++++++++++++++++++++++++++++++++--
 window-client.c    | 43 +++++++++++++++++++++++++++--
 window-customize.c |  4 +--
 window-tree.c      | 52 +++++++++++++++++++++++++++++++++--
 9 files changed, 261 insertions(+), 54 deletions(-)

diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c
index a58469ac..81209ee3 100644
--- a/cmd-choose-tree.c
+++ b/cmd-choose-tree.c
@@ -30,9 +30,9 @@ const struct cmd_entry cmd_choose_tree_entry = {
 	.name = "choose-tree",
 	.alias = NULL,
 
-	.args = { "F:Gf:NO:rst:wZ", 0, 1 },
-	.usage = "[-GNrswZ] [-F format] [-f filter] [-O sort-order] "
-	         CMD_TARGET_PANE_USAGE " [template]",
+	.args = { "F:f:GK:NO:rst:wZ", 0, 1 },
+	.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] "
+		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
 	.target = { 't', CMD_FIND_PANE, 0 },
 
@@ -44,9 +44,9 @@ const struct cmd_entry cmd_choose_client_entry = {
 	.name = "choose-client",
 	.alias = NULL,
 
-	.args = { "F:f:NO:rt:Z", 0, 1 },
-	.usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] "
-	         CMD_TARGET_PANE_USAGE " [template]",
+	.args = { "F:f:K:NO:rt:Z", 0, 1 },
+	.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
+		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
 	.target = { 't', CMD_FIND_PANE, 0 },
 
@@ -58,9 +58,9 @@ const struct cmd_entry cmd_choose_buffer_entry = {
 	.name = "choose-buffer",
 	.alias = NULL,
 
-	.args = { "F:f:NO:rt:Z", 0, 1 },
-	.usage = "[-NrZ] [-F format] [-f filter] [-O sort-order] "
-	         CMD_TARGET_PANE_USAGE " [template]",
+	.args = { "F:f:K:NO:rt:Z", 0, 1 },
+	.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
+		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
 	.target = { 't', CMD_FIND_PANE, 0 },
 
diff --git a/format.c b/format.c
index 80e72cf0..3a1385b2 100644
--- a/format.c
+++ b/format.c
@@ -100,6 +100,7 @@ format_job_cmp(struct format_job *fj1, struct format_job *fj2)
 #define FORMAT_QUOTE_STYLE 0x2000
 #define FORMAT_WINDOW_NAME 0x4000
 #define FORMAT_SESSION_NAME 0x8000
+#define FORMAT_CHARACTER 0x10000
 
 /* Limit on recursion. */
 #define FORMAT_LOOP_LIMIT 10
@@ -3522,7 +3523,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
 
 	/*
 	 * Modifiers are a ; separated list of the forms:
-	 *      l,m,C,b,d,n,t,w,q,E,T,S,W,P,<,>
+	 *      l,m,C,a,b,d,n,t,w,q,E,T,S,W,P,<,>
 	 *	=a
 	 *	=/a
 	 *      =/a/
@@ -3539,7 +3540,7 @@ format_build_modifiers(struct format_expand_state *es, const char **s,
 			cp++;
 
 		/* Check single character modifiers with no arguments. */
-		if (strchr("lbdnwETSWP<>", cp[0]) != NULL &&
+		if (strchr("labdnwETSWP<>", cp[0]) != NULL &&
 		    format_is_end(cp[1])) {
 			format_add_modifier(&list, count, cp, 1, NULL, 0);
 			cp++;
@@ -3956,7 +3957,7 @@ format_replace_expression(struct format_modifier *mexp,
 		mright = (long long)mright;
 	}
 	format_log(es, "expression left side is: %.*f", prec, mleft);
-	format_log(es, "expression right side is:  %.*f", prec, mright);
+	format_log(es, "expression right side is: %.*f", prec, mright);
 
 	switch (operator) {
 	case ADD:
@@ -4016,10 +4017,10 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
 {
 	struct format_tree		 *ft = es->ft;
 	struct window_pane		 *wp = ft->wp;
-	const char			 *errptr, *copy, *cp, *marker = NULL;
+	const char			 *errstr, *copy, *cp, *marker = NULL;
 	const char			 *time_format = NULL;
 	char				 *copy0, *condition, *found, *new;
-	char				 *value, *left, *right;
+	char				 *value, *left, *right, c;
 	size_t				  valuelen;
 	int				  modifiers = 0, limit = 0, width = 0;
 	int				  j;
@@ -4063,8 +4064,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
 				if (fm->argc < 1)
 					break;
 				limit = strtonum(fm->argv[0], INT_MIN, INT_MAX,
-				    &errptr);
-				if (errptr != NULL)
+				    &errstr);
+				if (errstr != NULL)
 					limit = 0;
 				if (fm->argc >= 2 && fm->argv[1] != NULL)
 					marker = fm->argv[1];
@@ -4073,8 +4074,8 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
 				if (fm->argc < 1)
 					break;
 				width = strtonum(fm->argv[0], INT_MIN, INT_MAX,
-				    &errptr);
-				if (errptr != NULL)
+				    &errstr);
+				if (errstr != NULL)
 					width = 0;
 				break;
 			case 'w':
@@ -4088,6 +4089,9 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
 			case 'l':
 				modifiers |= FORMAT_LITERAL;
 				break;
+			case 'a':
+				modifiers |= FORMAT_CHARACTER;
+				break;
 			case 'b':
 				modifiers |= FORMAT_BASENAME;
 				break;
@@ -4154,6 +4158,18 @@ format_replace(struct format_expand_state *es, const char *key, size_t keylen,
 		goto done;
 	}
 
+	/* Is this a character? */
+	if (modifiers & FORMAT_CHARACTER) {
+		new = format_expand1(es, copy);
+		c = strtonum(new, 32, 126, &errstr);
+		if (errstr != NULL)
+			value = xstrdup("");
+		else
+			xasprintf(&value, "%c", c);
+		free (new);
+		goto done;
+	}
+
 	/* Is this a loop, comparison or condition? */
 	if (modifiers & FORMAT_SESSIONS) {
 		value = format_loop_sessions(es, copy);
diff --git a/mode-tree.c b/mode-tree.c
index a47c0c06..c0f85026 100644
--- a/mode-tree.c
+++ b/mode-tree.c
@@ -46,6 +46,7 @@ struct mode_tree_data {
 	mode_tree_search_cb       searchcb;
 	mode_tree_menu_cb         menucb;
 	mode_tree_height_cb       heightcb;
+	mode_tree_key_cb	  keycb;
 
 	struct mode_tree_list	  children;
 	struct mode_tree_list	  saved;
@@ -74,6 +75,10 @@ struct mode_tree_item {
 	void				*itemdata;
 	u_int				 line;
 
+	key_code			 key;
+	const char			*keystr;
+	size_t				 keylen;
+
 	uint64_t			 tag;
 	const char			*name;
 	const char			*text;
@@ -135,6 +140,7 @@ mode_tree_free_item(struct mode_tree_item *mti)
 
 	free((void *)mti->name);
 	free((void *)mti->text);
+	free((void *)mti->keystr);
 
 	free(mti);
 }
@@ -193,6 +199,26 @@ mode_tree_build_lines(struct mode_tree_data *mtd,
 			flat = 0;
 		if (mti->expanded)
 			mode_tree_build_lines(mtd, &mti->children, depth + 1);
+
+		if (mtd->keycb != NULL) {
+			mti->key = mtd->keycb(mtd->modedata, mti->itemdata,
+			    mti->line);
+			if (mti->key == KEYC_UNKNOWN)
+				mti->key = KEYC_NONE;
+		} else if (mti->line < 10)
+			mti->key = '0' + mti->line;
+		else if (mti->line < 36)
+			mti->key = KEYC_META|('a' + mti->line - 10);
+		else
+			mti->key = KEYC_NONE;
+		if (mti->key != KEYC_NONE) {
+			mti->keystr = xstrdup(key_string_lookup_key(mti->key,
+			    0));
+			mti->keylen = strlen(mti->keystr);
+		} else {
+			mti->keystr = NULL;
+			mti->keylen = 0;
+		}
 	}
 	TAILQ_FOREACH(mti, mtl, entry) {
 		for (i = 0; i < mtd->line_size; i++) {
@@ -363,7 +389,7 @@ struct mode_tree_data *
 mode_tree_start(struct window_pane *wp, struct args *args,
     mode_tree_build_cb buildcb, mode_tree_draw_cb drawcb,
     mode_tree_search_cb searchcb, mode_tree_menu_cb menucb,
-    mode_tree_height_cb heightcb, void *modedata,
+    mode_tree_height_cb heightcb, mode_tree_key_cb keycb, void *modedata,
     const struct menu_item *menu, const char **sort_list, u_int sort_size,
     struct screen **s)
 {
@@ -402,6 +428,7 @@ mode_tree_start(struct window_pane *wp, struct args *args,
 	mtd->searchcb = searchcb;
 	mtd->menucb = menucb;
 	mtd->heightcb = heightcb;
+	mtd->keycb = keycb;
 
 	TAILQ_INIT(&mtd->children);
 
@@ -596,10 +623,10 @@ mode_tree_draw(struct mode_tree_data *mtd)
 	struct screen_write_ctx	 ctx;
 	struct grid_cell	 gc0, gc;
 	u_int			 w, h, i, j, sy, box_x, box_y, width;
-	char			*text, *start, key[7];
+	char			*text, *start, *key;
 	const char		*tag, *symbol;
 	size_t			 size, n;
-	int			 keylen;
+	int			 keylen, pad;
 
 	if (mtd->line_size == 0)
 		return;
@@ -614,28 +641,30 @@ mode_tree_draw(struct mode_tree_data *mtd)
 	screen_write_start(&ctx, s);
 	screen_write_clearscreen(&ctx, 8);
 
-	if (mtd->line_size > 10)
-		keylen = 6;
-	else
-		keylen = 4;
+	keylen = 0;
+	for (i = 0; i < mtd->line_size; i++) {
+		mti = mtd->line_list[i].item;
+		if (mti->key == KEYC_NONE)
+			continue;
+		if ((int)mti->keylen + 3 > keylen)
+			keylen = mti->keylen + 3;
+	}
 
 	for (i = 0; i < mtd->line_size; i++) {
 		if (i < mtd->offset)
 			continue;
 		if (i > mtd->offset + h - 1)
 			break;
-
 		line = &mtd->line_list[i];
 		mti = line->item;
 
 		screen_write_cursormove(&ctx, 0, i - mtd->offset, 0);
 
-		if (i < 10)
-			snprintf(key, sizeof key, "(%c)  ", '0' + i);
-		else if (i < 36)
-			snprintf(key, sizeof key, "(M-%c)", 'a' + (i - 10));
+		pad = keylen - 2 - mti->keylen;
+		if (mti->key != KEYC_NONE)
+			xasprintf(&key, "(%s)%*s", mti->keystr, pad, "");
 		else
-			*key = '\0';
+			key = xstrdup("");
 
 		if (line->flat)
 			symbol = "";
@@ -698,6 +727,7 @@ mode_tree_draw(struct mode_tree_data *mtd)
 			}
 		}
 		free(text);
+		free(key);
 
 		if (mti->tagged) {
 			gc.attr ^= GRID_ATTR_BRIGHT;
@@ -951,7 +981,6 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
 	struct mode_tree_item	*current, *parent, *mti;
 	u_int			 i, x, y;
 	int			 choice;
-	key_code		 tmp;
 
 	if (KEYC_IS_MOUSE(*key) && m != NULL) {
 		if (cmd_mouse_at(mtd->wp, m, &x, &y, 0) != 0) {
@@ -993,12 +1022,11 @@ mode_tree_key(struct mode_tree_data *mtd, struct client *c, key_code *key,
 	current = line->item;
 
 	choice = -1;
-	if (*key >= '0' && *key <= '9')
-		choice = (*key) - '0';
-	else if (((*key) & KEYC_MASK_MODIFIERS) == KEYC_META) {
-		tmp = (*key) & KEYC_MASK_KEY;
-		if (tmp >= 'a' && tmp <= 'z')
-			choice = 10 + (tmp - 'a');
+	for (i = 0; i < mtd->line_size; i++) {
+		if (*key == mtd->line_list[i].item->key) {
+			choice = i;
+			break;
+		}
 	}
 	if (choice != -1) {
 		if ((u_int)choice > mtd->line_size - 1) {
diff --git a/tmux.1 b/tmux.1
index 00b5cd84..74cb5f00 100644
--- a/tmux.1
+++ b/tmux.1
@@ -1985,12 +1985,17 @@ The default is to capture only the visible contents of the pane.
 .Op Fl NrZ
 .Op Fl F Ar format
 .Op Fl f Ar filter
+.Op Fl K Ar key-format
 .Op Fl O Ar sort-order
 .Op Fl t Ar target-pane
 .Op Ar template
 .Xc
 Put a pane into client mode, allowing a client to be selected interactively from
 a list.
+Each client is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the list may be navigated and an item chosen or otherwise manipulated using
+the keys below.
 .Fl Z
 zooms the pane.
 The following keys may be used in client mode:
@@ -2040,7 +2045,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero,
 the item in the list is not shown, otherwise it is shown.
 If a filter would lead to an empty list, it is ignored.
 .Fl F
-specifies the format for each item in the list.
+specifies the format for each item in the list and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
 .Fl N
 starts without the preview.
 This command works only if at least one client is attached.
@@ -2049,12 +2056,17 @@ This command works only if at least one client is attached.
 .Op Fl GNrswZ
 .Op Fl F Ar format
 .Op Fl f Ar filter
+.Op Fl K Ar key-format
 .Op Fl O Ar sort-order
 .Op Fl t Ar target-pane
 .Op Ar template
 .Xc
 Put a pane into tree mode, where a session, window or pane may be chosen
-interactively from a list.
+interactively from a tree.
+Each session, window or pane is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the tree may be navigated and an item chosen or otherwise manipulated using
+the keys below.
 .Fl s
 starts with sessions collapsed and
 .Fl w
@@ -2113,7 +2125,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero,
 the item in the list is not shown, otherwise it is shown.
 If a filter would lead to an empty list, it is ignored.
 .Fl F
-specifies the format for each item in the tree.
+specifies the format for each item in the tree and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
 .Fl N
 starts without the preview.
 .Fl G
@@ -4663,6 +4677,11 @@ For example,
 multiplies 5.5 by 3 for a result with four decimal places and
 .Ql #{e|%%:7,3}
 returns the modulus of 7 and 3.
+.Ql a
+replaces a numeric argument by its ASCII equivalent, so
+.Ql #{a:98}
+results in
+.Ql b .
 .Pp
 A limit may be placed on the length of the resultant string by prefixing it
 by an
@@ -5681,12 +5700,17 @@ The buffer commands are as follows:
 .Op Fl NZr
 .Op Fl F Ar format
 .Op Fl f Ar filter
+.Op Fl K Ar key-format
 .Op Fl O Ar sort-order
 .Op Fl t Ar target-pane
 .Op Ar template
 .Xc
 Put a pane into buffer mode, where a buffer may be chosen interactively from
 a list.
+Each buffer is shown on one line.
+A shortcut key is shown on the left in brackets allowing for immediate choice,
+or the list may be navigated and an item chosen or otherwise manipulated using
+the keys below.
 .Fl Z
 zooms the pane.
 The following keys may be used in buffer mode:
@@ -5734,7 +5758,9 @@ specifies an initial filter: the filter is a format - if it evaluates to zero,
 the item in the list is not shown, otherwise it is shown.
 If a filter would lead to an empty list, it is ignored.
 .Fl F
-specifies the format for each item in the list.
+specifies the format for each item in the list and
+.Fl K
+a format for each shortcut key; both are evaluated once for each line.
 .Fl N
 starts without the preview.
 This command works only if at least one client is attached.
diff --git a/tmux.h b/tmux.h
index 0c7636f8..9160ef92 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2855,6 +2855,7 @@ typedef void (*mode_tree_draw_cb)(void *, void *, struct screen_write_ctx *,
 typedef int (*mode_tree_search_cb)(void *, void *, const char *);
 typedef void (*mode_tree_menu_cb)(void *, struct client *, key_code);
 typedef u_int (*mode_tree_height_cb)(void *, u_int);
+typedef key_code (*mode_tree_key_cb)(void *, void *, u_int);
 typedef void (*mode_tree_each_cb)(void *, void *, struct client *, key_code);
 u_int	 mode_tree_count_tagged(struct mode_tree_data *);
 void	*mode_tree_get_current(struct mode_tree_data *);
@@ -2869,7 +2870,7 @@ void	 mode_tree_up(struct mode_tree_data *, int);
 void	 mode_tree_down(struct mode_tree_data *, int);
 struct mode_tree_data *mode_tree_start(struct window_pane *, struct args *,
 	     mode_tree_build_cb, mode_tree_draw_cb, mode_tree_search_cb,
-	     mode_tree_menu_cb, mode_tree_height_cb, void *,
+	     mode_tree_menu_cb, mode_tree_height_cb, mode_tree_key_cb, void *,
 	     const struct menu_item *, const char **, u_int, struct screen **);
 void	 mode_tree_zoom(struct mode_tree_data *, struct args *);
 void	 mode_tree_build(struct mode_tree_data *);
diff --git a/window-buffer.c b/window-buffer.c
index 4599cbc5..5e4dd699 100644
--- a/window-buffer.c
+++ b/window-buffer.c
@@ -41,6 +41,17 @@ static void		 window_buffer_key(struct window_mode_entry *,
 #define WINDOW_BUFFER_DEFAULT_FORMAT \
 	"#{t/p:buffer_created}: #{buffer_sample}"
 
+#define WINDOW_BUFFER_DEFAULT_KEY_FORMAT \
+	"#{?#{e|<:#{line},10}," \
+		"#{line}" \
+	"," \
+		"#{?#{e|<:#{line},36},"	\
+	        	"M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+		"," \
+	        	"" \
+		"}" \
+	"}"
+
 static const struct menu_item window_buffer_menu_items[] = {
 	{ "Paste", 'p', NULL },
 	{ "Paste Tagged", 'P', NULL },
@@ -93,6 +104,7 @@ struct window_buffer_modedata {
 	struct mode_tree_data		 *data;
 	char				 *command;
 	char				 *format;
+	char				 *key_format;
 
 	struct window_buffer_itemdata	**item_list;
 	u_int				  item_size;
@@ -232,7 +244,8 @@ window_buffer_draw(__unused void *modedata, void *itemdata,
 		while (end != pdata + psize && *end != '\n')
 			end++;
 		buf = xreallocarray(buf, 4, end - start + 1);
-		utf8_strvis(buf, start, end - start, VIS_OCTAL|VIS_CSTYLE|VIS_TAB);
+		utf8_strvis(buf, start, end - start,
+		    VIS_OCTAL|VIS_CSTYLE|VIS_TAB);
 		if (*buf != '\0') {
 			screen_write_cursormove(ctx, cx, cy + i, 0);
 			screen_write_nputs(ctx, sx, &grid_default_cell, "%s",
@@ -275,6 +288,41 @@ window_buffer_menu(void *modedata, struct client *c, key_code key)
 	window_buffer_key(wme, c, NULL, NULL, key, NULL);
 }
 
+static key_code
+window_buffer_get_key(void *modedata, void *itemdata, u_int line)
+{
+	struct window_buffer_modedata	*data = modedata;
+	struct window_buffer_itemdata	*item = itemdata;
+	struct format_tree		*ft;
+	struct session			*s;
+	struct winlink			*wl;
+	struct window_pane		*wp;
+	struct paste_buffer		*pb;
+	char				*expanded;
+	key_code			 key;
+
+	if (cmd_find_valid_state(&data->fs)) {
+		s = data->fs.s;
+		wl = data->fs.wl;
+		wp = data->fs.wp;
+	}
+	pb = paste_get_name(item->name);
+	if (pb == NULL)
+		return KEYC_NONE;
+
+	ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+	format_defaults(ft, NULL, NULL, 0, NULL);
+	format_defaults(ft, NULL, s, wl, wp);
+	format_defaults_paste_buffer(ft, pb);
+	format_add(ft, "line", "%u", line);
+
+	expanded = format_expand(ft, data->key_format);
+	key = key_string_lookup_string(expanded);
+	free(expanded);
+	format_free(ft);
+	return key;
+}
+
 static struct screen *
 window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
     struct args *args)
@@ -291,6 +339,10 @@ window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
 		data->format = xstrdup(WINDOW_BUFFER_DEFAULT_FORMAT);
 	else
 		data->format = xstrdup(args_get(args, 'F'));
+	if (args == NULL || !args_has(args, 'K'))
+		data->key_format = xstrdup(WINDOW_BUFFER_DEFAULT_KEY_FORMAT);
+	else
+		data->key_format = xstrdup(args_get(args, 'K'));
 	if (args == NULL || args->argc == 0)
 		data->command = xstrdup(WINDOW_BUFFER_DEFAULT_COMMAND);
 	else
@@ -298,8 +350,8 @@ window_buffer_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
 
 	data->data = mode_tree_start(wp, args, window_buffer_build,
 	    window_buffer_draw, window_buffer_search, window_buffer_menu, NULL,
-	    data, window_buffer_menu_items, window_buffer_sort_list,
-	    nitems(window_buffer_sort_list), &s);
+	    window_buffer_get_key, data, window_buffer_menu_items,
+	    window_buffer_sort_list, nitems(window_buffer_sort_list), &s);
 	mode_tree_zoom(data->data, args);
 
 	mode_tree_build(data->data);
@@ -324,6 +376,7 @@ window_buffer_free(struct window_mode_entry *wme)
 	free(data->item_list);
 
 	free(data->format);
+	free(data->key_format);
 	free(data->command);
 
 	free(data);
diff --git a/window-client.c b/window-client.c
index ec3c646a..db7c6dcc 100644
--- a/window-client.c
+++ b/window-client.c
@@ -40,6 +40,17 @@ static void		 window_client_key(struct window_mode_entry *,
 #define WINDOW_CLIENT_DEFAULT_FORMAT \
 	"#{t/p:client_activity}: session #{session_name}"
 
+#define WINDOW_CLIENT_DEFAULT_KEY_FORMAT \
+	"#{?#{e|<:#{line},10}," \
+		"#{line}" \
+	"," \
+		"#{?#{e|<:#{line},36},"	\
+	        	"M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+		"," \
+	        	"" \
+		"}" \
+	"}"
+
 static const struct menu_item window_client_menu_items[] = {
 	{ "Detach", 'd', NULL },
 	{ "Detach Tagged", 'D', NULL },
@@ -87,6 +98,7 @@ struct window_client_modedata {
 
 	struct mode_tree_data		 *data;
 	char				 *format;
+	char				 *key_format;
 	char				 *command;
 
 	struct window_client_itemdata	**item_list;
@@ -252,6 +264,26 @@ window_client_menu(void *modedata, struct client *c, key_code key)
 	window_client_key(wme, c, NULL, NULL, key, NULL);
 }
 
+static key_code
+window_client_get_key(void *modedata, void *itemdata, u_int line)
+{
+	struct window_client_modedata	*data = modedata;
+	struct window_client_itemdata	*item = itemdata;
+	struct format_tree		*ft;
+	char				*expanded;
+	key_code			 key;
+
+	ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+	format_defaults(ft, item->c, NULL, 0, NULL);
+	format_add(ft, "line", "%u", line);
+
+	expanded = format_expand(ft, data->key_format);
+	key = key_string_lookup_string(expanded);
+	free(expanded);
+	format_free(ft);
+	return key;
+}
+
 static struct screen *
 window_client_init(struct window_mode_entry *wme,
     __unused struct cmd_find_state *fs, struct args *args)
@@ -267,15 +299,19 @@ window_client_init(struct window_mode_entry *wme,
 		data->format = xstrdup(WINDOW_CLIENT_DEFAULT_FORMAT);
 	else
 		data->format = xstrdup(args_get(args, 'F'));
+	if (args == NULL || !args_has(args, 'K'))
+		data->key_format = xstrdup(WINDOW_CLIENT_DEFAULT_KEY_FORMAT);
+	else
+		data->key_format = xstrdup(args_get(args, 'K'));
 	if (args == NULL || args->argc == 0)
 		data->command = xstrdup(WINDOW_CLIENT_DEFAULT_COMMAND);
 	else
 		data->command = xstrdup(args->argv[0]);
 
 	data->data = mode_tree_start(wp, args, window_client_build,
-	    window_client_draw, NULL, window_client_menu, NULL, data,
-	    window_client_menu_items, window_client_sort_list,
-	    nitems(window_client_sort_list), &s);
+	    window_client_draw, NULL, window_client_menu, NULL,
+	    window_client_get_key, data, window_client_menu_items,
+	    window_client_sort_list, nitems(window_client_sort_list), &s);
 	mode_tree_zoom(data->data, args);
 
 	mode_tree_build(data->data);
@@ -300,6 +336,7 @@ window_client_free(struct window_mode_entry *wme)
 	free(data->item_list);
 
 	free(data->format);
+	free(data->key_format);
 	free(data->command);
 
 	free(data);
diff --git a/window-customize.c b/window-customize.c
index a1f191b5..fce002fd 100644
--- a/window-customize.c
+++ b/window-customize.c
@@ -890,8 +890,8 @@ window_customize_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
 
 	data->data = mode_tree_start(wp, args, window_customize_build,
 	    window_customize_draw, NULL, window_customize_menu,
-	    window_customize_height, data, window_customize_menu_items, NULL, 0,
-	    &s);
+	    window_customize_height, NULL, data, window_customize_menu_items,
+	    NULL, 0, &s);
 	mode_tree_zoom(data->data, args);
 
 	mode_tree_build(data->data);
diff --git a/window-tree.c b/window-tree.c
index 1498b337..881896e7 100644
--- a/window-tree.c
+++ b/window-tree.c
@@ -56,6 +56,17 @@ static void		 window_tree_key(struct window_mode_entry *,
 		"}" \
 	"}"
 
+#define WINDOW_TREE_DEFAULT_KEY_FORMAT \
+	"#{?#{e|<:#{line},10}," \
+		"#{line}" \
+	"," \
+		"#{?#{e|<:#{line},36},"	\
+	        	"M-#{a:#{e|+:97,#{e|-:#{line},10}}}" \
+		"," \
+	        	"" \
+		"}" \
+	"}"
+
 static const struct menu_item window_tree_menu_items[] = {
 	{ "Select", '\r', NULL },
 	{ "Expand", KEYC_RIGHT, NULL },
@@ -117,6 +128,7 @@ struct window_tree_modedata {
 
 	struct mode_tree_data		 *data;
 	char				 *format;
+	char				 *key_format;
 	char				 *command;
 	int				  squash_groups;
 
@@ -856,6 +868,35 @@ window_tree_menu(void *modedata, struct client *c, key_code key)
 	window_tree_key(wme, c, NULL, NULL, key, NULL);
 }
 
+static key_code
+window_tree_get_key(void *modedata, void *itemdata, u_int line)
+{
+	struct window_tree_modedata	*data = modedata;
+	struct window_tree_itemdata	*item = itemdata;
+	struct format_tree		*ft;
+	struct session			*s;
+	struct winlink			*wl;
+	struct window_pane		*wp;
+	char				*expanded;
+	key_code			 key;
+
+	ft = format_create(NULL, NULL, FORMAT_NONE, 0);
+	window_tree_pull_item(item, &s, &wl, &wp);
+	if (item->type == WINDOW_TREE_SESSION)
+		format_defaults(ft, NULL, s, NULL, NULL);
+	else if (item->type == WINDOW_TREE_WINDOW)
+		format_defaults(ft, NULL, s, wl, NULL);
+	else
+		format_defaults(ft, NULL, s, wl, wp);
+	format_add(ft, "line", "%u", line);
+
+	expanded = format_expand(ft, data->key_format);
+	key = key_string_lookup_string(expanded);
+	free(expanded);
+	format_free(ft);
+	return key;
+}
+
 static struct screen *
 window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
     struct args *args)
@@ -880,6 +921,10 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
 		data->format = xstrdup(WINDOW_TREE_DEFAULT_FORMAT);
 	else
 		data->format = xstrdup(args_get(args, 'F'));
+	if (args == NULL || !args_has(args, 'K'))
+		data->key_format = xstrdup(WINDOW_TREE_DEFAULT_KEY_FORMAT);
+	else
+		data->key_format = xstrdup(args_get(args, 'K'));
 	if (args == NULL || args->argc == 0)
 		data->command = xstrdup(WINDOW_TREE_DEFAULT_COMMAND);
 	else
@@ -887,9 +932,9 @@ window_tree_init(struct window_mode_entry *wme, struct cmd_find_state *fs,
 	data->squash_groups = !args_has(args, 'G');
 
 	data->data = mode_tree_start(wp, args, window_tree_build,
-	    window_tree_draw, window_tree_search, window_tree_menu, NULL, data,
-	    window_tree_menu_items, window_tree_sort_list,
-	    nitems(window_tree_sort_list), &s);
+	    window_tree_draw, window_tree_search, window_tree_menu, NULL,
+	    window_tree_get_key, data, window_tree_menu_items,
+	    window_tree_sort_list, nitems(window_tree_sort_list), &s);
 	mode_tree_zoom(data->data, args);
 
 	mode_tree_build(data->data);
@@ -913,6 +958,7 @@ window_tree_destroy(struct window_tree_modedata *data)
 	free(data->item_list);
 
 	free(data->format);
+	free(data->key_format);
 	free(data->command);
 
 	free(data);

From e6abe55134df1b9dc3b7dd7f3a65dff272a35bb7 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Mon, 12 Apr 2021 09:36:12 +0000
Subject: [PATCH 17/17] Add a flag to disable keys to close a message, GitHub
 issue 2625.

---
 alerts.c              |  9 +++++----
 cmd-display-message.c | 10 ++++++----
 cmd-list-keys.c       |  7 ++++---
 cmd-queue.c           |  2 +-
 cmd-run-shell.c       |  2 +-
 mode-tree.c           |  2 +-
 server-client.c       |  6 +++++-
 status.c              |  7 +++++--
 tmux.1                |  4 +++-
 tmux.h                |  3 ++-
 window-customize.c    |  4 ++--
 11 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/alerts.c b/alerts.c
index 0f2eb179..b5ea0cf4 100644
--- a/alerts.c
+++ b/alerts.c
@@ -315,10 +315,11 @@ alerts_set_message(struct winlink *wl, const char *type, const char *option)
 			tty_putcode(&c->tty, TTYC_BEL);
 		if (visual == VISUAL_OFF)
 			continue;
-		if (c->session->curw == wl)
-			status_message_set(c, -1, 1, "%s in current window", type);
-		else {
-			status_message_set(c, -1, 1, "%s in window %d", type,
+		if (c->session->curw == wl) {
+			status_message_set(c, -1, 1, 0, "%s in current window",
+			    type);
+		} else {
+			status_message_set(c, -1, 1, 0, "%s in window %d", type,
 			    wl->idx);
 		}
 	}
diff --git a/cmd-display-message.c b/cmd-display-message.c
index 301c1a5e..0522d37f 100644
--- a/cmd-display-message.c
+++ b/cmd-display-message.c
@@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = {
 	.name = "display-message",
 	.alias = "display",
 
-	.args = { "acd:Ipt:F:v", 0, 1 },
-	.usage = "[-aIpv] [-c target-client] [-d delay] [-F format] "
+	.args = { "acd:INpt:F:v", 0, 1 },
+	.usage = "[-aINpv] [-c target-client] [-d delay] [-F format] "
 		 CMD_TARGET_PANE_USAGE " [message]",
 
 	.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL },
@@ -132,8 +132,10 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item)
 		cmdq_error(item, "%s", msg);
 	else if (args_has(args, 'p'))
 		cmdq_print(item, "%s", msg);
-	else if (tc != NULL)
-		status_message_set(tc, delay, 0, "%s", msg);
+	else if (tc != NULL) {
+		status_message_set(tc, delay, 0, args_has(args, 'N'), "%s",
+		    msg);
+	}
 	free(msg);
 
 	format_free(ft);
diff --git a/cmd-list-keys.c b/cmd-list-keys.c
index dd82e57e..ca4bf752 100644
--- a/cmd-list-keys.c
+++ b/cmd-list-keys.c
@@ -113,9 +113,10 @@ cmd_list_keys_print_notes(struct cmdq_item *item, struct args *args,
 		else
 			note = xstrdup(bd->note);
 		tmp = utf8_padcstr(key, keywidth + 1);
-		if (args_has(args, '1') && tc != NULL)
-			status_message_set(tc, -1, 1, "%s%s%s", prefix, tmp, note);
-		else
+		if (args_has(args, '1') && tc != NULL) {
+			status_message_set(tc, -1, 1, 0, "%s%s%s", prefix, tmp,
+			    note);
+		} else
 			cmdq_print(item, "%s%s%s", prefix, tmp, note);
 		free(tmp);
 		free(note);
diff --git a/cmd-queue.c b/cmd-queue.c
index a0d80c34..54163919 100644
--- a/cmd-queue.c
+++ b/cmd-queue.c
@@ -862,7 +862,7 @@ cmdq_error(struct cmdq_item *item, const char *fmt, ...)
 		c->retval = 1;
 	} else {
 		*msg = toupper((u_char) *msg);
-		status_message_set(c, -1, 1, "%s", msg);
+		status_message_set(c, -1, 1, 0, "%s", msg);
 	}
 
 	free(msg);
diff --git a/cmd-run-shell.c b/cmd-run-shell.c
index 73ed79f4..56d5f723 100644
--- a/cmd-run-shell.c
+++ b/cmd-run-shell.c
@@ -193,7 +193,7 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
 		if (status == CMD_PARSE_ERROR) {
 			if (cdata->item == NULL) {
 				*error = toupper((u_char)*error);
-				status_message_set(c, -1, 1, "%s", error);
+				status_message_set(c, -1, 1, 0, "%s", error);
 			} else
 				cmdq_error(cdata->item, "%s", error);
 			free(error);
diff --git a/mode-tree.c b/mode-tree.c
index c0f85026..ca7f33a4 100644
--- a/mode-tree.c
+++ b/mode-tree.c
@@ -1204,7 +1204,7 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
 		if (status == CMD_PARSE_ERROR) {
 			if (c != NULL) {
 				*error = toupper((u_char)*error);
-				status_message_set(c, -1, 1, "%s", error);
+				status_message_set(c, -1, 1, 0, "%s", error);
 			}
 			free(error);
 		}
diff --git a/server-client.c b/server-client.c
index 219fdf3a..25d61376 100644
--- a/server-client.c
+++ b/server-client.c
@@ -1311,7 +1311,11 @@ server_client_handle_key(struct client *c, struct key_event *event)
 	 * immediately rather than queued.
 	 */
 	if (~c->flags & CLIENT_READONLY) {
-		status_message_clear(c);
+		if (c->message_string != NULL) {
+			if (c->message_ignore_keys)
+				return (0);
+			status_message_clear(c);
+		}
 		if (c->overlay_key != NULL) {
 			switch (c->overlay_key(c, event)) {
 			case 0:
diff --git a/status.c b/status.c
index 154d9452..f9786f4b 100644
--- a/status.c
+++ b/status.c
@@ -424,7 +424,7 @@ status_redraw(struct client *c)
 /* Set a status line message. */
 void
 status_message_set(struct client *c, int delay, int ignore_styles,
-    const char *fmt, ...)
+    int ignore_keys, const char *fmt, ...)
 {
 	struct timeval	tv;
 	va_list		ap;
@@ -433,7 +433,6 @@ status_message_set(struct client *c, int delay, int ignore_styles,
 	status_push_screen(c);
 
 	va_start(ap, fmt);
-	c->message_ignore_styles = ignore_styles;
 	xvasprintf(&c->message_string, fmt, ap);
 	va_end(ap);
 
@@ -456,6 +455,10 @@ status_message_set(struct client *c, int delay, int ignore_styles,
 		evtimer_add(&c->message_timer, &tv);
 	}
 
+	if (delay != 0)
+		c->message_ignore_keys = ignore_keys;
+	c->message_ignore_styles = ignore_styles;
+
 	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
 	c->flags |= CLIENT_REDRAWSTATUS;
 }
diff --git a/tmux.1 b/tmux.1
index 74cb5f00..ba82a101 100644
--- a/tmux.1
+++ b/tmux.1
@@ -5565,7 +5565,7 @@ The following keys are also available:
 .It Li "q" Ta "Exit menu"
 .El
 .It Xo Ic display-message
-.Op Fl aIpv
+.Op Fl aINpv
 .Op Fl c Ar target-client
 .Op Fl d Ar delay
 .Op Fl t Ar target-pane
@@ -5585,6 +5585,8 @@ If
 is not given, the
 .Ic message-time
 option is used; a delay of zero waits for a key press.
+.Ql N
+ignores key presses and closes only after the delay expires.
 The format of
 .Ar message
 is described in the
diff --git a/tmux.h b/tmux.h
index 9160ef92..fb2f35f9 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1687,6 +1687,7 @@ struct client {
 
 	uint64_t	 redraw_panes;
 
+	int		 message_ignore_keys;
 	int		 message_ignore_styles;
 	char		*message_string;
 	struct event	 message_timer;
@@ -2490,7 +2491,7 @@ struct style_range *status_get_range(struct client *, u_int, u_int);
 void	 status_init(struct client *);
 void	 status_free(struct client *);
 int	 status_redraw(struct client *);
-void status_message_set(struct client *, int, int, const char *, ...);
+void status_message_set(struct client *, int, int, int, const char *, ...);
 void	 status_message_clear(struct client *);
 int	 status_message_redraw(struct client *);
 void	 status_prompt_set(struct client *, struct cmd_find_state *,
diff --git a/window-customize.c b/window-customize.c
index fce002fd..34a13f73 100644
--- a/window-customize.c
+++ b/window-customize.c
@@ -999,7 +999,7 @@ window_customize_set_option_callback(struct client *c, void *itemdata,
 
 fail:
 	*cause = toupper((u_char)*cause);
-	status_message_set(c, -1, 1, "%s", cause);
+	status_message_set(c, -1, 1, 0, "%s", cause);
 	free(cause);
 	return (0);
 }
@@ -1205,7 +1205,7 @@ window_customize_set_command_callback(struct client *c, void *itemdata,
 
 fail:
 	*error = toupper((u_char)*error);
-	status_message_set(c, -1, 1, "%s", error);
+	status_message_set(c, -1, 1, 0, "%s", error);
 	free(error);
 	return (0);
 }