diff --git a/Makefile.am b/Makefile.am
index ceaae6ad..94b1564b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -88,7 +88,6 @@ dist_tmux_SOURCES = \
 	cmd-list-panes.c \
 	cmd-list-sessions.c \
 	cmd-list-windows.c \
-	cmd-list.c \
 	cmd-load-buffer.c \
 	cmd-lock-server.c \
 	cmd-move-window.c \
@@ -152,6 +151,7 @@ dist_tmux_SOURCES = \
 	options.c \
 	paste.c \
 	proc.c \
+	regsub.c \
 	resize.c \
 	screen-redraw.c \
 	screen-write.c \
diff --git a/cmd-load-buffer.c b/cmd-load-buffer.c
index 47cb0ca2..3e669093 100644
--- a/cmd-load-buffer.c
+++ b/cmd-load-buffer.c
@@ -93,7 +93,7 @@ cmd_load_buffer_exec(struct cmd *self, struct cmdq_item *item)
 		return (CMD_RETURN_WAIT);
 	}
 
-	file = server_client_get_path(c, path);
+	file = server_client_get_path(item->client, path);
 	free(path);
 
 	f = fopen(file, "rb");
diff --git a/cmd-parse.y b/cmd-parse.y
index a7c12f62..1dbc27a7 100644
--- a/cmd-parse.y
+++ b/cmd-parse.y
@@ -59,6 +59,7 @@ struct cmd_parse_state {
 	size_t				 len;
 	size_t				 off;
 
+	int				 condition;
 	int				 eol;
 	int				 eof;
 	struct cmd_parse_input		*input;
@@ -104,7 +105,7 @@ static void	 cmd_parse_print_commands(struct cmd_parse_input *, u_int,
 %token ENDIF
 %token <token> FORMAT TOKEN EQUALS
 
-%type <token> argument expanded
+%type <token> argument expanded format
 %type <arguments> arguments
 %type <flag> if_open if_elif
 %type <elif> elif elif1
@@ -160,7 +161,16 @@ statement	: condition
 			}
 		}
 
-expanded	: FORMAT
+format		: FORMAT
+		{
+			$$ = $1;
+		}
+		| TOKEN
+		{
+			$$ = $1;
+		}
+
+expanded	: format
 		{
 			struct cmd_parse_state	*ps = &parse_state;
 			struct cmd_parse_input	*pi = ps->input;
@@ -507,7 +517,10 @@ cmd_parse_print_commands(struct cmd_parse_input *pi, u_int line,
 
 	if (pi->item != NULL && (pi->flags & CMD_PARSE_VERBOSE)) {
 		s = cmd_list_print(cmdlist, 0);
-		cmdq_print(pi->item, "%u: %s", line, s);
+		if (pi->file != NULL)
+			cmdq_print(pi->item, "%s:%u: %s", pi->file, line, s);
+		else
+			cmdq_print(pi->item, "%u: %s", line, s);
 		free(s);
 	}
 }
@@ -967,12 +980,15 @@ yylex(void)
 {
 	struct cmd_parse_state	*ps = &parse_state;
 	char			*token, *cp;
-	int			 ch, next;
+	int			 ch, next, condition;
 
 	if (ps->eol)
 		ps->input->line++;
 	ps->eol = 0;
 
+	condition = ps->condition;
+	ps->condition = 0;
+
 	for (;;) {
 		ch = yylex_getc();
 
@@ -1012,11 +1028,11 @@ yylex(void)
 
 		if (ch == '#') {
 			/*
-			 * #{ opens a format; anything else is a comment,
-			 * ignore up to the end of the line.
+			 * #{ after a condition opens a format; anything else
+			 * is a comment, ignore up to the end of the line.
 			 */
 			next = yylex_getc();
-			if (next == '{') {
+			if (condition && next == '{') {
 				yylval.token = yylex_format();
 				if (yylval.token == NULL)
 					return (ERROR);
@@ -1043,6 +1059,7 @@ yylex(void)
 			}
 			if (*cp == '\0')
 				return (TOKEN);
+			ps->condition = 1;
 			if (strcmp(yylval.token, "%if") == 0) {
 				free(yylval.token);
 				return (IF);
diff --git a/cmd-save-buffer.c b/cmd-save-buffer.c
index 938bb3d6..3395612f 100644
--- a/cmd-save-buffer.c
+++ b/cmd-save-buffer.c
@@ -104,7 +104,7 @@ cmd_save_buffer_exec(struct cmd *self, struct cmdq_item *item)
 	if (args_has(self->args, 'a'))
 		flags = "ab";
 
-	file = server_client_get_path(c, path);
+	file = server_client_get_path(item->client, path);
 	free(path);
 
 	f = fopen(file, flags);
diff --git a/format.c b/format.c
index 5ca970b7..8126bfb2 100644
--- a/format.c
+++ b/format.c
@@ -23,6 +23,7 @@
 #include <errno.h>
 #include <fnmatch.h>
 #include <libgen.h>
+#include <regex.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
@@ -1112,7 +1113,7 @@ format_find(struct format_tree *ft, const char *key, int modifiers)
 			envent = environ_find(ft->s->environ, key);
 		if (envent == NULL)
 			envent = environ_find(global_environ, key);
-		if (envent != NULL) {
+		if (envent != NULL && envent->value != NULL) {
 			found = xstrdup(envent->value);
 			goto found;
 		}
@@ -1263,7 +1264,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count)
 			cp++;
 
 		/* Check single character modifiers with no arguments. */
-		if (strchr("lmCbdtqETSWP<>", cp[0]) != NULL &&
+		if (strchr("lbdtqETSWP<>", cp[0]) != NULL &&
 		    format_is_end(cp[1])) {
 			format_add_modifier(&list, count, cp, 1, NULL, 0);
 			cp++;
@@ -1284,7 +1285,7 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count)
 		}
 
 		/* Now try single character with arguments. */
-		if (strchr("s=", cp[0]) == NULL)
+		if (strchr("mCs=", cp[0]) == NULL)
 			break;
 		c = cp[0];
 
@@ -1345,39 +1346,67 @@ format_build_modifiers(struct format_tree *ft, const char **s, u_int *count)
 	return list;
 }
 
+/* Match against an fnmatch(3) pattern or regular expression. */
+static char *
+format_match(struct format_modifier *fm, const char *pattern, const char *text)
+{
+	const char	*s = "";
+	regex_t		 r;
+	int		 flags = 0;
+
+	if (fm->argc >= 1)
+		s = fm->argv[0];
+	if (strchr(s, 'r') == NULL) {
+		if (strchr(s, 'i') != NULL)
+			flags |= FNM_CASEFOLD;
+		if (fnmatch(pattern, text, flags) != 0)
+			return (xstrdup("0"));
+	} else {
+		flags = REG_EXTENDED|REG_NOSUB;
+		if (strchr(s, 'i') != NULL)
+			flags |= REG_ICASE;
+		if (regcomp(&r, pattern, flags) != 0)
+			return (xstrdup("0"));
+		if (regexec(&r, text, 0, NULL, 0) != 0) {
+			regfree(&r);
+			return (xstrdup("0"));
+		}
+		regfree(&r);
+	}
+	return (xstrdup("1"));
+}
+
 /* Perform substitution in string. */
 static char *
-format_substitute(const char *source, const char *from, const char *to)
+format_sub(struct format_modifier *fm, const char *text, const char *pattern,
+    const char *with)
 {
-	char		*copy, *new;
-	const char	*cp;
-	size_t		 fromlen, tolen, newlen, used;
+	char	*value;
+	int	 flags = REG_EXTENDED;
 
-	fromlen = strlen(from);
-	tolen = strlen(to);
+	if (fm->argc >= 3 && strchr(fm->argv[2], 'i') != NULL)
+		flags |= REG_ICASE;
+	value = regsub(pattern, with, text, flags);
+	if (value == NULL)
+		return (xstrdup(text));
+	return (value);
+}
 
-	newlen = strlen(source) + 1;
-	copy = new = xmalloc(newlen);
+/* Search inside pane. */
+static char *
+format_search(struct format_modifier *fm, struct window_pane *wp, const char *s)
+{
+	int	 ignore = 0, regex = 0;
+	char	*value;
 
-	for (cp = source; *cp != '\0'; /* nothing */) {
-		if (strncmp(cp, from, fromlen) != 0) {
-			*new++ = *cp++;
-			continue;
-		}
-		used = new - copy;
-
-		newlen += tolen;
-		copy = xrealloc(copy, newlen);
-
-		new = copy + used;
-		memcpy(new, to, tolen);
-
-		new += tolen;
-		cp += fromlen;
+	if (fm->argc >= 1) {
+		if (strchr(fm->argv[0], 'i') != NULL)
+			ignore = 1;
+		if (strchr(fm->argv[0], 'r') != NULL)
+			regex = 1;
 	}
-
-	*new = '\0';
-	return (copy);
+	xasprintf(&value, "%u", window_pane_search(wp, s, regex, ignore));
+	return (value);
 }
 
 /* Loop over sessions. */
@@ -1522,11 +1551,10 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen,
 	char			*copy0, *condition, *found, *new;
 	char			*value, *left, *right;
 	size_t			 valuelen;
-	int			 modifiers = 0, limit = 0;
+	int			 modifiers = 0, limit = 0, j;
 	struct format_modifier  *list, *fm, *cmp = NULL, *search = NULL;
 	struct format_modifier  *sub = NULL;
 	u_int			 i, count;
-	int			 j;
 
 	/* Make a copy of the key. */
 	copy = copy0 = xstrndup(key, keylen);
@@ -1553,18 +1581,18 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen,
 				search = fm;
 				break;
 			case 's':
-				if (fm->argc != 2)
+				if (fm->argc < 2)
 					break;
 				sub = fm;
 				break;
 			case '=':
-				if (fm->argc != 1 && fm->argc != 2)
+				if (fm->argc < 1)
 					break;
 				limit = strtonum(fm->argv[0], INT_MIN, INT_MAX,
 				    &errptr);
 				if (errptr != NULL)
 					limit = 0;
-				if (fm->argc == 2 && fm->argv[1] != NULL)
+				if (fm->argc >= 2 && fm->argv[1] != NULL)
 					marker = fm->argv[1];
 				break;
 			case 'l':
@@ -1635,7 +1663,7 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen,
 			value = xstrdup("0");
 		} else {
 			format_log(ft, "search '%s' pane %%%u", copy,  wp->id);
-			xasprintf(&value, "%u", window_pane_search(wp, copy));
+			value = format_search(fm, wp, copy);
 		}
 	} else if (cmp != NULL) {
 		/* Comparison of left and right. */
@@ -1687,12 +1715,8 @@ format_replace(struct format_tree *ft, const char *key, size_t keylen,
 				value = xstrdup("1");
 			else
 				value = xstrdup("0");
-		} else if (strcmp(cmp->modifier, "m") == 0) {
-			if (fnmatch(left, right, 0) == 0)
-				value = xstrdup("1");
-			else
-				value = xstrdup("0");
-		}
+		} else if (strcmp(cmp->modifier, "m") == 0)
+			value = format_match(fm, left, right);
 
 		free(right);
 		free(left);
@@ -1770,8 +1794,8 @@ done:
 
 	/* Perform substitution if any. */
 	if (sub != NULL) {
-		new = format_substitute(value, sub->argv[0], sub->argv[1]);
-		format_log(ft, "substituted '%s' to '%s: %s", sub->argv[0],
+		new = format_sub(sub, value, sub->argv[0], sub->argv[1]);
+		format_log(ft, "substituted '%s' to '%s': %s", sub->argv[0],
 		    sub->argv[1], new);
 		free(value);
 		value = new;
diff --git a/regsub.c b/regsub.c
new file mode 100644
index 00000000..53c83a8f
--- /dev/null
+++ b/regsub.c
@@ -0,0 +1,98 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <regex.h>
+#include <string.h>
+
+#include "tmux.h"
+
+static void
+regsub_copy(char **buf, size_t *len, const char *text, size_t start,
+    size_t end)
+{
+	size_t	add = end - start;
+
+	*buf = xrealloc(*buf, (*len) + add + 1);
+	memcpy((*buf) + *len, text + start, add);
+	(*len) += add;
+}
+
+static void
+regsub_expand(char **buf, size_t *len, const char *with, const char *text,
+    regmatch_t *m, u_int n)
+{
+	const char	*cp;
+	u_int		 i;
+
+	for (cp = with; *cp != '\0'; cp++) {
+		if (*cp == '\\') {
+			cp++;
+			if (*cp >= '0' && *cp <= '9') {
+				i = *cp - '0';
+				if (i < n && m[i].rm_so != m[i].rm_eo) {
+					regsub_copy(buf, len, text, m[i].rm_so,
+					    m[i].rm_eo);
+					continue;
+				}
+			}
+		}
+		*buf = xrealloc(*buf, (*len) + 2);
+		(*buf)[(*len)++] = *cp;
+	}
+}
+
+char *
+regsub(const char *pattern, const char *with, const char *text, int flags)
+{
+	regex_t		 r;
+	regmatch_t	 m[10];
+	size_t		 start, end, len = 0;
+	char		*buf = NULL;
+
+	if (*text == '\0')
+		return (xstrdup(""));
+	if (regcomp(&r, pattern, flags) != 0)
+		return (NULL);
+
+	start = 0;
+	end = strlen(text);
+
+	while (start != end) {
+		m[0].rm_so = start;
+		m[0].rm_eo = end;
+
+		if (regexec(&r, text, nitems(m), m, REG_STARTEND) != 0) {
+			regsub_copy(&buf, &len, text, start, end);
+			break;
+		}
+		if (m[0].rm_so == m[0].rm_eo) {
+			regsub_copy(&buf, &len, text, start, end);
+			break;
+		}
+
+		regsub_copy(&buf, &len, text, start, m[0].rm_so);
+		regsub_expand(&buf, &len, with, text, m, nitems(m));
+		start = m[0].rm_eo;
+	}
+	buf[len] = '\0';
+
+	regfree(&r);
+	return (buf);
+}
diff --git a/tmux.1 b/tmux.1
index fe27105d..9188ba47 100644
--- a/tmux.1
+++ b/tmux.1
@@ -587,9 +587,9 @@ or
 .Ql %endif .
 For example:
 .Bd -literal -offset indent
-%if #{==:#{host},myhost}
+%if "#{==:#{host},myhost}"
 set -g status-style bg=red
-%elif #{==:#{host},myotherhost}
+%elif "#{==:#{host},myotherhost}"
 set -g status-style bg=green
 %else
 set -g status-style bg=blue
@@ -4017,25 +4017,45 @@ if running on
 .Ql myhost ,
 otherwise by
 .Ql 0 .
-An
-.Ql m
-specifies an
-.Xr fnmatch 3
-comparison where the first argument is the pattern and the second the string to
-compare, for example
-.Ql #{m:*foo*,#{host}} .
 .Ql ||
 and
 .Ql &&
 evaluate to true if either or both of two comma-separated alternatives are
 true, for example
 .Ql #{||:#{pane_in_mode},#{alternate_on}} .
+.Pp
+An
+.Ql m
+specifies an
+.Xr fnmatch 3
+or regular expression comparison.
+The first argument is the pattern and the second the string to compare.
+An optional third argument specifies flags:
+.Ql r
+means the pattern is a regular expression instead of the default
+.Xr fnmatch 3
+pattern, and
+.Ql i
+means to ignore case.
+For example:
+.Ql #{m:*foo*,#{host}}
+or
+.Ql #{m/ri:^A,MYVAR} .
 A
 .Ql C
 performs a search for an
 .Xr fnmatch 3
-pattern in the pane content and evaluates to zero if not found, or a line
-number if found.
+pattern or regular expression in the pane content and evaluates to zero if not
+found, or a line number if found.
+Like
+.Ql m ,
+an
+.Ql r
+flag means search for a regular expression and
+.Ql i
+ignores case.
+For example:
+.Ql #{C/r:^Start}
 .Pp
 A limit may be placed on the length of the resultant string by prefixing it
 by an
@@ -4108,6 +4128,14 @@ will substitute
 with
 .Ql bar
 throughout.
+The first argument may be an extended regular expression and a final argument may be
+.Ql i
+to ignore case, for example
+.Ql s/a(.)/\e1x/i:
+would change
+.Ql abABab
+into
+.Ql bxBxbx .
 .Pp
 In addition, the first line of a shell command's output may be inserted using
 .Ql #() .
diff --git a/tmux.h b/tmux.h
index 41a98c92..1b784104 100644
--- a/tmux.h
+++ b/tmux.h
@@ -2398,7 +2398,8 @@ void		 window_pane_key(struct window_pane *, struct client *,
 		     struct session *, struct winlink *, key_code,
 		     struct mouse_event *);
 int		 window_pane_visible(struct window_pane *);
-u_int		 window_pane_search(struct window_pane *, const char *);
+u_int		 window_pane_search(struct window_pane *, const char *, int,
+		     int);
 const char	*window_printable_flags(struct winlink *);
 struct window_pane *window_pane_find_up(struct window_pane *);
 struct window_pane *window_pane_find_down(struct window_pane *);
@@ -2635,4 +2636,7 @@ int		 style_is_default(struct style *);
 struct winlink	*spawn_window(struct spawn_context *, char **);
 struct window_pane *spawn_pane(struct spawn_context *, char **);
 
+/* regsub.c */
+char		*regsub(const char *, const char *, const char *, int);
+
 #endif /* TMUX_H */
diff --git a/window-copy.c b/window-copy.c
index d81073bf..a7cb5dfa 100644
--- a/window-copy.c
+++ b/window-copy.c
@@ -3027,8 +3027,8 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only)
 	if (data->lineflag == LINE_SEL_LEFT_RIGHT && oy == data->sely)
 		window_copy_other_end(wme);
 
-	data->cx = data->lastcx;
 	if (scroll_only || data->cy == 0) {
+		data->cx = data->lastcx;
 		window_copy_scroll_down(wme, 1);
 		if (scroll_only) {
 			if (data->cy == screen_size_y(s) - 1)
@@ -3037,7 +3037,7 @@ window_copy_cursor_up(struct window_mode_entry *wme, int scroll_only)
 				window_copy_redraw_lines(wme, data->cy, 2);
 		}
 	} else {
-		window_copy_update_cursor(wme, data->cx, data->cy - 1);
+		window_copy_update_cursor(wme, data->lastcx, data->cy - 1);
 		if (window_copy_update_selection(wme, 1)) {
 			if (data->cy == screen_size_y(s) - 1)
 				window_copy_redraw_lines(wme, data->cy, 1);
@@ -3077,13 +3077,13 @@ window_copy_cursor_down(struct window_mode_entry *wme, int scroll_only)
 	if (data->lineflag == LINE_SEL_RIGHT_LEFT && oy == data->endsely)
 		window_copy_other_end(wme);
 
-	data->cx = data->lastcx;
 	if (scroll_only || data->cy == screen_size_y(s) - 1) {
+		data->cx = data->lastcx;
 		window_copy_scroll_up(wme, 1);
 		if (scroll_only && data->cy > 0)
 			window_copy_redraw_lines(wme, data->cy - 1, 2);
 	} else {
-		window_copy_update_cursor(wme, data->cx, data->cy + 1);
+		window_copy_update_cursor(wme, data->lastcx, data->cy + 1);
 		if (window_copy_update_selection(wme, 1))
 			window_copy_redraw_lines(wme, data->cy - 1, 2);
 	}
diff --git a/window.c b/window.c
index a6e15a34..4c5f7817 100644
--- a/window.c
+++ b/window.c
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <fnmatch.h>
+#include <regex.h>
 #include <signal.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -1207,24 +1208,41 @@ window_pane_visible(struct window_pane *wp)
 }
 
 u_int
-window_pane_search(struct window_pane *wp, const char *searchstr)
+window_pane_search(struct window_pane *wp, const char *term, int regex,
+    int ignore)
 {
 	struct screen	*s = &wp->base;
-	char		*newsearchstr, *line;
+	regex_t		 r;
+	char		*new = NULL, *line;
 	u_int		 i;
+	int		 flags = 0, found;
 
-	xasprintf(&newsearchstr, "*%s*", searchstr);
+	if (!regex) {
+		if (ignore)
+			flags |= FNM_CASEFOLD;
+		xasprintf(&new, "*%s*", term);
+	} else {
+		if (ignore)
+			flags |= REG_ICASE;
+		if (regcomp(&r, term, flags|REG_EXTENDED) != 0)
+			return (0);
+	}
 
 	for (i = 0; i < screen_size_y(s); i++) {
 		line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s));
-		if (fnmatch(newsearchstr, line, 0) == 0) {
-			free(line);
-			break;
-		}
+		if (!regex)
+			found = (fnmatch(new, line, 0) == 0);
+		else
+			found = (regexec(&r, line, 0, NULL, 0) == 0);
 		free(line);
+		if (found)
+			break;
 	}
+	if (!regex)
+		free(new);
+	else
+		regfree(&r);
 
-	free(newsearchstr);
 	if (i == screen_size_y(s))
 		return (0);
 	return (i + 1);