From 2fef10b9ac926de223e0e88b2d77489e983f62e5 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Fri, 5 May 2017 11:59:47 +0000
Subject: [PATCH 1/4] Add some formats to look at the session window stack,
 suggested by Scott ROCHFORD.

---
 format.c | 67 +++++++++++++++++++++++++++++++++++++++-----------------
 tmux.1   |  2 ++
 2 files changed, 49 insertions(+), 20 deletions(-)

diff --git a/format.c b/format.c
index 133ab779..b64af61e 100644
--- a/format.c
+++ b/format.c
@@ -42,25 +42,6 @@ typedef void (*format_cb)(struct format_tree *, struct format_entry *);
 static char	*format_job_get(struct format_tree *, const char *);
 static void	 format_job_timer(int, short, void *);
 
-static void	 format_cb_host(struct format_tree *, struct format_entry *);
-static void	 format_cb_host_short(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_pid(struct format_tree *, struct format_entry *);
-static void	 format_cb_session_alerts(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_window_layout(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_window_visible_layout(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_start_command(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_current_command(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_history_bytes(struct format_tree *,
-		     struct format_entry *);
-static void	 format_cb_pane_tabs(struct format_tree *,
-		     struct format_entry *);
-
 static char	*format_find(struct format_tree *, const char *, int);
 static void	 format_add_cb(struct format_tree *, const char *, format_cb);
 static void	 format_add_tv(struct format_tree *, const char *,
@@ -126,6 +107,7 @@ struct format_entry {
 /* Format entry tree. */
 struct format_tree {
 	struct window		*w;
+	struct winlink		*wl;
 	struct session		*s;
 	struct window_pane	*wp;
 
@@ -416,7 +398,7 @@ format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe)
 {
 	struct session	*s = ft->s;
 	struct winlink	*wl;
-	char		 alerts[256], tmp[16];
+	char		 alerts[1024], tmp[16];
 
 	if (s == NULL)
 		return;
@@ -440,6 +422,48 @@ format_cb_session_alerts(struct format_tree *ft, struct format_entry *fe)
 	fe->value = xstrdup(alerts);
 }
 
+/* Callback for session_stack. */
+static void
+format_cb_session_stack(struct format_tree *ft, struct format_entry *fe)
+{
+	struct session	*s = ft->s;
+	struct winlink	*wl;
+	char		 result[1024], tmp[16];
+
+	if (s == NULL)
+		return;
+
+	xsnprintf(result, sizeof result, "%u", s->curw->idx);
+	TAILQ_FOREACH(wl, &s->lastw, sentry) {
+		xsnprintf(tmp, sizeof tmp, "%u", wl->idx);
+
+		if (*result != '\0')
+			strlcat(result, ",", sizeof result);
+		strlcat(result, tmp, sizeof result);
+	}
+	fe->value = xstrdup(result);
+}
+
+/* Callback for window_stack_index. */
+static void
+format_cb_window_stack_index(struct format_tree *ft, struct format_entry *fe)
+{
+	struct session	*s = ft->wl->session;
+	struct winlink	*wl;
+	u_int		 idx;
+
+	idx = 0;
+	TAILQ_FOREACH(wl, &s->lastw, sentry) {
+		idx++;
+		if (wl == ft->wl)
+			break;
+	}
+	if (wl != NULL)
+		xasprintf(&fe->value, "%u", idx);
+	else
+		fe->value = xstrdup("0");
+}
+
 /* Callback for window_layout. */
 static void
 format_cb_window_layout(struct format_tree *ft, struct format_entry *fe)
@@ -1200,6 +1224,7 @@ format_defaults_session(struct format_tree *ft, struct session *s)
 	format_add(ft, "session_many_attached", "%d", s->attached > 1);
 
 	format_add_cb(ft, "session_alerts", format_cb_session_alerts);
+	format_add_cb(ft, "session_stack", format_cb_session_stack);
 }
 
 /* Set default format keys for a client. */
@@ -1286,10 +1311,12 @@ format_defaults_winlink(struct format_tree *ft, struct winlink *wl)
 
 	if (ft->w == NULL)
 		ft->w = wl->window;
+	ft->wl = wl;
 
 	format_defaults_window(ft, w);
 
 	format_add(ft, "window_index", "%d", wl->idx);
+	format_add_cb(ft, "window_stack_index", format_cb_window_stack_index);
 	format_add(ft, "window_flags", "%s", window_printable_flags(wl));
 	format_add(ft, "window_active", "%d", wl == s->curw);
 
diff --git a/tmux.1 b/tmux.1
index 23a28a1f..39e8d0df 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3586,6 +3586,7 @@ The following variables are available, where appropriate:
 .It Li "session_id" Ta "" Ta "Unique session ID"
 .It Li "session_many_attached" Ta "" Ta "1 if multiple clients attached"
 .It Li "session_name" Ta "#S" Ta "Name of session"
+.It Li "session_stack" Ta "" Ta "Window indexes in most recent order"
 .It Li "session_width" Ta "" Ta "Width of session"
 .It Li "session_windows" Ta "" Ta "Number of windows in session"
 .It Li "socket_path" Ta "" Ta "Server socket path"
@@ -3605,6 +3606,7 @@ The following variables are available, where appropriate:
 .It Li "window_name" Ta "#W" Ta "Name of window"
 .It Li "window_panes" Ta "" Ta "Number of panes in window"
 .It Li "window_silence_flag" Ta "" Ta "1 if window has silence alert"
+.It Li "window_stack_index" Ta "" Ta "Index in session most recent stack"
 .It Li "window_visible_layout" Ta "" Ta "Window layout description, respecting zoomed window panes"
 .It Li "window_width" Ta "" Ta "Width of window"
 .It Li "window_zoomed_flag" Ta "" Ta "1 if window is zoomed"

From d52f579fd5e7fd21d7dcf837780cbf98498b10ce Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Sun, 7 May 2017 21:25:59 +0000
Subject: [PATCH 2/4] Up to now, tmux sees \033\033[OA as M-Up and since we
 turned on xterm-keys by default, generates \033[1;3A instead of \033\033[OA.
 Unfortunately this confuses vi, which doesn't understand xterm keys and now
 sees Escape+Up pressed within escape-time as Escape followed by A.

The issue doesn't happen in xterm itself because it gets the keys from X
and can distinguish between a genuine M-Up and Escape+Up.

Because xterm can, tmux can too: xterm will give us \033[1;3A (that is,
kUP3) for a real M-Up and \033\033OA for Escape+Up - in fact, we can be
sure any \033 preceding an xterm key is a real Escape key press because
Meta would be part of the xterm key instead of a separate \033.

So change tmux to recognise both sequences as M-Up for its own purposes,
but generate the xterm version of M-Up only if it originally received
the xterm version from the terminal.

This means we will return to sending \033\033OA instead of the xterm key
for terminals that do not support xterm keys themselves, but there is no
practical way around this because they do not allow us to distinguish
between Escape+Up and M-Up. xterm style escape sequences are now the de
facto standard for these keys in any case.

Problem reported by jsing@ and subsequently by Cecile Tonglet in GitHub
issue 907.
---
 input-keys.c    |   1 +
 key-bindings.c  |   4 +-
 server-client.c |   2 +-
 tmux.h          |   3 +-
 tty-keys.c      | 152 ++++++++++++++++++++++++++----------------------
 xterm-keys.c    |  12 +++-
 6 files changed, 100 insertions(+), 74 deletions(-)

diff --git a/input-keys.c b/input-keys.c
index eb2879a9..01b00b05 100644
--- a/input-keys.c
+++ b/input-keys.c
@@ -201,6 +201,7 @@ input_key(struct window_pane *wp, key_code key, struct mouse_event *m)
 			return;
 		}
 	}
+	key &= ~KEYC_XTERM;
 
 	/* Otherwise look the key up in the table. */
 	for (i = 0; i < nitems(input_keys); i++) {
diff --git a/key-bindings.c b/key-bindings.c
index 60dbe544..9e327655 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -92,7 +92,7 @@ key_bindings_add(const char *name, key_code key, int repeat,
 
 	table = key_bindings_get_table(name, 1);
 
-	bd_find.key = key;
+	bd_find.key = (key & ~KEYC_XTERM);
 	bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
 	if (bd != NULL) {
 		RB_REMOVE(key_bindings, &table->key_bindings, bd);
@@ -119,7 +119,7 @@ key_bindings_remove(const char *name, key_code key)
 	if (table == NULL)
 		return;
 
-	bd_find.key = key;
+	bd_find.key = (key & ~KEYC_XTERM);
 	bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
 	if (bd == NULL)
 		return;
diff --git a/server-client.c b/server-client.c
index e9e26bef..72b5d312 100644
--- a/server-client.c
+++ b/server-client.c
@@ -904,7 +904,7 @@ retry:
 		log_debug("currently repeating");
 
 	/* Try to see if there is a key binding in the current table. */
-	bd_find.key = key;
+	bd_find.key = (key & ~KEYC_XTERM);
 	bd = RB_FIND(key_bindings, &table->key_bindings, &bd_find);
 	if (bd != NULL) {
 		/*
diff --git a/tmux.h b/tmux.h
index ac2991e8..378c937a 100644
--- a/tmux.h
+++ b/tmux.h
@@ -91,9 +91,10 @@ struct tmuxproc;
 #define KEYC_ESCAPE 0x200000000000ULL
 #define KEYC_CTRL   0x400000000000ULL
 #define KEYC_SHIFT  0x800000000000ULL
+#define KEYC_XTERM 0x1000000000000ULL
 
 /* Mask to obtain key w/o modifiers. */
-#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT)
+#define KEYC_MASK_MOD (KEYC_ESCAPE|KEYC_CTRL|KEYC_SHIFT|KEYC_XTERM)
 #define KEYC_MASK_KEY (~KEYC_MASK_MOD)
 
 /* Is this a mouse key? */
diff --git a/tty-keys.c b/tty-keys.c
index a011fcab..d7797a9c 100644
--- a/tty-keys.c
+++ b/tty-keys.c
@@ -257,67 +257,70 @@ static const struct tty_default_key_code tty_default_code_keys[] = {
 	{ TTYC_KCUB1, KEYC_LEFT },
 	{ TTYC_KCUF1, KEYC_RIGHT },
 
-	/* Key and modifier capabilities. */
-	{ TTYC_KDC2, KEYC_DC|KEYC_SHIFT },
-	{ TTYC_KDC3, KEYC_DC|KEYC_ESCAPE },
-	{ TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KDC5, KEYC_DC|KEYC_CTRL },
-	{ TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KDC7, KEYC_DC|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT },
-	{ TTYC_KDN3, KEYC_DOWN|KEYC_ESCAPE },
-	{ TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KDN5, KEYC_DOWN|KEYC_CTRL },
-	{ TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KDN7, KEYC_DOWN|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KEND2, KEYC_END|KEYC_SHIFT },
-	{ TTYC_KEND3, KEYC_END|KEYC_ESCAPE },
-	{ TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KEND5, KEYC_END|KEYC_CTRL },
-	{ TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KEND7, KEYC_END|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT },
-	{ TTYC_KHOM3, KEYC_HOME|KEYC_ESCAPE },
-	{ TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KHOM5, KEYC_HOME|KEYC_CTRL },
-	{ TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KHOM7, KEYC_HOME|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KIC2, KEYC_IC|KEYC_SHIFT },
-	{ TTYC_KIC3, KEYC_IC|KEYC_ESCAPE },
-	{ TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KIC5, KEYC_IC|KEYC_CTRL },
-	{ TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KIC7, KEYC_IC|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT },
-	{ TTYC_KLFT3, KEYC_LEFT|KEYC_ESCAPE },
-	{ TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL },
-	{ TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KLFT7, KEYC_LEFT|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT },
-	{ TTYC_KNXT3, KEYC_NPAGE|KEYC_ESCAPE },
-	{ TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL },
-	{ TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KNXT7, KEYC_NPAGE|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT },
-	{ TTYC_KPRV3, KEYC_PPAGE|KEYC_ESCAPE },
-	{ TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL },
-	{ TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KPRV7, KEYC_PPAGE|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT },
-	{ TTYC_KRIT3, KEYC_RIGHT|KEYC_ESCAPE },
-	{ TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL },
-	{ TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KRIT7, KEYC_RIGHT|KEYC_ESCAPE|KEYC_CTRL },
-	{ TTYC_KUP2, KEYC_UP|KEYC_SHIFT },
-	{ TTYC_KUP3, KEYC_UP|KEYC_ESCAPE },
-	{ TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_ESCAPE },
-	{ TTYC_KUP5, KEYC_UP|KEYC_CTRL },
-	{ TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL },
-	{ TTYC_KUP7, KEYC_UP|KEYC_ESCAPE|KEYC_CTRL },
+	/*
+	 * Key and modifier capabilities. We set the xterm flag to mark that
+	 * any leading escape means an escape key press and not the modifier.
+	 */
+	{ TTYC_KDC2, KEYC_DC|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KDC3, KEYC_DC|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KDC4, KEYC_DC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KDC5, KEYC_DC|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KDC6, KEYC_DC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KDC7, KEYC_DC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KDN2, KEYC_DOWN|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KDN3, KEYC_DOWN|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KDN4, KEYC_DOWN|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KDN5, KEYC_DOWN|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KDN6, KEYC_DOWN|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KDN7, KEYC_DOWN|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KEND2, KEYC_END|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KEND3, KEYC_END|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KEND4, KEYC_END|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KEND5, KEYC_END|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KEND6, KEYC_END|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KEND7, KEYC_END|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KHOM2, KEYC_HOME|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KHOM3, KEYC_HOME|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KHOM4, KEYC_HOME|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KHOM5, KEYC_HOME|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KHOM6, KEYC_HOME|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KHOM7, KEYC_HOME|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KIC2, KEYC_IC|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KIC3, KEYC_IC|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KIC4, KEYC_IC|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KIC5, KEYC_IC|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KIC6, KEYC_IC|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KIC7, KEYC_IC|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KLFT2, KEYC_LEFT|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KLFT3, KEYC_LEFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KLFT4, KEYC_LEFT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KLFT5, KEYC_LEFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KLFT6, KEYC_LEFT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KLFT7, KEYC_LEFT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KNXT2, KEYC_NPAGE|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KNXT3, KEYC_NPAGE|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KNXT4, KEYC_NPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KNXT5, KEYC_NPAGE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KNXT6, KEYC_NPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KNXT7, KEYC_NPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KPRV2, KEYC_PPAGE|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KPRV3, KEYC_PPAGE|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KPRV4, KEYC_PPAGE|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KPRV5, KEYC_PPAGE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KPRV6, KEYC_PPAGE|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KPRV7, KEYC_PPAGE|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KRIT2, KEYC_RIGHT|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KRIT3, KEYC_RIGHT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KRIT4, KEYC_RIGHT|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KRIT5, KEYC_RIGHT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KRIT6, KEYC_RIGHT|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KRIT7, KEYC_RIGHT|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KUP2, KEYC_UP|KEYC_SHIFT|KEYC_XTERM },
+	{ TTYC_KUP3, KEYC_UP|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KUP4, KEYC_UP|KEYC_SHIFT|KEYC_ESCAPE|KEYC_XTERM },
+	{ TTYC_KUP5, KEYC_UP|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KUP6, KEYC_UP|KEYC_SHIFT|KEYC_CTRL|KEYC_XTERM },
+	{ TTYC_KUP7, KEYC_UP|KEYC_ESCAPE|KEYC_CTRL|KEYC_XTERM },
 };
 
 /* Add key to tree. */
@@ -476,6 +479,7 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key,
 	enum utf8_state		 more;
 	u_int			 i;
 	wchar_t			 wc;
+	int			 n;
 
 	log_debug("%s: next key is %zu (%.*s) (expired=%d)", c->name, len,
 	    (int)len, buf, expired);
@@ -493,6 +497,13 @@ tty_keys_next1(struct tty *tty, const char *buf, size_t len, key_code *key,
 		return (0);
 	}
 
+	/* Is this an an xterm(1) key? */
+	n = xterm_keys_find(buf, len, size, key);
+	if (n == 0)
+		return (0);
+	if (n == 1 && !expired)
+		return (1);
+
 	/* Is this valid UTF-8? */
 	more = utf8_open(&ud, (u_char)*buf);
 	if (more == UTF8_MORE) {
@@ -573,6 +584,18 @@ first_key:
 		/* Look for a key without the escape. */
 		n = tty_keys_next1(tty, buf + 1, len - 1, &key, &size, expired);
 		if (n == 0) {	/* found */
+			if (key & KEYC_XTERM) {
+				/*
+				 * We want the escape key as well as the xterm
+				 * key, because the xterm sequence implicitly
+				 * includes the escape (so if we see
+				 * \033\033[1;3D we know it is an Escape
+				 * followed by M-Left, not just M-Left).
+				 */
+				key = '\033';
+				size = 1;
+				goto complete_key;
+			}
 			key |= KEYC_ESCAPE;
 			size++;
 			goto complete_key;
@@ -588,13 +611,6 @@ first_key:
 	if (n == 1)
 		goto partial_key;
 
-	/* Is this an an xterm(1) key? */
-	n = xterm_keys_find(buf, len, &size, &key);
-	if (n == 0)
-		goto complete_key;
-	if (n == 1 && !expired)
-		goto partial_key;
-
 	/*
 	 * At this point, we know the key is not partial (with or without
 	 * escape). So pass it through even if the timer has not expired.
diff --git a/xterm-keys.c b/xterm-keys.c
index 5ffc845f..b10c10db 100644
--- a/xterm-keys.c
+++ b/xterm-keys.c
@@ -197,7 +197,7 @@ xterm_keys_find(const char *buf, size_t len, size_t *size, key_code *key)
 		if (matched == -1)
 			continue;
 		if (matched == 0)
-			*key = entry->key | modifiers;
+			*key = (entry->key|modifiers|KEYC_XTERM);
 		return (matched);
 	}
 	return (-1);
@@ -227,8 +227,16 @@ xterm_keys_lookup(key_code key)
 	if (modifiers == 1)
 		return (NULL);
 
+	/*
+	 * If this has the escape modifier, but was not originally an xterm
+	 * key, it may be a genuine escape + key. So don't pass it through as
+	 * an xterm key or programs like vi may be confused.
+	 */
+	if ((key & (KEYC_ESCAPE|KEYC_XTERM)) == KEYC_ESCAPE)
+		return (NULL);
+
 	/* Otherwise, find the key in the table. */
-	key &= ~(KEYC_SHIFT|KEYC_ESCAPE|KEYC_CTRL);
+	key &= KEYC_MASK_KEY;
 	for (i = 0; i < nitems(xterm_keys_table); i++) {
 		entry = &xterm_keys_table[i];
 		if (key == entry->key)

From 5fee4638e08b1642a3b8882c5cf8825dd76b3a81 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Sun, 7 May 2017 22:27:57 +0000
Subject: [PATCH 3/4] Add a format for the name of the pane's mode, lets it be
 used as a conditional for key bindings.

---
 format.c        |  3 +++
 tmux.1          |  1 +
 tmux.h          | 19 +++++++++++--------
 window-choose.c |  2 ++
 window-clock.c  |  2 ++
 window-copy.c   |  2 ++
 6 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/format.c b/format.c
index b64af61e..d6bc9ceb 100644
--- a/format.c
+++ b/format.c
@@ -1371,6 +1371,9 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp)
 	}
 
 	format_add(ft, "pane_in_mode", "%d", wp->screen != &wp->base);
+	if (wp->mode != NULL)
+		format_add(ft, "pane_mode", "%s", wp->mode->name);
+
 	format_add(ft, "pane_synchronized", "%d",
 	    !!options_get_number(wp->window->options, "synchronize-panes"));
 	format_add(ft, "pane_search_string", "%s",
diff --git a/tmux.1 b/tmux.1
index 39e8d0df..96320f56 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3561,6 +3561,7 @@ The following variables are available, where appropriate:
 .It Li "pane_input_off" Ta "" Ta "If input to pane is disabled"
 .It Li "pane_index" Ta "#P" Ta "Index of pane"
 .It Li "pane_left" Ta "" Ta "Left of pane"
+.It Li "pane_mode" Ta "" Ta "Name of pane mode, if any."
 .It Li "pane_pid" Ta "" Ta "PID of first process in pane"
 .It Li "pane_right" Ta "" Ta "Right of pane"
 .It Li "pane_search_string" Ta "" Ta "Last search string in copy mode"
diff --git a/tmux.h b/tmux.h
index 378c937a..1b1343b7 100644
--- a/tmux.h
+++ b/tmux.h
@@ -689,15 +689,18 @@ struct screen_write_ctx {
  * right function to handle input and output.
  */
 struct window_mode {
-	struct screen *(*init)(struct window_pane *);
-	void	(*free)(struct window_pane *);
-	void	(*resize)(struct window_pane *, u_int, u_int);
-	void	(*key)(struct window_pane *, struct client *, struct session *,
-		    key_code, struct mouse_event *);
+	const char	*name;
 
-	const char *(*key_table)(struct window_pane *);
-	void	(*command)(struct window_pane *, struct client *,
-		    struct session *, struct args *, struct mouse_event *);
+	struct screen	*(*init)(struct window_pane *);
+	void		 (*free)(struct window_pane *);
+	void		 (*resize)(struct window_pane *, u_int, u_int);
+	void		 (*key)(struct window_pane *, struct client *,
+			     struct session *, key_code, struct mouse_event *);
+
+	const char	*(*key_table)(struct window_pane *);
+	void		 (*command)(struct window_pane *, struct client *,
+			     struct session *, struct args *,
+			     struct mouse_event *);
 };
 #define WINDOW_MODE_TIMEOUT 180
 
diff --git a/window-choose.c b/window-choose.c
index 0e2f1bf9..2f3a1631 100644
--- a/window-choose.c
+++ b/window-choose.c
@@ -57,6 +57,8 @@ enum window_choose_input_type {
 };
 
 const struct window_mode window_choose_mode = {
+	.name = "choose-mode",
+
 	.init = window_choose_init,
 	.free = window_choose_free,
 	.resize = window_choose_resize,
diff --git a/window-clock.c b/window-clock.c
index 94f014fc..d23ac136 100644
--- a/window-clock.c
+++ b/window-clock.c
@@ -34,6 +34,8 @@ static void	window_clock_timer_callback(int, short, void *);
 static void	window_clock_draw_screen(struct window_pane *);
 
 const struct window_mode window_clock_mode = {
+	.name = "clock-mode",
+
 	.init = window_clock_init,
 	.free = window_clock_free,
 	.resize = window_clock_resize,
diff --git a/window-copy.c b/window-copy.c
index 7def63db..8424bfa6 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -105,6 +105,8 @@ static void	window_copy_move_mouse(struct mouse_event *);
 static void	window_copy_drag_update(struct client *, struct mouse_event *);
 
 const struct window_mode window_copy_mode = {
+	.name = "copy-mode",
+
 	.init = window_copy_init,
 	.free = window_copy_free,
 	.resize = window_copy_resize,

From 18f36906a9e40ebcf9705fa9deb197bc4a1f813a Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Tue, 9 May 2017 11:00:48 +0000
Subject: [PATCH 4/4] Set current pane in rotate-window.

---
 cmd-rotate-window.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/cmd-rotate-window.c b/cmd-rotate-window.c
index 3c15b54e..7a1d9b28 100644
--- a/cmd-rotate-window.c
+++ b/cmd-rotate-window.c
@@ -43,6 +43,7 @@ const struct cmd_entry cmd_rotate_window_entry = {
 static enum cmd_retval
 cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
 {
+	struct cmd_find_state	*current = &item->shared->current;
 	struct winlink		*wl = item->target.wl;
 	struct window		*w = wl->window;
 	struct window_pane	*wp, *wp2;
@@ -77,6 +78,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
 		if ((wp = TAILQ_PREV(w->active, window_panes, entry)) == NULL)
 			wp = TAILQ_LAST(&w->panes, window_panes);
 		window_set_active_pane(w, wp);
+		cmd_find_from_winlink_pane(current, wl, wp);
 		server_redraw_window(w);
 	} else {
 		wp = TAILQ_FIRST(&w->panes);
@@ -104,6 +106,7 @@ cmd_rotate_window_exec(struct cmd *self, struct cmdq_item *item)
 		if ((wp = TAILQ_NEXT(w->active, entry)) == NULL)
 			wp = TAILQ_FIRST(&w->panes);
 		window_set_active_pane(w, wp);
+		cmd_find_from_winlink_pane(current, wl, wp);
 		server_redraw_window(w);
 	}