diff --git a/arguments.c b/arguments.c
index 30e8fa0b..5669df92 100644
--- a/arguments.c
+++ b/arguments.c
@@ -166,7 +166,7 @@ args_parse(const struct args_parse *parse, struct args_value *values,
 			}
 			argument = *++found;
 			if (argument != ':') {
-				log_debug("%s: add -%c", __func__, flag);
+				log_debug("%s: -%c", __func__, flag);
 				args_set(args, flag, NULL);
 				continue;
 			}
@@ -192,7 +192,7 @@ args_parse(const struct args_parse *parse, struct args_value *values,
 				args_copy_value(new, &values[i++]);
 			}
 			s = args_value_as_string(new);
-			log_debug("%s: add -%c = %s", __func__, flag, s);
+			log_debug("%s: -%c = %s", __func__, flag, s);
 			args_set(args, flag, new);
 			break;
 		}
@@ -203,7 +203,8 @@ args_parse(const struct args_parse *parse, struct args_value *values,
 			value = &values[i];
 
 			s = args_value_as_string(value);
-			log_debug("%s: %u = %s", __func__, i, s);
+			log_debug("%s: %u = %s (type %d)", __func__, i, s,
+			    value->type);
 
 			if (parse->cb != NULL) {
 				type = parse->cb(args, args->count, cause);
@@ -265,6 +266,63 @@ args_parse(const struct args_parse *parse, struct args_value *values,
 	return (args);
 }
 
+/* Copy and expand a value. */
+static void
+args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
+    char **argv)
+{
+	char	*s, *expanded;
+	int	 i;
+
+	to->type = from->type;
+	switch (from->type) {
+	case ARGS_NONE:
+		break;
+	case ARGS_STRING:
+		expanded = xstrdup(from->string);
+		for (i = 0; i < argc; i++) {
+			s = cmd_template_replace(expanded, argv[i], i + 1);
+			free(expanded);
+			expanded = s;
+		}
+		to->string = expanded;
+		break;
+	case ARGS_COMMANDS:
+		to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
+		break;
+	}
+}
+
+/* Copy an arguments set. */
+struct args *
+args_copy(struct args *args, int argc, char **argv)
+{
+	struct args		*new_args;
+	struct args_entry	*entry;
+	struct args_value	*value, *new_value;
+	u_int			 i;
+
+	new_args = args_create();
+	RB_FOREACH(entry, args_tree, &args->tree) {
+		if (entry->count == 1) {
+			args_set(new_args, entry->flag, NULL);
+			continue;
+		}
+		TAILQ_FOREACH(value, &entry->values, entry) {
+			new_value = xcalloc(1, sizeof *new_value);
+			args_copy_copy_value(new_value, value, argc, argv);
+			args_set(new_args, entry->flag, new_value);
+		}
+	}
+	new_args->count = args->count;
+	new_args->values = xcalloc(args->count, sizeof *new_args->values);
+	for (i = 0; i < args->count; i++) {
+		new_value = &new_args->values[i];
+		args_copy_copy_value(new_value, &args->values[i], argc, argv);
+	}
+	return (new_args);
+}
+
 /* Free a value. */
 void
 args_free_value(struct args_value *value)
@@ -282,6 +340,16 @@ args_free_value(struct args_value *value)
 	free(value->cached);
 }
 
+/* Free values. */
+void
+args_free_values(struct args_value *values, u_int count)
+{
+	u_int	i;
+
+	for (i = 0; i < count; i++)
+		args_free_value(&values[i]);
+}
+
 /* Free an arguments set. */
 void
 args_free(struct args *args)
@@ -290,10 +358,8 @@ args_free(struct args *args)
 	struct args_entry	*entry1;
 	struct args_value	*value;
 	struct args_value	*value1;
-	u_int			 i;
 
-	for (i = 0; i < args->count; i++)
-		args_free_value(&args->values[i]);
+	args_free_values(args->values, args->count);
 	free(args->values);
 
 	RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) {
@@ -311,7 +377,7 @@ args_free(struct args *args)
 
 /* Convert arguments to vector. */
 void
-args_vector(struct args *args, int *argc, char ***argv)
+args_to_vector(struct args *args, int *argc, char ***argv)
 {
 	char	*s;
 	u_int	 i;
@@ -335,6 +401,21 @@ args_vector(struct args *args, int *argc, char ***argv)
 	}
 }
 
+/* Convert arguments from vector. */
+struct args_value *
+args_from_vector(int argc, char **argv)
+{
+	struct args_value	*values;
+	int			 i;
+
+	values = xcalloc(argc, sizeof *values);
+	for (i = 0; i < argc; i++) {
+		values[i].type = ARGS_STRING;
+		values[i].string = xstrdup(argv[i]);
+	}
+	return (values);
+}
+
 /* Add to string. */
 static void printflike(3, 4)
 args_print_add(char **buf, size_t *len, const char *fmt, ...)
@@ -424,7 +505,7 @@ args_print(struct args *args)
 char *
 args_escape(const char *s)
 {
-	static const char	 dquoted[] = " #';${}";
+	static const char	 dquoted[] = " #';${}%";
 	static const char	 squoted[] = " \"";
 	char			*escaped, *result;
 	int			 flags, quotes = 0;
@@ -538,6 +619,13 @@ args_count(struct args *args)
 	return (args->count);
 }
 
+/* Get argument values. */
+struct args_value *
+args_values(struct args *args)
+{
+	return (args->values);
+}
+
 /* Get argument value. */
 struct args_value *
 args_value(struct args *args, u_int idx)
@@ -570,8 +658,8 @@ args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx)
 		cmdq_error(item, "%s", error);
 		free(error);
 	}
-       else
-	       cmdlist->references++;
+	else
+		cmdlist->references++;
 	args_make_commands_free(state);
 	return (cmdlist);
 }
@@ -631,8 +719,11 @@ args_make_commands(struct args_command_state *state, int argc, char **argv,
 	char			*cmd, *new_cmd;
 	int			 i;
 
-	if (state->cmdlist != NULL)
-		return (state->cmdlist);
+	if (state->cmdlist != NULL) {
+		if (argc == 0)
+			return (state->cmdlist);
+		return (cmd_list_copy(state->cmdlist, argc, argv));
+	}
 
 	cmd = xstrdup(state->cmd);
 	for (i = 0; i < argc; i++) {
diff --git a/client.c b/client.c
index fc0e1dde..8ca08524 100644
--- a/client.c
+++ b/client.c
@@ -243,6 +243,7 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
 	ssize_t			 linelen;
 	char			*line = NULL, **caps = NULL, *cause;
 	u_int			 ncaps = 0;
+	struct args_value	*values;
 
 	/* Ignore SIGCHLD now or daemon() in the server will leave a zombie. */
 	signal(SIGCHLD, SIG_IGN);
@@ -258,17 +259,20 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
 		msg = MSG_COMMAND;
 
 		/*
-		 * It sucks parsing the command string twice (in client and
-		 * later in server) but it is necessary to get the start server
-		 * flag.
+		 * It's annoying parsing the command string twice (in client
+		 * and later in server) but it is necessary to get the start
+		 * server flag.
 		 */
-		pr = cmd_parse_from_arguments(argc, argv, NULL);
+		values = args_from_vector(argc, argv);
+		pr = cmd_parse_from_arguments(values, argc, NULL);
 		if (pr->status == CMD_PARSE_SUCCESS) {
 			if (cmd_list_any_have(pr->cmdlist, CMD_STARTSERVER))
 				flags |= CLIENT_STARTSERVER;
 			cmd_list_free(pr->cmdlist);
 		} else
 			free(pr->error);
+		args_free_values(values, argc);
+		free(values);
 	}
 
 	/* Create client process structure (starts logging). */
diff --git a/cmd-bind-key.c b/cmd-bind-key.c
index be0ae40e..dab03b01 100644
--- a/cmd-bind-key.c
+++ b/cmd-bind-key.c
@@ -58,8 +58,7 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
 	key_code		  key;
 	const char		 *tablename, *note = args_get(args, 'N');
 	struct cmd_parse_result	 *pr;
-	char			**argv;
-	int			  argc, repeat;
+	int			  repeat;
 	struct args_value	 *value;
 	u_int			  count = args_count(args);
 
@@ -92,9 +91,8 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
 	if (count == 2)
 		pr = cmd_parse_from_string(args_string(args, 1), NULL);
 	else {
-		args_vector(args, &argc, &argv);
-		pr = cmd_parse_from_arguments(argc - 1, argv + 1, NULL);
-		cmd_free_argv(argc, argv);
+		pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1,
+		    NULL);
 	}
 	switch (pr->status) {
 	case CMD_PARSE_ERROR:
diff --git a/cmd-choose-tree.c b/cmd-choose-tree.c
index 9258f366..7aa1d217 100644
--- a/cmd-choose-tree.c
+++ b/cmd-choose-tree.c
@@ -24,13 +24,16 @@
  * Enter a mode.
  */
 
-static enum cmd_retval	cmd_choose_tree_exec(struct cmd *, struct cmdq_item *);
+static enum args_parse_type	cmd_choose_tree_args_parse(struct args *args,
+				    u_int idx, char **cause);
+static enum cmd_retval		cmd_choose_tree_exec(struct cmd *,
+    				    struct cmdq_item *);
 
 const struct cmd_entry cmd_choose_tree_entry = {
 	.name = "choose-tree",
 	.alias = NULL,
 
-	.args = { "F:f:GK:NO:rst:wZ", 0, 1, NULL },
+	.args = { "F:f:GK:NO:rst:wZ", 0, 1, cmd_choose_tree_args_parse },
 	.usage = "[-GNrswZ] [-F format] [-f filter] [-K key-format] "
 		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
@@ -44,7 +47,7 @@ const struct cmd_entry cmd_choose_client_entry = {
 	.name = "choose-client",
 	.alias = NULL,
 
-	.args = { "F:f:K:NO:rt:Z", 0, 1, NULL },
+	.args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse },
 	.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
 		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
@@ -58,7 +61,7 @@ const struct cmd_entry cmd_choose_buffer_entry = {
 	.name = "choose-buffer",
 	.alias = NULL,
 
-	.args = { "F:f:K:NO:rt:Z", 0, 1, NULL },
+	.args = { "F:f:K:NO:rt:Z", 0, 1, cmd_choose_tree_args_parse },
 	.usage = "[-NrZ] [-F format] [-f filter] [-K key-format] "
 		 "[-O sort-order] " CMD_TARGET_PANE_USAGE " [template]",
 
@@ -81,6 +84,13 @@ const struct cmd_entry cmd_customize_mode_entry = {
 	.exec = cmd_choose_tree_exec
 };
 
+static enum args_parse_type
+cmd_choose_tree_args_parse(__unused struct args *args, __unused u_int idx,
+    __unused char **cause)
+{
+	return (ARGS_PARSE_COMMANDS_OR_STRING);
+}
+
 static enum cmd_retval
 cmd_choose_tree_exec(struct cmd *self, struct cmdq_item *item)
 {
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index 737c44c7..820053ec 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -169,11 +169,13 @@ static int
 cmd_command_prompt_callback(struct client *c, void *data, const char *s,
     int done)
 {
-	struct cmd_command_prompt_cdata		*cdata = data;
-	char					*error;
-	struct cmdq_item			*item = cdata->item, *new_item;
-	struct cmd_list				*cmdlist;
-	struct cmd_command_prompt_prompt	*prompt;
+	struct cmd_command_prompt_cdata		 *cdata = data;
+	char					 *error;
+	struct cmdq_item			 *item = cdata->item, *new_item;
+	struct cmd_list				 *cmdlist;
+	struct cmd_command_prompt_prompt	 *prompt;
+	int					  argc = 0;
+	char					**argv = NULL;
 
 	if (s == NULL)
 		goto out;
@@ -181,7 +183,6 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
 		if (cdata->flags & PROMPT_INCREMENTAL)
 			goto out;
 
-		cmd_append_argv(&cdata->argc, &cdata->argv, s);
 		if (++cdata->current != cdata->count) {
 			prompt = &cdata->prompts[cdata->current];
 			status_prompt_update(c, prompt->prompt, prompt->input);
@@ -189,8 +190,15 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
 		}
 	}
 
-	cmdlist = args_make_commands(cdata->state, cdata->argc, cdata->argv,
-	    &error);
+	argc = cdata->argc;
+	argv = cmd_copy_argv(cdata->argc, cdata->argv);
+	cmd_append_argv(&argc, &argv, s);
+	if (done) {
+		cdata->argc = argc;
+		cdata->argv = cmd_copy_argv(argc, argv);
+	}
+
+	cmdlist = args_make_commands(cdata->state, argc, argv, &error);
 	if (cmdlist == NULL) {
 		cmdq_append(c, cmdq_get_error(error));
 		free(error);
@@ -201,6 +209,7 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
 		new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
 		cmdq_insert_after(item, new_item);
 	}
+	cmd_free_argv(argc, argv);
 
 	if (c->prompt_inputcb != cmd_command_prompt_callback)
 		return (1);
diff --git a/cmd-display-menu.c b/cmd-display-menu.c
index 4c5fb510..1a11bd01 100644
--- a/cmd-display-menu.c
+++ b/cmd-display-menu.c
@@ -407,7 +407,7 @@ cmd_display_popup_exec(struct cmd *self, struct cmdq_item *item)
 			shell = _PATH_BSHELL;
 		cmd_append_argv(&argc, &argv, shell);
 	} else
-		args_vector(args, &argc, &argv);
+		args_to_vector(args, &argc, &argv);
 
 	if (args_has(args, 'E') > 1)
 		flags |= POPUP_CLOSEEXITZERO;
diff --git a/cmd-new-session.c b/cmd-new-session.c
index 93c7e7b4..cb9abfb3 100644
--- a/cmd-new-session.c
+++ b/cmd-new-session.c
@@ -42,7 +42,8 @@ const struct cmd_entry cmd_new_session_entry = {
 	.args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1, NULL },
 	.usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] "
 		 "[-f flags] [-n window-name] [-s session-name] "
-		 CMD_TARGET_SESSION_USAGE " [-x width] [-y height] [command]",
+		 CMD_TARGET_SESSION_USAGE " [-x width] [-y height] "
+		 "[shell-command]",
 
 	.target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
 
@@ -283,7 +284,7 @@ cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
 		sc.tc = c;
 
 	sc.name = args_get(args, 'n');
-	args_vector(args, &sc.argc, &sc.argv);
+	args_to_vector(args, &sc.argc, &sc.argv);
 
 	sc.idx = -1;
 	sc.cwd = args_get(args, 'c');
diff --git a/cmd-new-window.c b/cmd-new-window.c
index e88795c2..e7f0868f 100644
--- a/cmd-new-window.c
+++ b/cmd-new-window.c
@@ -40,7 +40,7 @@ const struct cmd_entry cmd_new_window_entry = {
 
 	.args = { "abc:de:F:kn:PSt:", 0, -1, NULL },
 	.usage = "[-abdkPS] [-c start-directory] [-e environment] [-F format] "
-		 "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [command]",
+		 "[-n window-name] " CMD_TARGET_WINDOW_USAGE " [shell-command]",
 
 	.target = { 't', CMD_FIND_WINDOW, CMD_FIND_WINDOW_INDEX },
 
@@ -105,7 +105,7 @@ cmd_new_window_exec(struct cmd *self, struct cmdq_item *item)
 	sc.tc = tc;
 
 	sc.name = args_get(args, 'n');
-	args_vector(args, &sc.argc, &sc.argv);
+	args_to_vector(args, &sc.argc, &sc.argv);
 	sc.environ = environ_create();
 
 	av = args_first_value(args, 'e');
diff --git a/cmd-parse.y b/cmd-parse.y
index a08c5819..2b5b7e0b 100644
--- a/cmd-parse.y
+++ b/cmd-parse.y
@@ -44,13 +44,15 @@ struct cmd_parse_scope {
 
 enum cmd_parse_argument_type {
 	CMD_PARSE_STRING,
-	CMD_PARSE_COMMANDS
+	CMD_PARSE_COMMANDS,
+	CMD_PARSE_PARSED_COMMANDS
 };
 
 struct cmd_parse_argument {
 	enum cmd_parse_argument_type	 type;
 	char				*string;
 	struct cmd_parse_commands	*commands;
+	struct cmd_list			*cmdlist;
 
 	TAILQ_ENTRY(cmd_parse_argument)	 entry;
 };
@@ -608,6 +610,9 @@ cmd_parse_free_argument(struct cmd_parse_argument *arg)
 	case CMD_PARSE_COMMANDS:
 		cmd_parse_free_commands(arg->commands);
 		break;
+	case CMD_PARSE_PARSED_COMMANDS:
+		cmd_list_free(arg->cmdlist);
+		break;
 	}
 	free(arg);
 }
@@ -723,6 +728,11 @@ cmd_parse_log_commands(struct cmd_parse_commands *cmds, const char *prefix)
 				cmd_parse_log_commands(arg->commands, s);
 				free(s);
 				break;
+			case CMD_PARSE_PARSED_COMMANDS:
+				s = cmd_list_print(arg->cmdlist, 0);
+				log_debug("%s %u:%u: %s", prefix, i, j, s);
+				free(s);
+				break;
 			}
 			j++;
 		}
@@ -818,6 +828,11 @@ cmd_parse_build_command(struct cmd_parse_command *cmd,
 			values[count].type = ARGS_COMMANDS;
 			values[count].cmdlist = pr->cmdlist;
 			break;
+		case CMD_PARSE_PARSED_COMMANDS:
+			values[count].type = ARGS_COMMANDS;
+			values[count].cmdlist = arg->cmdlist;
+			values[count].cmdlist->references++;
+			break;
 		}
 		count++;
 	}
@@ -1023,39 +1038,19 @@ cmd_parse_from_buffer(const void *buf, size_t len, struct cmd_parse_input *pi)
 	return (&pr);
 }
 
-static void
-cmd_parse_add_command(struct cmd_parse_commands *cmds,
-    struct cmd_parse_input *pi, int argc, char **argv)
+struct cmd_parse_result *
+cmd_parse_from_arguments(struct args_value *values, u_int count,
+    struct cmd_parse_input *pi)
 {
+	static struct cmd_parse_result	 pr;
+	struct cmd_parse_input		 input;
+	struct cmd_parse_commands	*cmds;
 	struct cmd_parse_command	*cmd;
 	struct cmd_parse_argument	*arg;
-	int				 i;
-
-	cmd_log_argv(argc, argv, "%s", __func__);
-
-	cmd = xcalloc(1, sizeof *cmd);
-	cmd->line = pi->line;
-
-	TAILQ_INIT(&cmd->arguments);
-	for (i = 0; i < argc; i++) {
-		arg = xcalloc(1, sizeof *arg);
-		arg->type = CMD_PARSE_STRING;
-		arg->string = xstrdup(argv[i]);
-		TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
-	}
-
-	TAILQ_INSERT_TAIL(cmds, cmd, entry);
-}
-
-struct cmd_parse_result *
-cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
-{
-	static struct cmd_parse_result	  pr;
-	struct cmd_parse_input		  input;
-	struct cmd_parse_commands	 *cmds;
-	char				**copy, **new_argv;
-	size_t				  size;
-	int				  i, last, new_argc;
+	u_int				 i;
+	char				*copy;
+	size_t				 size;
+	int				 end;
 
 	/*
 	 * The commands are already split up into arguments, so just separate
@@ -1066,40 +1061,51 @@ cmd_parse_from_arguments(int argc, char **argv, struct cmd_parse_input *pi)
 		memset(&input, 0, sizeof input);
 		pi = &input;
 	}
-	cmd_log_argv(argc, argv, "%s", __func__);
 
 	cmds = cmd_parse_new_commands();
-	copy = cmd_copy_argv(argc, argv);
 
-	last = 0;
-	for (i = 0; i < argc; i++) {
-		size = strlen(copy[i]);
-		if (size == 0 || copy[i][size - 1] != ';')
-			continue;
-		copy[i][--size] = '\0';
-		if (size > 0 && copy[i][size - 1] == '\\') {
-			copy[i][size - 1] = ';';
-			continue;
+	cmd = xcalloc(1, sizeof *cmd);
+	cmd->line = pi->line;
+	TAILQ_INIT(&cmd->arguments);
+
+	for (i = 0; i < count; i++) {
+		end = 0;
+		if (values[i].type == ARGS_STRING) {
+			copy = xstrdup(values[i].string);
+			size = strlen(copy);
+			if (size != 0 && copy[size - 1] == ';') {
+				copy[--size] = '\0';
+				if (size > 0 && copy[size - 1] == '\\')
+					copy[size - 1] = ';';
+				else
+					end = 1;
+			}
+			if (!end || size != 0) {
+				arg = xcalloc(1, sizeof *arg);
+				arg->type = CMD_PARSE_STRING;
+				arg->string = copy;
+				TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+			}
+		} else if (values[i].type == ARGS_COMMANDS) {
+			arg = xcalloc(1, sizeof *arg);
+			arg->type = CMD_PARSE_PARSED_COMMANDS;
+			arg->cmdlist = values[i].cmdlist;
+			arg->cmdlist->references++;
+			TAILQ_INSERT_TAIL(&cmd->arguments, arg, entry);
+		} else
+			fatalx("unknown argument type");
+		if (end) {
+			TAILQ_INSERT_TAIL(cmds, cmd, entry);
+			cmd = xcalloc(1, sizeof *cmd);
+			cmd->line = pi->line;
+			TAILQ_INIT(&cmd->arguments);
 		}
-
-		new_argc = i - last;
-		new_argv = copy + last;
-		if (size != 0)
-			new_argc++;
-
-		if (new_argc != 0)
-			cmd_parse_add_command(cmds, pi, new_argc, new_argv);
-		last = i + 1;
 	}
-	if (last != argc) {
-		new_argv = copy + last;
-		new_argc = argc - last;
+	if (!TAILQ_EMPTY(&cmd->arguments))
+		TAILQ_INSERT_TAIL(cmds, cmd, entry);
+	else
+		free(cmd);
 
-		if (new_argc != 0)
-			cmd_parse_add_command(cmds, pi, new_argc, new_argv);
-	}
-
-	cmd_free_argv(argc, copy);
 	cmd_parse_build_commands(cmds, pi, &pr);
 	cmd_parse_free_commands(cmds);
 	return (&pr);
diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c
index 24a49dcb..821558ae 100644
--- a/cmd-refresh-client.c
+++ b/cmd-refresh-client.c
@@ -77,6 +77,58 @@ out:
 	free(copy);
 }
 
+static enum cmd_retval
+cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item)
+{
+	struct args		*args = cmd_get_args(self);
+	struct client		*tc = cmdq_get_target_client(item);
+	const char		*size = args_get(args, 'C');
+	u_int			 w, x, y;
+	struct client_window	*cw;
+
+	if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) {
+		if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
+		    y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) {
+			cmdq_error(item, "size too small or too big");
+			return (CMD_RETURN_ERROR);
+		}
+		log_debug("%s: client %s window @%u: size %ux%u", __func__,
+		    tc->name, w, x, y);
+		cw = server_client_add_client_window(tc, w);
+		cw->sx = x;
+		cw->sy = y;
+		tc->flags |= CLIENT_WINDOWSIZECHANGED;
+		recalculate_sizes_now(1);
+		return (CMD_RETURN_NORMAL);
+	}
+	if (sscanf(size, "@%u:", &w) == 1) {
+		cw = server_client_get_client_window(tc, w);
+		if (cw != NULL) {
+			log_debug("%s: client %s window @%u: no size", __func__,
+			    tc->name, w);
+			cw->sx = 0;
+			cw->sy = 0;
+			recalculate_sizes_now(1);
+		}
+		return (CMD_RETURN_NORMAL);
+	}
+
+	if (sscanf(size, "%u,%u", &x, &y) != 2 &&
+	    sscanf(size, "%ux%u", &x, &y) != 2) {
+		cmdq_error(item, "bad size argument");
+		return (CMD_RETURN_ERROR);
+	}
+	if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
+	    y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) {
+		cmdq_error(item, "size too small or too big");
+		return (CMD_RETURN_ERROR);
+	}
+	tty_set_size(&tc->tty, x, y, 0, 0);
+	tc->flags |= CLIENT_SIZECHANGED;
+	recalculate_sizes_now(1);
+	return (CMD_RETURN_NORMAL);
+}
+
 static void
 cmd_refresh_client_update_offset(struct client *tc, const char *value)
 {
@@ -117,8 +169,8 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
 	struct client		*tc = cmdq_get_target_client(item);
 	struct tty		*tty = &tc->tty;
 	struct window		*w;
-	const char		*size, *errstr;
-	u_int			 x, y, adjust;
+	const char		*errstr;
+	u_int			 adjust;
 	struct args_value	*av;
 
 	if (args_has(args, 'c') ||
@@ -205,21 +257,7 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item)
 	if (args_has(args, 'C')) {
 		if (~tc->flags & CLIENT_CONTROL)
 			goto not_control_client;
-		size = args_get(args, 'C');
-		if (sscanf(size, "%u,%u", &x, &y) != 2 &&
-		    sscanf(size, "%ux%u", &x, &y) != 2) {
-			cmdq_error(item, "bad size argument");
-			return (CMD_RETURN_ERROR);
-		}
-		if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM ||
-		    y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) {
-			cmdq_error(item, "size too small or too big");
-			return (CMD_RETURN_ERROR);
-		}
-		tty_set_size(&tc->tty, x, y, 0, 0);
-		tc->flags |= CLIENT_SIZECHANGED;
-		recalculate_sizes_now(1);
-		return (CMD_RETURN_NORMAL);
+		return (cmd_refresh_client_control_client_size(self, item));
 	}
 
 	if (args_has(args, 'S')) {
diff --git a/cmd-resize-window.c b/cmd-resize-window.c
index 24d73c87..ad739165 100644
--- a/cmd-resize-window.c
+++ b/cmd-resize-window.c
@@ -108,7 +108,9 @@ cmd_resize_window_exec(struct cmd *self, struct cmdq_item *item)
 	}
 
 	options_set_number(w->options, "window-size", WINDOW_SIZE_MANUAL);
-	resize_window(w, sx, sy, xpixel, ypixel);
+	w->manual_sx = sx;
+	w->manual_sy = sy;
+	recalculate_size(w, 1);
 
 	return (CMD_RETURN_NORMAL);
 }
diff --git a/cmd-respawn-pane.c b/cmd-respawn-pane.c
index 652ef755..6d60002e 100644
--- a/cmd-respawn-pane.c
+++ b/cmd-respawn-pane.c
@@ -36,7 +36,7 @@ const struct cmd_entry cmd_respawn_pane_entry = {
 
 	.args = { "c:e:kt:", 0, -1, NULL },
 	.usage = "[-k] [-c start-directory] [-e environment] "
-		 CMD_TARGET_PANE_USAGE " [command]",
+		 CMD_TARGET_PANE_USAGE " [shell-command]",
 
 	.target = { 't', CMD_FIND_PANE, 0 },
 
@@ -61,10 +61,8 @@ cmd_respawn_pane_exec(struct cmd *self, struct cmdq_item *item)
 	sc.wl = wl;
 
 	sc.wp0 = wp;
-	sc.lc = NULL;
 
-	sc.name = NULL;
-	args_vector(args, &sc.argc, &sc.argv);
+	args_to_vector(args, &sc.argc, &sc.argv);
 	sc.environ = environ_create();
 
 	av = args_first_value(args, 'e');
diff --git a/cmd-respawn-window.c b/cmd-respawn-window.c
index 4e6dc2a9..9a1a02c9 100644
--- a/cmd-respawn-window.c
+++ b/cmd-respawn-window.c
@@ -36,7 +36,7 @@ const struct cmd_entry cmd_respawn_window_entry = {
 
 	.args = { "c:e:kt:", 0, -1, NULL },
 	.usage = "[-k] [-c start-directory] [-e environment] "
-		 CMD_TARGET_WINDOW_USAGE " [command]",
+		 CMD_TARGET_WINDOW_USAGE " [shell-command]",
 
 	.target = { 't', CMD_FIND_WINDOW, 0 },
 
@@ -61,8 +61,7 @@ cmd_respawn_window_exec(struct cmd *self, struct cmdq_item *item)
 	sc.wl = wl;
 	sc.tc = tc;
 
-	sc.name = NULL;
-	args_vector(args, &sc.argc, &sc.argv);
+	args_to_vector(args, &sc.argc, &sc.argv);
 	sc.environ = environ_create();
 
 	av = args_first_value(args, 'e');
diff --git a/cmd-split-window.c b/cmd-split-window.c
index 109c587b..74c8b5ab 100644
--- a/cmd-split-window.c
+++ b/cmd-split-window.c
@@ -41,7 +41,8 @@ const struct cmd_entry cmd_split_window_entry = {
 
 	.args = { "bc:de:fF:hIl:p:Pt:vZ", 0, -1, NULL },
 	.usage = "[-bdefhIPvZ] [-c start-directory] [-e environment] "
-		 "[-F format] [-l size] " CMD_TARGET_PANE_USAGE " [command]",
+		 "[-F format] [-l size] " CMD_TARGET_PANE_USAGE
+		 "[shell-command]",
 
 	.target = { 't', CMD_FIND_PANE, 0 },
 
@@ -135,8 +136,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item)
 	sc.wp0 = wp;
 	sc.lc = lc;
 
-	sc.name = NULL;
-	args_vector(args, &sc.argc, &sc.argv);
+	args_to_vector(args, &sc.argc, &sc.argv);
 	sc.environ = environ_create();
 
 	av = args_first_value(args, 'e');
diff --git a/cmd.c b/cmd.c
index 4b1dbbbf..44952d11 100644
--- a/cmd.c
+++ b/cmd.c
@@ -543,6 +543,23 @@ cmd_free(struct cmd *cmd)
 	free(cmd);
 }
 
+/* Copy a command. */
+struct cmd *
+cmd_copy(struct cmd *cmd, int argc, char **argv)
+{
+	struct cmd	*new_cmd;
+
+	new_cmd = xcalloc(1, sizeof *new_cmd);
+	new_cmd->entry = cmd->entry;
+	new_cmd->args = args_copy(cmd->args, argc, argv);
+
+	if (cmd->file != NULL)
+		new_cmd->file = xstrdup(cmd->file);
+	new_cmd->line = cmd->line;
+
+	return (new_cmd);
+}
+
 /* Get a command as a string. */
 char *
 cmd_print(struct cmd *cmd)
@@ -617,6 +634,37 @@ cmd_list_free(struct cmd_list *cmdlist)
 	free(cmdlist);
 }
 
+/* Copy a command list, expanding %s in arguments. */
+struct cmd_list *
+cmd_list_copy(struct cmd_list *cmdlist, int argc, char **argv)
+{
+	struct cmd	*cmd;
+	struct cmd_list	*new_cmdlist;
+	struct cmd	*new_cmd;
+	u_int		 group = cmdlist->group;
+	char		*s;
+
+	s = cmd_list_print(cmdlist, 0);
+	log_debug("%s: %s", __func__, s);
+	free(s);
+
+	new_cmdlist = cmd_list_new();
+	TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
+		if (cmd->group != group) {
+			new_cmdlist->group = cmd_list_next_group++;
+			group = cmd->group;
+		}
+		new_cmd = cmd_copy(cmd, argc, argv);
+		cmd_list_append(new_cmdlist, new_cmd);
+	}
+
+	s = cmd_list_print(new_cmdlist, 0);
+	log_debug("%s: %s", __func__, s);
+	free(s);
+
+	return (new_cmdlist);
+}
+
 /* Get a command list as a string. */
 char *
 cmd_list_print(struct cmd_list *cmdlist, int escaped)
diff --git a/key-bindings.c b/key-bindings.c
index 66e1ec9f..9f7e734a 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -29,7 +29,7 @@
 	" 'Previous' 'p' {switch-client -p}" \
 	" ''" \
 	" 'Renumber' 'N' {move-window -r}" \
-	" 'Rename' 'n' {command-prompt -I \"#S\" \"rename-session -- '%%'\"}" \
+	" 'Rename' 'n' {command-prompt -I \"#S\" {rename-session -- '%%'}}" \
 	" ''" \
 	" 'New Session' 's' {new-session}" \
 	" 'New Window' 'w' {new-window}"
@@ -41,7 +41,7 @@
 	" 'Kill' 'X' {kill-window}" \
 	" 'Respawn' 'R' {respawn-window -k}" \
 	" '#{?pane_marked,Unmark,Mark}' 'm' {select-pane -m}" \
-	" 'Rename' 'n' {command-prompt -FI \"#W\" \"rename-window -t#{window_id} -- '%%'\"}" \
+	" 'Rename' 'n' {command-prompt -FI \"#W\" {rename-window -t '#{window_id}' -- '%%'}}" \
 	" ''" \
 	" 'New After' 'w' {new-window -a}" \
 	" 'New At End' 'W' {new-window}"
@@ -350,16 +350,16 @@ key_bindings_init(void)
 		"bind -N 'Break pane to a new window' ! { break-pane }",
 		"bind -N 'Split window vertically' '\"' { split-window }",
 		"bind -N 'List all paste buffers' '#' { list-buffers }",
-		"bind -N 'Rename current session' '$' { command-prompt -I'#S' \"rename-session -- '%%'\" }",
+		"bind -N 'Rename current session' '$' { command-prompt -I'#S' { rename-session -- '%%' } }",
 		"bind -N 'Split window horizontally' % { split-window -h }",
 		"bind -N 'Kill current window' & { confirm-before -p\"kill-window #W? (y/n)\" kill-window }",
-		"bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex \"select-window -t ':%%'\" }",
+		"bind -N 'Prompt for window index to select' \"'\" { command-prompt -T window-target -pindex { select-window -t ':%%' } }",
 		"bind -N 'Switch to previous client' ( { switch-client -p }",
 		"bind -N 'Switch to next client' ) { switch-client -n }",
-		"bind -N 'Rename current window' , { command-prompt -I'#W' \"rename-window -- '%%'\" }",
+		"bind -N 'Rename current window' , { command-prompt -I'#W' { rename-window -- '%%' } }",
 		"bind -N 'Delete the most recent paste buffer' - { delete-buffer }",
-		"bind -N 'Move the current window' . { command-prompt -T target \"move-window -t '%%'\" }",
-		"bind -N 'Describe key binding' '/' { command-prompt -kpkey 'list-keys -1N \"%%%\"' }",
+		"bind -N 'Move the current window' . { command-prompt -T target { move-window -t '%%' } }",
+		"bind -N 'Describe key binding' '/' { command-prompt -kpkey  { list-keys -1N '%%' } }",
 		"bind -N 'Select window 0' 0 { select-window -t:=0 }",
 		"bind -N 'Select window 1' 1 { select-window -t:=1 }",
 		"bind -N 'Select window 2' 2 { select-window -t:=2 }",
@@ -382,7 +382,7 @@ key_bindings_init(void)
 		"bind -N 'Paste the most recent paste buffer' ] { paste-buffer -p }",
 		"bind -N 'Create a new window' c { new-window }",
 		"bind -N 'Detach the current client' d { detach-client }",
-		"bind -N 'Search for a pane' f { command-prompt \"find-window -Z -- '%%'\" }",
+		"bind -N 'Search for a pane' f { command-prompt { find-window -Z -- '%%' } }",
 		"bind -N 'Display window information' i { display-message }",
 		"bind -N 'Select the previously current window' l { last-window }",
 		"bind -N 'Toggle the marked pane' m { select-pane -m }",
@@ -482,25 +482,25 @@ key_bindings_init(void)
 		"bind -Tcopy-mode C-k { send -X copy-pipe-end-of-line-and-cancel }",
 		"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 -T search -ip'(search up)' -I'#{pane_search_string}' 'send -X search-backward-incremental \"%%%\"' }",
-		"bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' 'send -X search-forward-incremental \"%%%\"' }",
+		"bind -Tcopy-mode C-r { command-prompt -T search -ip'(search up)' -I'#{pane_search_string}' { send -X search-backward-incremental '%%' } }",
+		"bind -Tcopy-mode C-s { command-prompt -T search -ip'(search down)' -I'#{pane_search_string}' { send -X search-forward-incremental '%%' } }",
 		"bind -Tcopy-mode C-v { send -X page-down }",
 		"bind -Tcopy-mode C-w { send -X copy-pipe-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 T { command-prompt -1p'(jump to backward)' { send -X jump-to-backward '%%' } }",
 		"bind -Tcopy-mode X { send -X set-mark }",
-		"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 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 r { send -X refresh-from-pane }",
-		"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 Home { send -X start-of-line }",
 		"bind -Tcopy-mode End { send -X end-of-line }",
 		"bind -Tcopy-mode MouseDown1Pane select-pane",
@@ -516,15 +516,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 }",
@@ -562,25 +562,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 -T search -p'(search down)' 'send -X search-forward \"%%%\"' }",
+		"bind -Tcopy-mode-vi / { command-prompt -T search -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 -T search -p'(search up)' 'send -X search-backward \"%%%\"' }",
+		"bind -Tcopy-mode-vi ? { command-prompt -T search -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-pipe-end-of-line-and-cancel }",
 		"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 }",
@@ -588,14 +588,14 @@ 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 X { send -X set-mark }",
 		"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 }",
@@ -605,7 +605,7 @@ key_bindings_init(void)
 		"bind -Tcopy-mode-vi o { send -X other-end }",
 		"bind -Tcopy-mode-vi q { send -X cancel }",
 		"bind -Tcopy-mode-vi r { send -X refresh-from-pane }",
-		"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/resize.c b/resize.c
index f321e7d2..8416ad6a 100644
--- a/resize.c
+++ b/resize.c
@@ -87,7 +87,9 @@ ignore_client_size(struct client *c)
 				return (1);
 		}
 	}
-	if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED))
+	if ((c->flags & CLIENT_CONTROL) &&
+	    (~c->flags & CLIENT_SIZECHANGED) &&
+	    (~c->flags & CLIENT_WINDOWSIZECHANGED))
 		return (1);
 	return (0);
 }
@@ -113,23 +115,25 @@ clients_calculate_size(int type, int current, struct client *c,
     int, int, struct session *, struct window *), u_int *sx, u_int *sy,
     u_int *xpixel, u_int *ypixel)
 {
-	struct client	*loop;
-	u_int		 cx, cy, n = 0;
-
-	/* Manual windows do not have their size changed based on a client. */
-	if (type == WINDOW_SIZE_MANUAL) {
-		log_debug("%s: type is manual", __func__);
-		return (0);
-	}
+	struct client		*loop;
+	struct client_window	*cw;
+	u_int			 cx, cy, n = 0;
 
 	/*
 	 * Start comparing with 0 for largest and UINT_MAX for smallest or
 	 * latest.
 	 */
-	if (type == WINDOW_SIZE_LARGEST)
-		*sx = *sy = 0;
-	else
-		*sx = *sy = UINT_MAX;
+	if (type == WINDOW_SIZE_LARGEST) {
+		*sx = 0;
+		*sy = 0;
+	} else if (type == WINDOW_SIZE_MANUAL) {
+		*sx = w->manual_sx;
+		*sy = w->manual_sy;
+		log_debug("%s: manual size %ux%u", __func__, *sx, *sy);
+	} else {
+		*sx = UINT_MAX;
+		*sy = UINT_MAX;
+	}
 	*xpixel = *ypixel = 0;
 
 	/*
@@ -139,14 +143,18 @@ clients_calculate_size(int type, int current, struct client *c,
 	if (type == WINDOW_SIZE_LATEST && w != NULL)
 		n = clients_with_window(w);
 
+	/* Skip setting the size if manual */
+	if (type == WINDOW_SIZE_MANUAL)
+		goto skip;
+
 	/* Loop over the clients and work out the size. */
 	TAILQ_FOREACH(loop, &clients, entry) {
 		if (loop != c && ignore_client_size(loop)) {
-			log_debug("%s: ignoring %s", __func__, loop->name);
+			log_debug("%s: ignoring %s (1)", __func__, loop->name);
 			continue;
 		}
 		if (loop != c && skip_client(loop, type, current, s, w)) {
-			log_debug("%s: skipping %s", __func__, loop->name);
+			log_debug("%s: skipping %s (1)", __func__, loop->name);
 			continue;
 		}
 
@@ -160,9 +168,23 @@ clients_calculate_size(int type, int current, struct client *c,
 			continue;
 		}
 
+		/*
+		 * If the client has a per-window size, use this instead if it is
+		 * smaller.
+		 */
+		if (w != NULL)
+			cw = server_client_get_client_window(loop, w->id);
+		else
+			cw = NULL;
+
 		/* Work out this client's size. */
-		cx = loop->tty.sx;
-		cy = loop->tty.sy - status_line_size(loop);
+		if (cw != NULL) {
+			cx = cw->sx;
+			cy = cw->sy;
+		} else {
+			cx = loop->tty.sx;
+			cy = loop->tty.sy - status_line_size(loop);
+		}
 
 		/*
 		 * If it is larger or smaller than the best so far, update the
@@ -191,7 +213,44 @@ clients_calculate_size(int type, int current, struct client *c,
 	else
 		log_debug("%s: no calculated size", __func__);
 
+skip:
+	/*
+	 * Do not allow any size to be larger than the per-client window size
+	 * if one exists.
+	 */
+	if (w != NULL) {
+		TAILQ_FOREACH(loop, &clients, entry) {
+			if (loop != c && ignore_client_size(loop))
+				continue;
+			if (loop != c && skip_client(loop, type, current, s, w))
+				continue;
+
+			/* Look up per-window size if any. */
+			if (~loop->flags & CLIENT_WINDOWSIZECHANGED)
+				continue;
+			cw = server_client_get_client_window(loop, w->id);
+			if (cw == NULL)
+				continue;
+
+			/* Clamp the size. */
+			log_debug("%s: %s size for @%u is %ux%u", __func__,
+			    loop->name, w->id, cw->sx, cw->sy);
+			if (cw->sx != 0 && *sx > cw->sx)
+				*sx = cw->sx;
+			if (cw->sy != 0 && *sy > cw->sy)
+				*sy = cw->sy;
+		}
+	}
+	if (*sx != UINT_MAX && *sy != UINT_MAX)
+		log_debug("%s: calculated size %ux%u", __func__, *sx, *sy);
+	else
+		log_debug("%s: no calculated size", __func__);
+
 	/* Return whether a suitable size was found. */
+	if (type == WINDOW_SIZE_MANUAL) {
+		log_debug("%s: type is manual", __func__);
+		return (1);
+	}
 	if (type == WINDOW_SIZE_LARGEST) {
 		log_debug("%s: type is largest", __func__);
 		return (*sx != 0 && *sy != 0);
diff --git a/server-client.c b/server-client.c
index 3efee844..1290c2a6 100644
--- a/server-client.c
+++ b/server-client.c
@@ -2121,6 +2121,7 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 	int			  argc;
 	char			**argv, *cause;
 	struct cmd_parse_result	 *pr;
+	struct args_value	 *values;
 
 	if (c->flags & CLIENT_EXIT)
 		return;
@@ -2146,7 +2147,8 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 		*argv = xstrdup("new-session");
 	}
 
-	pr = cmd_parse_from_arguments(argc, argv, NULL);
+	values = args_from_vector(argc, argv);
+	pr = cmd_parse_from_arguments(values, argc, NULL);
 	switch (pr->status) {
 	case CMD_PARSE_ERROR:
 		cause = pr->error;
@@ -2154,6 +2156,8 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
 	case CMD_PARSE_SUCCESS:
 		break;
 	}
+	args_free_values(values, argc);
+	free(values);
 	cmd_free_argv(argc, argv);
 
 	cmdq_append(c, cmdq_get_command(pr->cmdlist, NULL));
@@ -2444,7 +2448,7 @@ server_client_get_flags(struct client *c)
 }
 
 /* Get client window. */
-static struct client_window *
+struct client_window *
 server_client_get_client_window(struct client *c, u_int id)
 {
 	struct client_window	cw = { .window = id };
@@ -2452,6 +2456,21 @@ server_client_get_client_window(struct client *c, u_int id)
 	return (RB_FIND(client_windows, &c->windows, &cw));
 }
 
+/* Add client window. */
+struct client_window *
+server_client_add_client_window(struct client *c, u_int id)
+{
+	struct client_window	*cw;
+
+	cw = server_client_get_client_window(c, id);
+	if (cw == NULL) {
+		cw = xcalloc(1, sizeof *cw);
+		cw->window = id;
+		RB_INSERT(client_windows, &c->windows, cw);
+	}
+	return cw;
+}
+
 /* Get client active pane. */
 struct window_pane *
 server_client_get_pane(struct client *c)
@@ -2480,12 +2499,7 @@ server_client_set_pane(struct client *c, struct window_pane *wp)
 	if (s == NULL)
 		return;
 
-	cw = server_client_get_client_window(c, s->curw->window->id);
-	if (cw == NULL) {
-		cw = xcalloc(1, sizeof *cw);
-		cw->window = s->curw->window->id;
-		RB_INSERT(client_windows, &c->windows, cw);
-	}
+	cw = server_client_add_client_window(c, s->curw->window->id);
 	cw->pane = wp;
 	log_debug("%s pane now %%%u", c->name, wp->id);
 }
diff --git a/tmux.1 b/tmux.1
index 2f5ac243..b8e96984 100644
--- a/tmux.1
+++ b/tmux.1
@@ -1331,7 +1331,7 @@ specified multiple times.
 .Op Fl cDlLRSU
 .Op Fl A Ar pane:state
 .Op Fl B Ar name:what:format
-.Op Fl C Ar XxY
+.Op Fl C Ar size
 .Op Fl f Ar flags
 .Op Fl t Ar target-client
 .Op Ar adjustment
@@ -1375,7 +1375,17 @@ window, changing the current window in the attached session will reset
 it.
 .Pp
 .Fl C
-sets the width and height of a control mode client.
+sets the width and height of a control mode client or of a window for a
+control mode client,
+.Ar size
+must be one of
+.Ql widthxheight
+or
+.Ql window ID:widthxheight ,
+for example
+.Ql 80x24
+or
+.Ql @0:80x24 .
 .Fl A
 allows a control mode client to trigger actions on a pane.
 The argument is a pane ID (with leading
diff --git a/tmux.h b/tmux.h
index e363fc75..c95daa46 100644
--- a/tmux.h
+++ b/tmux.h
@@ -999,6 +999,8 @@ struct window {
 
 	u_int		 sx;
 	u_int		 sy;
+	u_int		 manual_sx;
+	u_int		 manual_sy;
 	u_int		 xpixel;
 	u_int		 ypixel;
 
@@ -1556,6 +1558,10 @@ RB_HEAD(client_files, client_file);
 struct client_window {
 	u_int			 window;
 	struct window_pane	*pane;
+
+	u_int			 sx;
+	u_int			 sy;
+
 	RB_ENTRY(client_window)	 entry;
 };
 RB_HEAD(client_windows, client_window);
@@ -1651,6 +1657,7 @@ struct client {
 #define CLIENT_ACTIVEPANE 0x80000000ULL
 #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL
 #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL
+#define CLIENT_WINDOWSIZECHANGED 0x400000000ULL
 #define CLIENT_ALLREDRAWFLAGS		\
 	(CLIENT_REDRAWWINDOW|		\
 	 CLIENT_REDRAWSTATUS|		\
@@ -2211,8 +2218,11 @@ void		 args_set(struct args *, u_char, struct args_value *);
 struct args 	*args_create(void);
 struct args	*args_parse(const struct args_parse *, struct args_value *,
 		     u_int, char **);
-void		 args_vector(struct args *, int *, char ***);
+struct args	*args_copy(struct args *, int, char **);
+void		 args_to_vector(struct args *, int *, char ***);
+struct args_value *args_from_vector(int, char **);
 void		 args_free_value(struct args_value *);
+void		 args_free_values(struct args_value *, u_int);
 void		 args_free(struct args *);
 char		*args_print(struct args *);
 char		*args_escape(const char *);
@@ -2221,6 +2231,7 @@ const char	*args_get(struct args *, u_char);
 u_char		 args_first(struct args *, struct args_entry **);
 u_char		 args_next(struct args_entry **);
 u_int		 args_count(struct args *);
+struct args_value *args_values(struct args *);
 struct args_value *args_value(struct args *, u_int);
 const char	*args_string(struct args *, u_int);
 struct cmd_list	*args_make_commands_now(struct cmd *, struct cmdq_item *,
@@ -2285,9 +2296,11 @@ u_int		 cmd_get_group(struct cmd *);
 void		 cmd_get_source(struct cmd *, const char **, u_int *);
 struct cmd	*cmd_parse(struct args_value *, u_int, const char *, u_int,
 		     char **);
+struct cmd	*cmd_copy(struct cmd *, int, char **);
 void		 cmd_free(struct cmd *);
 char		*cmd_print(struct cmd *);
 struct cmd_list	*cmd_list_new(void);
+struct cmd_list	*cmd_list_copy(struct cmd_list *, int, char **);
 void		 cmd_list_append(struct cmd_list *, struct cmd *);
 void		 cmd_list_append_all(struct cmd_list *, struct cmd_list *);
 void		 cmd_list_move(struct cmd_list *, struct cmd_list *);
@@ -2321,7 +2334,7 @@ enum cmd_parse_status cmd_parse_and_append(const char *,
 		     struct cmdq_state *, char **);
 struct cmd_parse_result *cmd_parse_from_buffer(const void *, size_t,
 		     struct cmd_parse_input *);
-struct cmd_parse_result *cmd_parse_from_arguments(int, char **,
+struct cmd_parse_result *cmd_parse_from_arguments(struct args_value *, u_int,
 		     struct cmd_parse_input *);
 
 /* cmd-queue.c */
@@ -2466,6 +2479,8 @@ void	 server_client_push_stderr(struct client *);
 const char *server_client_get_cwd(struct client *, struct session *);
 void	 server_client_set_flags(struct client *, const char *);
 const char *server_client_get_flags(struct client *);
+struct client_window *server_client_get_client_window(struct client *, u_int);
+struct client_window *server_client_add_client_window(struct client *, u_int);
 struct window_pane *server_client_get_pane(struct client *);
 void	 server_client_set_pane(struct client *, struct window_pane *);
 void	 server_client_remove_pane(struct window_pane *);
diff --git a/window.c b/window.c
index 5e256874..ec9644bc 100644
--- a/window.c
+++ b/window.c
@@ -316,6 +316,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
 
 	w->sx = sx;
 	w->sy = sy;
+	w->manual_sx = sx;
+	w->manual_sy = sy;
 	w->xpixel = xpixel;
 	w->ypixel = ypixel;