From 461217d0f0b347b688d6f41dcc997b984951aac1 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Fri, 6 Jan 2017 11:57:03 +0000
Subject: [PATCH] Incremental search in copy mode (on for emacs keys by
 default) - much the same as normal searching but updates the cursor position
 and marked search terms as you type. C-r and C-s in the prompt repeat the
 search, once finished searching (with Enter), N and n work as before.

---
 cmd-command-prompt.c |  43 +++++++----
 cmd-confirm-before.c |   4 +-
 cmd.c                |   2 +-
 key-bindings.c       |  64 ++++++++---------
 status.c             |  82 ++++++++++++---------
 tmux.1               |  13 ++--
 tmux.h               |   5 +-
 window-copy.c        | 167 +++++++++++++++++++++++++++++++++----------
 8 files changed, 256 insertions(+), 124 deletions(-)

diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 4c1c8593..cbe9c538 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -32,15 +32,15 @@
 static enum cmd_retval	cmd_command_prompt_exec(struct cmd *,
 			    struct cmdq_item *);
 
-static int	cmd_command_prompt_callback(void *, const char *);
+static int	cmd_command_prompt_callback(void *, const char *, int);
 static void	cmd_command_prompt_free(void *);
 
 const struct cmd_entry cmd_command_prompt_entry = {
 	.name = "command-prompt",
 	.alias = NULL,
 
-	.args = { "1I:Np:t:", 0, 1 },
-	.usage = "[-1N] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " "
+	.args = { "1iI:Np:t:", 0, 1 },
+	.usage = "[-1Ni] [-I inputs] [-p prompts] " CMD_TARGET_CLIENT_USAGE " "
 		 "[template]",
 
 	.tflag = CMD_CLIENT,
@@ -51,10 +51,14 @@ const struct cmd_entry cmd_command_prompt_entry = {
 
 struct cmd_command_prompt_cdata {
 	struct client	*c;
+	int		 flags;
+
 	char		*inputs;
 	char		*next_input;
-	char		*next_prompt;
+
 	char		*prompts;
+	char		*next_prompt;
+
 	char		*template;
 	int		 idx;
 };
@@ -73,14 +77,17 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
 	if (c->prompt_string != NULL)
 		return (CMD_RETURN_NORMAL);
 
-	cdata = xmalloc(sizeof *cdata);
+	cdata = xcalloc(1, sizeof *cdata);
 	cdata->c = c;
-	cdata->idx = 1;
+
 	cdata->inputs = NULL;
 	cdata->next_input = NULL;
-	cdata->next_prompt = NULL;
+
 	cdata->prompts = NULL;
+	cdata->next_prompt = NULL;
+
 	cdata->template = NULL;
+	cdata->idx = 1;
 
 	if (args->argc != 0)
 		cdata->template = xstrdup(args->argv[0]);
@@ -112,11 +119,13 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
 
 	flags = 0;
 	if (args_has(args, '1'))
-		flags |= PROMPT_SINGLE;
+		cdata->flags |= PROMPT_SINGLE;
 	else if (args_has(args, 'N'))
-		flags |= PROMPT_NUMERIC;
+		cdata->flags |= PROMPT_NUMERIC;
+	else if (args_has(args, 'i'))
+		cdata->flags |= PROMPT_INCREMENTAL;
 	status_prompt_set(c, prompt, input, cmd_command_prompt_callback,
-	    cmd_command_prompt_free, cdata, flags);
+	    cmd_command_prompt_free, cdata, cdata->flags);
 	free(prompt);
 
 	return (CMD_RETURN_NORMAL);
@@ -134,7 +143,7 @@ cmd_command_prompt_error(struct cmdq_item *item, void *data)
 }
 
 static int
-cmd_command_prompt_callback(void *data, const char *s)
+cmd_command_prompt_callback(void *data, const char *s, int done)
 {
 	struct cmd_command_prompt_cdata	*cdata = data;
 	struct client			*c = cdata->c;
@@ -145,16 +154,20 @@ cmd_command_prompt_callback(void *data, const char *s)
 
 	if (s == NULL)
 		return (0);
+	if (done && (cdata->flags & PROMPT_INCREMENTAL))
+		return (0);
 
 	new_template = cmd_template_replace(cdata->template, s, cdata->idx);
-	free(cdata->template);
-	cdata->template = new_template;
+	if (done) {
+		free(cdata->template);
+		cdata->template = new_template;
+	}
 
 	/*
 	 * Check if there are more prompts; if so, get its respective input
 	 * and update the prompt data.
 	 */
-	if ((ptr = strsep(&cdata->next_prompt, ",")) != NULL) {
+	if (done && (ptr = strsep(&cdata->next_prompt, ",")) != NULL) {
 		xasprintf(&prompt, "%s ", ptr);
 		input = strsep(&cdata->next_input, ",");
 		status_prompt_update(c, prompt, input);
@@ -178,6 +191,8 @@ cmd_command_prompt_callback(void *data, const char *s)
 	if (new_item != NULL)
 		cmdq_append(c, new_item);
 
+	if (!done)
+		free(new_template);
 	if (c->prompt_callbackfn != (void *)&cmd_command_prompt_callback)
 		return (1);
 	return (0);
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
index d468bbc3..a2ffd059 100644
--- a/cmd-confirm-before.c
+++ b/cmd-confirm-before.c
@@ -31,7 +31,7 @@
 static enum cmd_retval	cmd_confirm_before_exec(struct cmd *,
 			    struct cmdq_item *);
 
-static int	cmd_confirm_before_callback(void *, const char *);
+static int	cmd_confirm_before_callback(void *, const char *, int);
 static void	cmd_confirm_before_free(void *);
 
 const struct cmd_entry cmd_confirm_before_entry = {
@@ -96,7 +96,7 @@ cmd_confirm_before_error(struct cmdq_item *item, void *data)
 }
 
 static int
-cmd_confirm_before_callback(void *data, const char *s)
+cmd_confirm_before_callback(void *data, const char *s, __unused int done)
 {
 	struct cmd_confirm_before_data	*cdata = data;
 	struct client			*c = cdata->client;
diff --git a/cmd.c b/cmd.c
index 08242920..9791bee7 100644
--- a/cmd.c
+++ b/cmd.c
@@ -692,7 +692,7 @@ cmd_template_replace(const char *template, const char *s, int idx)
 
 			buf = xrealloc(buf, len + (strlen(s) * 2) + 1);
 			for (cp = s; *cp != '\0'; cp++) {
-				if (quoted && *cp == '"')
+				if (quoted && (*cp == '"' || *cp == '$'))
 					buf[len++] = '\\';
 				buf[len++] = *cp;
 			}
diff --git a/key-bindings.c b/key-bindings.c
index 25290a4a..838510c6 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -243,23 +243,23 @@ key_bindings_init(void)
 		"bind -Tcopy-mode C-k send -X copy-end-of-line",
 		"bind -Tcopy-mode C-n send -X cursor-down",
 		"bind -Tcopy-mode C-p send -X cursor-up",
-		"bind -Tcopy-mode C-r command-prompt -p'search up' \"send -X search-backward \\\"%%%\\\"\"",
-		"bind -Tcopy-mode C-s command-prompt -p'search down' \"send -X search-forward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode C-r command-prompt -ip'search up' 'send -X search-backward-incremental \"%%%\"'",
+		"bind -Tcopy-mode C-s command-prompt -ip'search down' 'send -X search-forward-incremental \"%%%\"'",
 		"bind -Tcopy-mode C-v send -X page-down",
 		"bind -Tcopy-mode C-w send -X copy-selection-and-cancel",
 		"bind -Tcopy-mode Escape send -X cancel",
 		"bind -Tcopy-mode Space send -X page-down",
 		"bind -Tcopy-mode , send -X jump-reverse",
 		"bind -Tcopy-mode \\; send -X jump-again",
-		"bind -Tcopy-mode F command-prompt -1p'jump backward' \"send -X jump-backward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode F command-prompt -1p'jump backward' 'send -X jump-backward \"%%%\"'",
 		"bind -Tcopy-mode N send -X search-reverse",
 		"bind -Tcopy-mode R send -X rectangle-toggle",
-		"bind -Tcopy-mode T command-prompt -1p'jump to backward' \"send -X jump-to-backward \\\"%%%\\\"\"",
-		"bind -Tcopy-mode f command-prompt -1p'jump forward' \"send -X jump-forward \\\"%%%\\\"\"",
-		"bind -Tcopy-mode g command-prompt -p'goto line' \"send -X goto-line '%%'\"",
+		"bind -Tcopy-mode T command-prompt -1p'jump to backward' 'send -X jump-to-backward \"%%%\"'",
+		"bind -Tcopy-mode f command-prompt -1p'jump forward' 'send -X jump-forward \"%%%\"'",
+		"bind -Tcopy-mode g command-prompt -p'goto line' 'send -X goto-line \"%%%\"'",
 		"bind -Tcopy-mode n send -X search-again",
 		"bind -Tcopy-mode q send -X cancel",
-		"bind -Tcopy-mode t command-prompt -1p'jump to forward' \"send -X jump-to-forward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode t command-prompt -1p'jump to forward' 'send -X jump-to-forward \"%%%\"'",
 		"bind -Tcopy-mode MouseDown1Pane select-pane",
 		"bind -Tcopy-mode MouseDrag1Pane select-pane\\; send -X begin-selection",
 		"bind -Tcopy-mode MouseDragEnd1Pane send -X copy-selection-and-cancel",
@@ -273,15 +273,15 @@ key_bindings_init(void)
 		"bind -Tcopy-mode Down send -X cursor-down",
 		"bind -Tcopy-mode Left send -X cursor-left",
 		"bind -Tcopy-mode Right send -X cursor-right",
-		"bind -Tcopy-mode M-1 command-prompt -Np'repeat' -I1 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-2 command-prompt -Np'repeat' -I2 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-3 command-prompt -Np'repeat' -I3 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-4 command-prompt -Np'repeat' -I4 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-5 command-prompt -Np'repeat' -I5 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-6 command-prompt -Np'repeat' -I6 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-7 command-prompt -Np'repeat' -I7 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-8 command-prompt -Np'repeat' -I8 \"send -N '%%'\"",
-		"bind -Tcopy-mode M-9 command-prompt -Np'repeat' -I9 \"send -N '%%'\"",
+		"bind -Tcopy-mode M-1 command-prompt -Np'repeat' -I1 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-2 command-prompt -Np'repeat' -I2 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-3 command-prompt -Np'repeat' -I3 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-4 command-prompt -Np'repeat' -I4 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-5 command-prompt -Np'repeat' -I5 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-6 command-prompt -Np'repeat' -I6 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-7 command-prompt -Np'repeat' -I7 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-8 command-prompt -Np'repeat' -I8 'send -N \"%%%\"'",
+		"bind -Tcopy-mode M-9 command-prompt -Np'repeat' -I9 'send -N \"%%%\"'",
 		"bind -Tcopy-mode M-< send -X history-top",
 		"bind -Tcopy-mode M-> send -X history-bottom",
 		"bind -Tcopy-mode M-R send -X top-line",
@@ -313,25 +313,25 @@ key_bindings_init(void)
 		"bind -Tcopy-mode-vi Space send -X begin-selection",
 		"bind -Tcopy-mode-vi '$' send -X end-of-line",
 		"bind -Tcopy-mode-vi , send -X jump-reverse",
-		"bind -Tcopy-mode-vi / command-prompt -p'search down' \"send -X search-forward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi / command-prompt -p'search down' 'send -X search-forward \"%%%\"'",
 		"bind -Tcopy-mode-vi 0 send -X start-of-line",
-		"bind -Tcopy-mode-vi 1 command-prompt -Np'repeat' -I1 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 2 command-prompt -Np'repeat' -I2 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 3 command-prompt -Np'repeat' -I3 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 4 command-prompt -Np'repeat' -I4 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 5 command-prompt -Np'repeat' -I5 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 6 command-prompt -Np'repeat' -I6 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 7 command-prompt -Np'repeat' -I7 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 8 command-prompt -Np'repeat' -I8 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi 9 command-prompt -Np'repeat' -I9 \"send -N '%%'\"",
-		"bind -Tcopy-mode-vi : command-prompt -p'goto line' \"send -X goto-line '%%'\"",
+		"bind -Tcopy-mode-vi 1 command-prompt -Np'repeat' -I1 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 2 command-prompt -Np'repeat' -I2 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 3 command-prompt -Np'repeat' -I3 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 4 command-prompt -Np'repeat' -I4 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 5 command-prompt -Np'repeat' -I5 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 6 command-prompt -Np'repeat' -I6 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 7 command-prompt -Np'repeat' -I7 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 8 command-prompt -Np'repeat' -I8 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi 9 command-prompt -Np'repeat' -I9 'send -N \"%%%\"'",
+		"bind -Tcopy-mode-vi : command-prompt -p'goto line' 'send -X goto-line \"%%%\"'",
 		"bind -Tcopy-mode-vi \\; send -X jump-again",
-		"bind -Tcopy-mode-vi ? command-prompt -p'search up' \"send -X search-backward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi ? command-prompt -p'search up' 'send -X search-backward \"%%%\"'",
 		"bind -Tcopy-mode-vi A send -X append-selection-and-cancel",
 		"bind -Tcopy-mode-vi B send -X previous-space",
 		"bind -Tcopy-mode-vi D send -X copy-end-of-line",
 		"bind -Tcopy-mode-vi E send -X next-space-end",
-		"bind -Tcopy-mode-vi F command-prompt -1p'jump backward' \"send -X jump-backward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi F command-prompt -1p'jump backward' 'send -X jump-backward \"%%%\"'",
 		"bind -Tcopy-mode-vi G send -X history-bottom",
 		"bind -Tcopy-mode-vi H send -X top-line",
 		"bind -Tcopy-mode-vi J send -X scroll-down",
@@ -339,13 +339,13 @@ key_bindings_init(void)
 		"bind -Tcopy-mode-vi L send -X bottom-line",
 		"bind -Tcopy-mode-vi M send -X middle-line",
 		"bind -Tcopy-mode-vi N send -X search-reverse",
-		"bind -Tcopy-mode-vi T command-prompt -1p'jump to backward' \"send -X jump-to-backward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi T command-prompt -1p'jump to backward' 'send -X jump-to-backward \"%%%\"'",
 		"bind -Tcopy-mode-vi V send -X select-line",
 		"bind -Tcopy-mode-vi W send -X next-space",
 		"bind -Tcopy-mode-vi ^ send -X back-to-indentation",
 		"bind -Tcopy-mode-vi b send -X previous-word",
 		"bind -Tcopy-mode-vi e send -X next-word-end",
-		"bind -Tcopy-mode-vi f command-prompt -1p'jump forward' \"send -X jump-forward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi f command-prompt -1p'jump forward' 'send -X jump-forward \"%%%\"'",
 		"bind -Tcopy-mode-vi g send -X history-top",
 		"bind -Tcopy-mode-vi h send -X cursor-left",
 		"bind -Tcopy-mode-vi j send -X cursor-down",
@@ -354,7 +354,7 @@ key_bindings_init(void)
 		"bind -Tcopy-mode-vi n send -X search-again",
 		"bind -Tcopy-mode-vi o send -X other-end",
 		"bind -Tcopy-mode-vi q send -X cancel",
-		"bind -Tcopy-mode-vi t command-prompt -1p'jump to forward' \"send -X jump-to-forward \\\"%%%\\\"\"",
+		"bind -Tcopy-mode-vi t command-prompt -1p'jump to forward' 'send -X jump-to-forward \"%%%\"'",
 		"bind -Tcopy-mode-vi v send -X rectangle-toggle",
 		"bind -Tcopy-mode-vi w send -X next-word",
 		"bind -Tcopy-mode-vi { send -X previous-paragraph",
diff --git a/status.c b/status.c
index ecb55380..c54d8f40 100644
--- a/status.c
+++ b/status.c
@@ -657,7 +657,7 @@ status_message_redraw(struct client *c)
 /* Enable status line prompt. */
 void
 status_prompt_set(struct client *c, const char *msg, const char *input,
-    int (*callbackfn)(void *, const char *), void (*freefn)(void *),
+    int (*callbackfn)(void *, const char *, int), void (*freefn)(void *),
     void *data, int flags)
 {
 	struct format_tree	*ft;
@@ -687,7 +687,8 @@ status_prompt_set(struct client *c, const char *msg, const char *input,
 	c->prompt_flags = flags;
 	c->prompt_mode = PROMPT_ENTRY;
 
-	c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
+	if (~flags & PROMPT_INCREMENTAL)
+		c->tty.flags |= (TTY_NOCURSOR|TTY_FREEZE);
 	c->flags |= CLIENT_STATUS;
 
 	free(tmp);
@@ -976,7 +977,7 @@ status_prompt_key(struct client *c, key_code key)
 {
 	struct options		*oo = c->session->options;
 	struct paste_buffer	*pb;
-	char			*s, word[64];
+	char			*s, *cp, word[64], prefix = '=';
 	const char		*histstr, *bufdata, *ws = NULL;
 	u_char			 ch;
 	size_t			 size, n, off, idx, bufsize, used;
@@ -989,7 +990,7 @@ status_prompt_key(struct client *c, key_code key)
 		if (key >= '0' && key <= '9')
 			goto append_key;
 		s = utf8_tocstr(c->prompt_buffer);
-		c->prompt_callbackfn(c->prompt_data, s);
+		c->prompt_callbackfn(c->prompt_data, s, 1);
 		status_prompt_clear(c);
 		free(s);
 		return (1);
@@ -1013,28 +1014,28 @@ process_key:
 	case '\002': /* C-b */
 		if (c->prompt_index > 0) {
 			c->prompt_index--;
-			c->flags |= CLIENT_STATUS;
+			break;
 		}
 		break;
 	case KEYC_RIGHT:
 	case '\006': /* C-f */
 		if (c->prompt_index < size) {
 			c->prompt_index++;
-			c->flags |= CLIENT_STATUS;
+			break;
 		}
 		break;
 	case KEYC_HOME:
 	case '\001': /* C-a */
 		if (c->prompt_index != 0) {
 			c->prompt_index = 0;
-			c->flags |= CLIENT_STATUS;
+			break;
 		}
 		break;
 	case KEYC_END:
 	case '\005': /* C-e */
 		if (c->prompt_index != size) {
 			c->prompt_index = size;
-			c->flags |= CLIENT_STATUS;
+			break;
 		}
 		break;
 	case '\011': /* Tab */
@@ -1094,8 +1095,7 @@ process_key:
 		c->prompt_index = (first - c->prompt_buffer) + strlen(s);
 		free(s);
 
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case KEYC_BSPACE:
 	case '\010': /* C-h */
 		if (c->prompt_index != 0) {
@@ -1108,7 +1108,7 @@ process_key:
 				    sizeof *c->prompt_buffer);
 				c->prompt_index--;
 			}
-			c->flags |= CLIENT_STATUS;
+			goto changed;
 		}
 		break;
 	case KEYC_DC:
@@ -1118,18 +1118,17 @@ process_key:
 			    c->prompt_buffer + c->prompt_index + 1,
 			    (size + 1 - c->prompt_index) *
 			    sizeof *c->prompt_buffer);
-			c->flags |= CLIENT_STATUS;
+			goto changed;
 		}
 		break;
 	case '\025': /* C-u */
 		c->prompt_buffer[0].size = 0;
 		c->prompt_index = 0;
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case '\013': /* C-k */
 		if (c->prompt_index < size) {
 			c->prompt_buffer[c->prompt_index].size = 0;
-			c->flags |= CLIENT_STATUS;
+			goto changed;
 		}
 		break;
 	case '\027': /* C-w */
@@ -1161,8 +1160,7 @@ process_key:
 		    '\0', (c->prompt_index - idx) * sizeof *c->prompt_buffer);
 		c->prompt_index = idx;
 
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case 'f'|KEYC_ESCAPE:
 		ws = options_get_string(oo, "word-separators");
 
@@ -1185,8 +1183,7 @@ process_key:
 		    c->prompt_index != 0)
 			c->prompt_index--;
 
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case 'b'|KEYC_ESCAPE:
 		ws = options_get_string(oo, "word-separators");
 
@@ -1206,9 +1203,7 @@ process_key:
 				break;
 			}
 		}
-
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case KEYC_UP:
 	case '\020': /* C-p */
 		histstr = status_prompt_up_history(&c->prompt_hindex);
@@ -1217,8 +1212,7 @@ process_key:
 		free(c->prompt_buffer);
 		c->prompt_buffer = utf8_fromcstr(histstr);
 		c->prompt_index = utf8_strlen(c->prompt_buffer);
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case KEYC_DOWN:
 	case '\016': /* C-n */
 		histstr = status_prompt_down_history(&c->prompt_hindex);
@@ -1227,8 +1221,7 @@ process_key:
 		free(c->prompt_buffer);
 		c->prompt_buffer = utf8_fromcstr(histstr);
 		c->prompt_index = utf8_strlen(c->prompt_buffer);
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case '\031': /* C-y */
 		if ((pb = paste_get_top(NULL)) == NULL)
 			break;
@@ -1259,9 +1252,7 @@ process_key:
 			}
 			c->prompt_index += n;
 		}
-
-		c->flags |= CLIENT_STATUS;
-		break;
+		goto changed;
 	case '\024': /* C-t */
 		idx = c->prompt_index;
 		if (idx < size)
@@ -1272,7 +1263,7 @@ process_key:
 			    &c->prompt_buffer[idx - 1]);
 			utf8_copy(&c->prompt_buffer[idx - 1], &tmp);
 			c->prompt_index = idx;
-			c->flags |= CLIENT_STATUS;
+			goto changed;
 		}
 		break;
 	case '\r':
@@ -1280,17 +1271,34 @@ process_key:
 		s = utf8_tocstr(c->prompt_buffer);
 		if (*s != '\0')
 			status_prompt_add_history(s);
-		if (c->prompt_callbackfn(c->prompt_data, s) == 0)
+		if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0)
 			status_prompt_clear(c);
 		free(s);
 		break;
 	case '\033': /* Escape */
 	case '\003': /* C-c */
-		if (c->prompt_callbackfn(c->prompt_data, NULL) == 0)
+		if (c->prompt_callbackfn(c->prompt_data, NULL, 1) == 0)
 			status_prompt_clear(c);
 		break;
+	case '\022': /* C-r */
+		if (c->prompt_flags & PROMPT_INCREMENTAL) {
+			prefix = '-';
+			goto changed;
+		}
+		break;
+	case '\023': /* C-s */
+		if (c->prompt_flags & PROMPT_INCREMENTAL) {
+			prefix = '+';
+			goto changed;
+		}
+		break;
+	default:
+		goto append_key;
 	}
 
+	c->flags |= CLIENT_STATUS;
+	return (0);
+
 append_key:
 	if (key <= 0x1f || key >= KEYC_BASE)
 		return (0);
@@ -1317,12 +1325,20 @@ append_key:
 		s = utf8_tocstr(c->prompt_buffer);
 		if (strlen(s) != 1)
 			status_prompt_clear(c);
-		else if (c->prompt_callbackfn(c->prompt_data, s) == 0)
+		else if (c->prompt_callbackfn(c->prompt_data, s, 1) == 0)
 			status_prompt_clear(c);
 		free(s);
 	}
 
+changed:
 	c->flags |= CLIENT_STATUS;
+	if (c->prompt_flags & PROMPT_INCREMENTAL) {
+		s = utf8_tocstr(c->prompt_buffer);
+		xasprintf(&cp, "%c%s", prefix, s);
+		c->prompt_callbackfn(c->prompt_data, cp, 0);
+		free(cp);
+		free(s);
+	}
 	return (0);
 }
 
diff --git a/tmux.1 b/tmux.1
index 9c47dee1..be61f01c 100644
--- a/tmux.1
+++ b/tmux.1
@@ -1020,7 +1020,7 @@ Key tables may be viewed with the
 command.
 .Pp
 The following commands are supported in copy mode:
-.Bl -column "CommandXXXXXXXXXXXXXXXXX" "viXXXXXXXXXX" "emacs" -offset indent
+.Bl -column "CommandXXXXXXXXXXXXXXXXXXXXXXXXXX" "viXXXXXXXXXX" "emacs" -offset indent
 .It Sy "Command" Ta Sy "vi" Ta Sy "emacs"
 .It Li "append-selection" Ta "" Ta ""
 .It Li "append-selection-and-cancel" Ta "A" Ta ""
@@ -1067,8 +1067,10 @@ The following commands are supported in copy mode:
 .It Li "scroll-down" Ta "C-e" Ta "C-Down"
 .It Li "scroll-up" Ta "C-y" Ta "C-Up"
 .It Li "search-again" Ta "n" Ta "n"
-.It Li "search-backward <for>" Ta "?" Ta "C-r"
-.It Li "search-forward <for>" Ta "/" Ta "C-s"
+.It Li "search-backward <for>" Ta "?" Ta ""
+.It Li "search-forward <for>" Ta "/" Ta ""
+.It Li "search-backward-incremental <for>" Ta "" Ta "C-r"
+.It Li "search-forward-incremental <for>" Ta "" Ta "C-s"
 .It Li "search-reverse" Ta "N" Ta "N"
 .It Li "select-line" Ta "V" Ta ""
 .It Li "start-of-line" Ta "0" Ta "C-a"
@@ -3730,7 +3732,7 @@ session option.
 Commands related to the status line are as follows:
 .Bl -tag -width Ds
 .It Xo Ic command-prompt
-.Op Fl 1
+.Op Fl 1i
 .Op Fl I Ar inputs
 .Op Fl p Ar prompts
 .Op Fl t Ar target-client
@@ -3780,6 +3782,9 @@ but any quotation marks are escaped.
 .Fl 1
 makes the prompt only accept one key press, in this case the resulting input
 is a single character.
+.Fl i
+executes the command every time the prompt input changes instead of when the
+user exits the command prompt.
 .Pp
 The following keys have a special meaning in the command prompt, depending
 on the value of the
diff --git a/tmux.h b/tmux.h
index 1b01d4f9..78f1d9a0 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1417,7 +1417,7 @@ struct client {
 	char		*prompt_string;
 	struct utf8_data *prompt_buffer;
 	size_t		 prompt_index;
-	int		 (*prompt_callbackfn)(void *, const char *);
+	int		 (*prompt_callbackfn)(void *, const char *, int);
 	void		 (*prompt_freefn)(void *);
 	void		*prompt_data;
 	u_int		 prompt_hindex;
@@ -1425,6 +1425,7 @@ struct client {
 
 #define PROMPT_SINGLE 0x1
 #define PROMPT_NUMERIC 0x2
+#define PROMPT_INCREMENTAL 0x4
 	int		 prompt_flags;
 
 	struct session	*session;
@@ -1915,7 +1916,7 @@ void printflike(2, 3) status_message_set(struct client *, const char *, ...);
 void	 status_message_clear(struct client *);
 int	 status_message_redraw(struct client *);
 void	 status_prompt_set(struct client *, const char *, const char *,
-	     int (*)(void *, const char *), void (*)(void *), void *, int);
+	     int (*)(void *, const char *, int), void (*)(void *), void *, int);
 void	 status_prompt_clear(struct client *);
 int	 status_prompt_redraw(struct client *);
 int	 status_prompt_key(struct client *, key_code);
diff --git a/window-copy.c b/window-copy.c
index c4dcb615..2d19f6c6 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -50,14 +50,15 @@ static int	window_copy_search_lr(struct grid *, struct grid *, u_int *,
 static int	window_copy_search_rl(struct grid *, struct grid *, u_int *,
 		    u_int, u_int, u_int, int);
 static int	window_copy_search_marks(struct window_pane *, struct screen *);
+static void	window_copy_clear_marks(struct window_pane *);
 static void	window_copy_move_left(struct screen *, u_int *, u_int *);
 static void	window_copy_move_right(struct screen *, u_int *, u_int *);
 static int	window_copy_is_lowercase(const char *);
-static void	window_copy_search_jump(struct window_pane *, struct grid *,
+static int	window_copy_search_jump(struct window_pane *, struct grid *,
 		    struct grid *, u_int, u_int, u_int, int, int, int);
-static void	window_copy_search(struct window_pane *, int, int);
-static void	window_copy_search_up(struct window_pane *, int);
-static void	window_copy_search_down(struct window_pane *, int);
+static int	window_copy_search(struct window_pane *, int, int);
+static int	window_copy_search_up(struct window_pane *, int);
+static int	window_copy_search_down(struct window_pane *, int);
 static void	window_copy_goto_line(struct window_pane *, const char *);
 static void	window_copy_update_cursor(struct window_pane *, u_int, u_int);
 static void	window_copy_start_selection(struct window_pane *);
@@ -173,6 +174,9 @@ struct window_copy_mode_data {
 	int		 searchtype;
 	char		*searchstr;
 	bitstr_t        *searchmark;
+	int		 searchx;
+	int		 searchy;
+	int		 searcho;
 
 	int		 jumptype;
 	char		 jumpchar;
@@ -203,6 +207,7 @@ window_copy_init(struct window_pane *wp)
 	data->searchtype = WINDOW_COPY_OFF;
 	data->searchstr = NULL;
 	data->searchmark = NULL;
+	data->searchx = data->searchy = data->searcho = -1;
 
 	if (wp->fd != -1)
 		bufferevent_disable(wp->event, EV_READ|EV_WRITE);
@@ -487,6 +492,9 @@ window_copy_resize(struct window_pane *wp, u_int sx, u_int sy)
 
 	if (data->searchmark != NULL)
 		window_copy_search_marks(wp, NULL);
+	data->searchx = data->cx;
+	data->searchy = data->cy;
+	data->searcho = data->oy;
 
 	window_copy_redraw_screen(wp);
 }
@@ -507,7 +515,8 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 	struct screen			*sn = &data->screen;
 	const char			*command, *argument, *ws;
 	u_int				 np = wp->modeprefix;
-	int				 cancel = 0;
+	int				 cancel = 0, redraw = 0;
+	char				 prefix;
 
 	if (args->argc == 0)
 		return;
@@ -521,13 +530,13 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			if (s != NULL)
 				window_copy_append_selection(wp, NULL);
 			window_copy_clear_selection(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "append-selection-and-cancel") == 0) {
 			if (s != NULL)
 				window_copy_append_selection(wp, NULL);
 			window_copy_clear_selection(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 			cancel = 1;
 		}
 		if (strcmp(command, "back-to-indentation") == 0)
@@ -538,7 +547,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			else {
 				sn->sel.lineflag = LINE_SEL_NONE;
 				window_copy_start_selection(wp);
-				window_copy_redraw_screen(wp);
+				redraw = 1;
 			}
 		}
 		if (strcmp(command, "stop-selection") == 0)
@@ -547,20 +556,20 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			data->cx = 0;
 			data->cy = screen_size_y(sn) - 1;
 			window_copy_update_selection(wp, 1);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "cancel") == 0)
 			cancel = 1;
 		if (strcmp(command, "clear-selection") == 0) {
 			window_copy_clear_selection(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "copy-end-of-line") == 0) {
 			window_copy_start_selection(wp);
 			for (; np > 1; np--)
 				window_copy_cursor_down(wp, 0);
 			window_copy_cursor_end_of_line(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 
 			if (s != NULL) {
 				window_copy_copy_selection(wp, NULL);
@@ -573,7 +582,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			for (; np > 1; np--)
 				window_copy_cursor_down(wp, 0);
 			window_copy_cursor_end_of_line(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 
 			if (s != NULL) {
 				window_copy_copy_selection(wp, NULL);
@@ -584,13 +593,13 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			if (s != NULL)
 				window_copy_copy_selection(wp, NULL);
 			window_copy_clear_selection(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "copy-selection-and-cancel") == 0) {
 			if (s != NULL)
 				window_copy_copy_selection(wp, NULL);
 			window_copy_clear_selection(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 			cancel = 1;
 		}
 		if (strcmp(command, "cursor-down") == 0) {
@@ -624,14 +633,14 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			data->cy = screen_size_y(sn) - 1;
 			data->oy = 0;
 			window_copy_update_selection(wp, 1);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "history-top") == 0) {
 			data->cx = 0;
 			data->cy = 0;
 			data->oy = screen_hsize(data->backing);
 			window_copy_update_selection(wp, 1);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "jump-again") == 0) {
 			switch (data->jumptype) {
@@ -677,7 +686,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			data->cx = 0;
 			data->cy = (screen_size_y(sn) - 1) / 2;
 			window_copy_update_selection(wp, 1);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "next-paragraph") == 0) {
 			for (; np != 0; np--)
@@ -766,7 +775,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			for (; np > 1; np--)
 				window_copy_cursor_down(wp, 0);
 			window_copy_cursor_end_of_line(wp);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "select-word") == 0) {
 			sn->sel.lineflag = LINE_SEL_LEFT_RIGHT;
@@ -775,7 +784,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			window_copy_cursor_previous_word(wp, ws);
 			window_copy_start_selection(wp);
 			window_copy_cursor_next_word_end(wp, ws);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 		if (strcmp(command, "start-of-line") == 0)
 			window_copy_cursor_start_of_line(wp);
@@ -783,7 +792,7 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 			data->cx = 0;
 			data->cy = 0;
 			window_copy_update_selection(wp, 1);
-			window_copy_redraw_screen(wp);
+			redraw = 1;
 		}
 	} else if (args->argc == 2 && *args->argv[1] != '\0') {
 		argument = args->argv[1];
@@ -825,26 +834,98 @@ window_copy_command(struct window_pane *wp, struct client *c, struct session *s,
 		}
 		if (strcmp(command, "search-backward") == 0) {
 			data->searchtype = WINDOW_COPY_SEARCHUP;
+			free(data->searchstr);
 			data->searchstr = xstrdup(argument);
 			for (; np != 0; np--)
 				window_copy_search_up(wp, 1);
 		}
 		if (strcmp(command, "search-forward") == 0) {
 			data->searchtype = WINDOW_COPY_SEARCHDOWN;
+			free(data->searchstr);
 			data->searchstr = xstrdup(argument);
 			for (; np != 0; np--)
 				window_copy_search_down(wp, 1);
 		}
+		if (strcmp(command, "search-backward-incremental") == 0) {
+			prefix = *argument++;
+			if (data->searchx == -1 || data->searchy == -1) {
+				data->searchx = data->cx;
+				data->searchy = data->cy;
+				data->searcho = data->oy;
+			} else if (data->searchstr != NULL &&
+			    strcmp(argument, data->searchstr) != 0) {
+				data->cx = data->searchx;
+				data->cy = data->searchy;
+				data->oy = data->searcho;
+				redraw = 1;
+			}
+			if (*argument == '\0') {
+				window_copy_clear_marks(wp);
+				redraw = 1;
+			} else if (prefix == '=' || prefix == '-') {
+				data->searchtype = WINDOW_COPY_SEARCHUP;
+				free(data->searchstr);
+				data->searchstr = xstrdup(argument);
+				if (!window_copy_search_up(wp, 1)) {
+					window_copy_clear_marks(wp);
+					redraw = 1;
+				}
+			} else if (prefix == '+') {
+				data->searchtype = WINDOW_COPY_SEARCHDOWN;
+				free(data->searchstr);
+				data->searchstr = xstrdup(argument);
+				if (!window_copy_search_down(wp, 1)) {
+					window_copy_clear_marks(wp);
+					redraw = 1;
+				}
+			}
+		}
+		if (strcmp(command, "search-forward-incremental") == 0) {
+			prefix = *argument++;
+			if (data->searchx == -1 || data->searchy == -1) {
+				data->searchx = data->cx;
+				data->searchy = data->cy;
+				data->searcho = data->oy;
+			} else if (data->searchstr != NULL &&
+			    strcmp(argument, data->searchstr) != 0) {
+				data->cx = data->searchx;
+				data->cy = data->searchy;
+				data->oy = data->searcho;
+				redraw = 1;
+			}
+			if (*argument == '\0') {
+				window_copy_clear_marks(wp);
+				redraw = 1;
+			} else if (prefix == '=' || prefix == '+') {
+				data->searchtype = WINDOW_COPY_SEARCHDOWN;
+				free(data->searchstr);
+				data->searchstr = xstrdup(argument);
+				if (!window_copy_search_down(wp, 1)) {
+					window_copy_clear_marks(wp);
+					redraw = 1;
+				}
+			} else if (prefix == '-') {
+				data->searchtype = WINDOW_COPY_SEARCHUP;
+				free(data->searchstr);
+				data->searchstr = xstrdup(argument);
+				if (!window_copy_search_up(wp, 1)) {
+					window_copy_clear_marks(wp);
+					redraw = 1;
+				}
+			}
+		}
 	}
 
 	if (strncmp(command, "search-", 7) != 0 && data->searchmark != NULL) {
-		free(data->searchmark);
-		data->searchmark = NULL;
-		window_copy_redraw_screen(wp);
+		window_copy_clear_marks(wp);
+		redraw = 1;
+		data->searchx = data->searchy = -1;
 	}
 
 	if (cancel)
 		window_pane_reset_mode(wp);
+	else if (redraw)
+		window_copy_redraw_screen(wp);
 	wp->modeprefix = 1;
 }
 
@@ -986,7 +1067,7 @@ window_copy_is_lowercase(const char *ptr)
  * up, down otherwise. If wrap then go to begin/end of grid and try again if
  * not found.
  */
-static void
+static int
 window_copy_search_jump(struct window_pane *wp, struct grid *gd,
     struct grid *sgd, u_int fx, u_int fy, u_int endline, int cis, int wrap,
     int direction)
@@ -1015,13 +1096,17 @@ window_copy_search_jump(struct window_pane *wp, struct grid *gd,
 		}
 	}
 
-	if (found)
+	if (found) {
 		window_copy_scroll_to(wp, px, i);
-	else if (wrap) {
-		window_copy_search_jump(wp, gd, sgd, direction ? 0 : gd->sx - 1,
-		    direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0,
-		    direction);
+		return (1);
 	}
+	if (wrap) {
+		return (window_copy_search_jump(wp, gd, sgd,
+		    direction ? 0 : gd->sx - 1,
+		    direction ? 0 : gd->hsize + gd->sy - 1, fy, cis, 0,
+		    direction));
+	}
+	return (0);
 }
 
 /*
@@ -1029,7 +1114,7 @@ window_copy_search_jump(struct window_pane *wp, struct grid *gd,
  * down. If moveflag is 0 then look for string at the current cursor position
  * as well.
  */
-static void
+static int
 window_copy_search(struct window_pane *wp, int direction, int moveflag)
 {
 	struct window_copy_mode_data	*data = wp->modedata;
@@ -1037,7 +1122,7 @@ window_copy_search(struct window_pane *wp, int direction, int moveflag)
 	struct screen_write_ctx		 ctx;
 	struct grid			*gd = s->grid;
 	u_int				 fx, fy, endline;
-	int				 wrapflag, cis;
+	int				 wrapflag, cis, found;
 
 	fx = data->cx;
 	fy = screen_hsize(data->backing) - data->oy + data->cy;
@@ -1062,13 +1147,14 @@ window_copy_search(struct window_pane *wp, int direction, int moveflag)
 		endline = gd->hsize + gd->sy - 1;
 	else
 		endline = 0;
-	window_copy_search_jump(wp, gd, ss.grid, fx, fy, endline, cis,
+	found = window_copy_search_jump(wp, gd, ss.grid, fx, fy, endline, cis,
 	    wrapflag, direction);
 
 	if (window_copy_search_marks(wp, &ss))
 		window_copy_redraw_screen(wp);
 
 	screen_free(&ss);
+	return (found);
 }
 
 static int
@@ -1119,15 +1205,24 @@ window_copy_search_marks(struct window_pane *wp, struct screen *ssp)
 }
 
 static void
-window_copy_search_up(struct window_pane *wp, int moveflag)
+window_copy_clear_marks(struct window_pane *wp)
 {
-	window_copy_search(wp, 0, moveflag);
+	struct window_copy_mode_data	*data = wp->modedata;
+
+	free(data->searchmark);
+	data->searchmark = NULL;
 }
 
-static void
+static int
+window_copy_search_up(struct window_pane *wp, int moveflag)
+{
+	return (window_copy_search(wp, 0, moveflag));
+}
+
+static int
 window_copy_search_down(struct window_pane *wp, int moveflag)
 {
-	window_copy_search(wp, 1, moveflag);
+	return (window_copy_search(wp, 1, moveflag));
 }
 
 static void