Compare commits

...
63 Commits
Author SHA1 Message Date
Nicholas Marriott c624de9f3c Merge branch 'master' into command_parser
# Conflicts:
#	arguments.c
#	cmd-command-prompt.c
#	cmd-parse.y
#	cmd-run-shell.c
#	window-panes.c
2026-09-01 20:17:10 +01:00
Nicholas Marriott cf3ab3cad9 Merge branch 'master' into command_parser 2026-08-19 20:57:59 +01:00
Nicholas Marriott c6f62289b0 Merge branch 'master' into command_parser 2026-08-07 16:08:26 +01:00
Nicholas Marriott b114b7e089 Update tmux.1. 2026-07-29 15:11:38 +01:00
Nicholas Marriott f85b015dc1 Merge branch 'master' into command_parser 2026-07-29 13:37:36 +01:00
Nicholas Marriott 899b4b5471 Merge branch 'master' into command_parser 2026-07-29 10:38:22 +01:00
Nicholas Marriott 16064a410e Merge branch 'master' into command_parser 2026-07-21 08:34:58 +01:00
Your Name fcc7366e86 Merge branch 'master' into command_parser 2026-07-16 21:26:42 +01:00
Nicholas Marriott 5d2b6542b6 Fix control mode error and update regression tests. 2026-07-15 11:05:15 +01:00
Nicholas Marriott 66e6b2e1c3 Merge master into command_parser 2026-07-15 10:34:14 +01:00
Nicholas Marriott baf71480d0 Merge remote-tracking branch 'origin/master' into command_parser
# Conflicts:
#	cmd-queue.c
#	notify.c
#	options.c
2026-07-13 11:12:46 +01:00
Nicholas Marriott 5838b45315 Merge branch 'master' into command_parser 2026-07-02 22:55:04 +01:00
Nicholas Marriott cfa755a961 Rm a commment. 2026-07-01 17:37:35 +01:00
Nicholas Marriott 82683707bc Merge branch 'master' into command_parser 2026-07-01 14:53:15 +01:00
Nicholas Marriott 3ee24bde1a Merge branch 'master' into command_parser 2026-07-01 12:51:34 +01:00
Nicholas Marriott 4f7bef5083 Use only one space with -p. 2026-07-01 12:41:14 +01:00
Nicholas Marriott 80b1f20e7d Use \; at top level also. 2026-07-01 12:32:51 +01:00
Nicholas Marriott 111a11ac6a Expand tabs in print. 2026-07-01 12:25:45 +01:00
Nicholas Marriott 1853c220a4 Only print one line by default. 2026-07-01 08:22:21 +01:00
Nicholas Marriott 115002748b Add some tests. 2026-07-01 07:35:55 +01:00
Nicholas Marriott db8f9b4c52 Fix various problems. 2026-07-01 07:07:49 +01:00
Nicholas Marriott 4951eac7fa Remove parser test. 2026-07-01 00:19:19 +01:00
Nicholas Marriott 185ae656e0 Merge branch 'master' into command_parser 2026-07-01 00:13:28 +01:00
Nicholas Marriott ab3da84e34 Handle aliases. 2026-07-01 00:02:10 +01:00
Nicholas Marriott bce30eefd3 Remove unused end_line. 2026-06-30 22:38:35 +01:00
Nicholas Marriott 1302bbde07 Remove stray decl. 2026-06-30 22:37:02 +01:00
Nicholas Marriott e935342702 More cleanup. 2026-06-30 22:32:36 +01:00
Nicholas Marriott 6d56dd9e7b Move file and flags into parser. 2026-06-30 22:11:25 +01:00
Nicholas Marriott 8ca7434d65 Remove stray leftovers. 2026-06-30 21:55:01 +01:00
Nicholas Marriott a9cff084c0 Remove cmd_list and old stuff. 2026-06-30 21:50:26 +01:00
Nicholas Marriott 186f51b7ee Do not report errors using client before one is set. 2026-06-30 19:36:38 +01:00
Nicholas Marriott 298b35c8a9 Fail on any error. 2026-06-30 18:59:45 +01:00
Nicholas Marriott 7a1cc1d443 Remove XXX. 2026-06-30 18:54:36 +01:00
Nicholas Marriott fefb926291 Better way of working out any flag. 2026-06-30 18:48:58 +01:00
Nicholas Marriott 36273fde4f Better version. 2026-06-30 18:33:36 +01:00
Nicholas Marriott 8ac29f6587 Handle ; better. 2026-06-30 18:31:09 +01:00
Nicholas Marriott db9b54f232 Handle ; better. 2026-06-30 18:30:44 +01:00
Nicholas Marriott 54f5c82500 Convert most of remaining bits. 2026-06-30 18:19:26 +01:00
Nicholas Marriott b3bb8bbd9f Options and hooks updates. 2026-06-30 17:57:38 +01:00
Nicholas Marriott c92720e3b0 Preserve file and flags. 2026-06-30 17:37:15 +01:00
Nicholas Marriott 08500f844d Do not use item if NULL. 2026-06-30 17:24:28 +01:00
Nicholas Marriott a97912a4f8 Simplify args stuff. 2026-06-30 17:19:37 +01:00
Your Name 91ea9e4beb Init. 2026-06-30 16:34:10 +01:00
Your Name b30f5f82e0 Switch over args stuff. 2026-06-30 16:30:09 +01:00
Your Name d4eca8d586 Read only bits. 2026-06-30 15:45:16 +01:00
Nicholas Marriott 8908f50524 Allow input to be NULL. 2026-06-30 15:42:25 +01:00
Your Name eb2e26f4cd Default command tree bits. 2026-06-30 15:33:37 +01:00
Nicholas Marriott 9569c57fc3 Some key bits. 2026-06-30 15:24:20 +01:00
Nicholas Marriott b48e2ea8e9 Handle key binding. 2026-06-30 11:20:04 +01:00
Your Name f7cd6ab547 No need to escape since logging already does it. 2026-06-30 10:38:39 +01:00
Your Name 401793eba6 Fix indentation. 2026-06-30 10:06:54 +01:00
Your Name fcfa0b6db4 Allow simple commands to be fired. 2026-06-30 10:03:49 +01:00
Your Name 5f7a4b0f52 Remove unused arguments. 2026-06-30 09:47:33 +01:00
Your Name bcc2b7ad1e Remove pointless debug. 2026-06-30 09:41:23 +01:00
Your Name 9e8cf6f0e2 Stub out all users of old API. 2026-06-30 09:39:45 +01:00
Your Name b8a62979ac Log func also. 2026-06-30 09:39:34 +01:00
Your Name 45f4000017 Not so much const. 2026-06-30 09:36:26 +01:00
Your Name 572b32766c Remove old bits. 2026-06-30 08:43:02 +01:00
Your Name e6ad44c8a3 Fix some errors. 2026-06-30 08:40:10 +01:00
Nicholas Marriott 96cd98560b Include Makefile. 2026-06-30 08:34:42 +01:00
Your Name ac23a47a5f Use correct branch for if. 2026-06-30 08:33:07 +01:00
Your Name 22b6ad6fce Add invoke API. 2026-06-30 08:24:44 +01:00
Nicholas Marriott efe2453eac New tmux parser and test program. 2026-06-30 07:27:06 +01:00
41 changed files with 3731 additions and 2231 deletions
+1
View File
@@ -104,6 +104,7 @@ dist_tmux_SOURCES = \
cmd-find-window.c \
cmd-find.c \
cmd-if-shell.c \
cmd-invoke.c \
cmd-join-pane.c \
cmd-kill-pane.c \
cmd-kill-server.c \
+43 -142
View File
@@ -51,13 +51,6 @@ struct args {
struct args_value *values;
};
/* Prepared command state. */
struct args_command_state {
struct cmd_list *cmdlist;
char *cmd;
struct cmd_parse_input pi;
};
static struct args_entry *args_find(struct args *, u_char);
static int args_cmp(struct args_entry *, struct args_entry *);
@@ -89,8 +82,7 @@ args_copy_value(struct args_value *to, struct args_value *from)
case ARGS_NONE:
break;
case ARGS_COMMANDS:
to->cmdlist = from->cmdlist;
to->cmdlist->references++;
to->cmd = cmd_parse_add_ref(from->cmd);
break;
case ARGS_STRING:
to->string = xstrdup(from->string);
@@ -123,7 +115,7 @@ args_value_as_string(struct args_value *value)
return ("");
case ARGS_COMMANDS:
if (value->cached == NULL)
value->cached = cmd_list_print(value->cmdlist, 0);
value->cached = cmd_parse_print(value->cmd, 0);
return (value->cached);
case ARGS_STRING:
return (value->string);
@@ -367,7 +359,7 @@ args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
to->string = expanded;
break;
case ARGS_COMMANDS:
to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
to->cmd = cmd_parse_add_ref(from->cmd);
break;
}
}
@@ -418,7 +410,7 @@ args_free_value(struct args_value *value)
free(value->string);
break;
case ARGS_COMMANDS:
cmd_list_free(value->cmdlist);
cmd_parse_free(value->cmd);
break;
}
free(value->cached);
@@ -477,7 +469,7 @@ args_to_vector(struct args *args, int *argc, char ***argv)
cmd_append_argv(argc, argv, args->values[i].string);
break;
case ARGS_COMMANDS:
s = cmd_list_print(args->values[i].cmdlist, 0);
s = cmd_parse_print(args->values[i].cmd, 0);
cmd_append_argv(argc, argv, s);
free(s);
break;
@@ -532,7 +524,7 @@ args_print_add_value(char **buf, size_t *len, struct args_value *value)
case ARGS_NONE:
break;
case ARGS_COMMANDS:
expanded = cmd_list_print(value->cmdlist, 0);
expanded = cmd_parse_print(value->cmd, 0);
args_print_add(buf, len, "{ %s }", expanded);
break;
case ARGS_STRING:
@@ -747,144 +739,53 @@ args_string(struct args *args, u_int idx)
return (args_value_as_string(&args->values[idx]));
}
/* Make a command now. */
struct cmd_list *
args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
int expand)
/*
* Convert one command argument into a parse tree. A string value is parsed;
* existing commands are referenced.
*/
struct cmd_parse_tree *
args_to_commands(struct cmd *self, struct cmdq_item *item, u_int idx,
const char *default_command, int expand, char **cause)
{
struct args_command_state *state;
char *error;
struct cmd_list *cmdlist;
struct args *args = cmd_get_args(self);
struct args_value *value;
struct cmd_parse_input pi = { 0 };
const char *cmd = NULL, *file;
char *expanded;
struct cmd_parse_tree *tree;
state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
cmdlist = args_make_commands(state, 0, NULL, &error);
if (cmdlist == NULL) {
cmdq_error(item, "%s", error);
free(error);
}
args_make_commands_free(state);
return (cmdlist);
}
*cause = NULL;
/* Save bits to make a command later. */
struct args_command_state *
args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
const char *default_command, int wait, int expand)
{
struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct client *tc = cmdq_get_target_client(item);
struct args_value *value;
struct args_command_state *state;
const char *cmd;
const char *file;
state = xcalloc(1, sizeof *state);
if (idx < args->count) {
value = &args->values[idx];
if (value->type == ARGS_COMMANDS) {
state->cmdlist = value->cmdlist;
state->cmdlist->references++;
return (state);
}
cmd = value->string;
} else {
if (default_command == NULL)
fatalx("argument out of range");
if (idx >= args->count)
cmd = default_command;
else {
value = &args->values[idx];
switch (value->type) {
case ARGS_COMMANDS:
return (cmd_parse_add_ref(value->cmd));
case ARGS_STRING:
cmd = value->string;
break;
case ARGS_NONE:
break;
}
}
if (cmd == NULL)
fatalx("argument out of range");
if (expand)
state->cmd = format_single_from_target(item, cmd);
expanded = format_single_from_target(item, cmd);
else
state->cmd = xstrdup(cmd);
log_debug("%s: %s", __func__, state->cmd);
expanded = xstrdup(cmd);
log_debug("%s: %s", __func__, expanded);
if (wait)
state->pi.item = item;
cmd_get_source(self, &file, &state->pi.line);
cmd_get_source(self, &file, &pi.line);
if (file != NULL)
state->pi.file = xstrdup(file);
state->pi.c = tc;
if (state->pi.c != NULL)
state->pi.c->references++;
cmd_find_copy_state(&state->pi.fs, target);
return (state);
}
/* Return argument as command. */
struct cmd_list *
args_make_commands(struct args_command_state *state, int argc, char **argv,
char **error)
{
struct cmd_parse_result *pr;
char *cmd, *new_cmd;
int i;
if (state->cmdlist != NULL) {
if (argc == 0) {
state->cmdlist->references++;
return (state->cmdlist);
}
return (cmd_list_copy(state->cmdlist, argc, argv));
}
cmd = xstrdup(state->cmd);
log_debug("%s: %s", __func__, cmd);
cmd_log_argv(argc, argv, __func__);
for (i = 0; i < argc; i++) {
new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
free(cmd);
cmd = new_cmd;
}
log_debug("%s: %s", __func__, cmd);
pr = cmd_parse_from_string(cmd, &state->pi);
free(cmd);
switch (pr->status) {
case CMD_PARSE_ERROR:
*error = pr->error;
return (NULL);
case CMD_PARSE_SUCCESS:
return (pr->cmdlist);
}
fatalx("invalid parse return state");
}
/* Free commands state. */
void
args_make_commands_free(struct args_command_state *state)
{
if (state->cmdlist != NULL)
cmd_list_free(state->cmdlist);
if (state->pi.c != NULL)
server_client_unref(state->pi.c);
free((void *)state->pi.file);
free(state->cmd);
free(state);
}
/* Get prepared command. */
char *
args_make_commands_get_command(struct args_command_state *state)
{
struct cmd *first;
int n;
char *s;
if (state->cmdlist != NULL) {
first = cmd_list_first(state->cmdlist);
if (first == NULL)
return (xstrdup(""));
return (xstrdup(cmd_get_entry(first)->name));
}
n = strcspn(state->cmd, " ,");
xasprintf(&s, "%.*s", n, state->cmd);
return (s);
pi.file = file;
pi.flags = cmd_get_parse_flags(self);
tree = cmd_parse_from_string(expanded, &pi, cause);
free(expanded);
return (tree);
}
/* Get first value in argument. */
+24 -26
View File
@@ -87,20 +87,21 @@ start_cfg(void)
if (cfg_quiet)
flags = CMD_PARSE_QUIET;
for (i = 0; i < cfg_nfiles; i++)
load_cfg(cfg_files[i], c, NULL, NULL, flags, NULL);
load_cfg(cfg_files[i], NULL, NULL, flags, NULL);
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
}
int
load_cfg(const char *path, struct client *c, struct cmdq_item *item,
load_cfg(const char *path, struct cmdq_item *item,
struct cmd_find_state *current, int flags, struct cmdq_item **new_item)
{
FILE *f;
struct cmd_parse_input pi;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item0;
struct cmdq_state *state;
char *cause = NULL;
if (new_item != NULL)
*new_item = NULL;
@@ -117,18 +118,16 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
pi.flags = flags;
pi.file = path;
pi.line = 1;
pi.item = item;
pi.c = c;
pr = cmd_parse_from_file(f, &pi);
tree = cmd_parse_from_file(f, &pi, &cause);
fclose(f);
if (pr->status == CMD_PARSE_ERROR) {
cfg_add_cause("%s", pr->error);
free(pr->error);
if (tree == NULL) {
cfg_add_cause("%s", cause);
free(cause);
return (-1);
}
cmd_parse_log(__func__, tree);
if (flags & CMD_PARSE_PARSEONLY) {
cmd_list_free(pr->cmdlist);
cmd_parse_free(tree);
return (0);
}
@@ -138,12 +137,12 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
state = cmdq_new_state(NULL, NULL, 0);
cmdq_add_format(state, "current_file", "%s", pi.file);
new_item0 = cmdq_get_command(pr->cmdlist, state);
new_item0 = cmd_invoke_get(tree, state, NULL);
if (item != NULL)
new_item0 = cmdq_insert_after(item, new_item0);
else
new_item0 = cmdq_append(NULL, new_item0);
cmd_list_free(pr->cmdlist);
cmd_parse_free(tree);
cmdq_free_state(state);
if (new_item != NULL)
@@ -153,13 +152,14 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item,
int
load_cfg_from_buffer(const void *buf, size_t len, const char *path,
struct client *c, struct cmdq_item *item, struct cmd_find_state *current,
int flags, struct cmdq_item **new_item)
struct cmdq_item *item, struct cmd_find_state *current, int flags,
struct cmdq_item **new_item)
{
struct cmd_parse_input pi;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item0;
struct cmdq_state *state;
char *cause = NULL;
if (new_item != NULL)
*new_item = NULL;
@@ -170,17 +170,15 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
pi.flags = flags;
pi.file = path;
pi.line = 1;
pi.item = item;
pi.c = c;
pr = cmd_parse_from_buffer(buf, len, &pi);
if (pr->status == CMD_PARSE_ERROR) {
cfg_add_cause("%s", pr->error);
free(pr->error);
tree = cmd_parse_from_buffer(buf, len, &pi, &cause);
if (tree == NULL) {
cfg_add_cause("%s", cause);
free(cause);
return (-1);
}
cmd_parse_log(__func__, tree);
if (flags & CMD_PARSE_PARSEONLY) {
cmd_list_free(pr->cmdlist);
cmd_parse_free(tree);
return (0);
}
@@ -190,12 +188,12 @@ load_cfg_from_buffer(const void *buf, size_t len, const char *path,
state = cmdq_new_state(NULL, NULL, 0);
cmdq_add_format(state, "current_file", "%s", pi.file);
new_item0 = cmdq_get_command(pr->cmdlist, state);
new_item0 = cmd_invoke_get(tree, state, NULL);
if (item != NULL)
new_item0 = cmdq_insert_after(item, new_item0);
else
new_item0 = cmdq_append(NULL, new_item0);
cmd_list_free(pr->cmdlist);
cmd_parse_free(tree);
cmdq_free_state(state);
if (new_item != NULL)
+21 -18
View File
@@ -69,6 +69,25 @@ static void client_dispatch_attached(struct imsg *);
static void client_dispatch_wait(struct imsg *);
static const char *client_exit_message(void);
/* Does any command have a flag? */
static int
client_command_has_flag(int argc, char **argv, int flag)
{
struct args_value *values;
struct cmd_parse_tree *tree;
int found;
values = args_from_vector(argc, argv);
tree = cmd_parse_from_arguments(values, argc, NULL);
found = cmd_parse_any_have(tree, flag);
cmd_parse_free(tree);
args_free_values(values, argc);
free(values);
return (found);
}
/*
* Get server create lock. If already held then server start is happening in
* another client, so block until the lock is released and return -2 to
@@ -232,7 +251,6 @@ int
client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
int feat)
{
struct cmd_parse_result *pr;
struct msg_command *data;
int fd, i;
const char *ttynam, *termname, *cwd;
@@ -242,7 +260,6 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
size_t size;
char **caps = NULL, *cause;
u_int ncaps = 0;
struct args_value *values;
/* Set up the initial command. */
if (shell_command != NULL) {
@@ -253,22 +270,8 @@ client_main(struct event_base *base, int argc, char **argv, uint64_t flags,
flags |= CLIENT_STARTSERVER;
} else {
msg = MSG_COMMAND;
/*
* It's annoying parsing the command string twice (in client
* and later in server) but it is necessary to get the start
* server flag.
*/
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);
if (client_command_has_flag(argc, argv, CMD_STARTSERVER))
flags |= CLIENT_STARTSERVER;
}
/* Create client process structure (starts logging). */
+18 -24
View File
@@ -54,13 +54,14 @@ cmd_bind_key_args_parse(__unused struct args *args, __unused u_int idx,
static enum cmd_retval
cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = cmd_get_args(self);
key_code key;
const char *tablename, *note = args_get(args, 'N');
struct cmd_parse_result *pr;
int repeat;
struct args_value *value;
u_int count = args_count(args);
struct args *args = cmd_get_args(self);
key_code key;
const char *tablename, *note = args_get(args, 'N'), *file;
int repeat;
struct args_value *value;
u_int count = args_count(args);
struct cmd_parse_tree *cmd;
struct cmd_parse_input pi;
key = key_string_lookup_string(args_string(args, 0));
if (key == KEYC_NONE || key == KEYC_UNKNOWN) {
@@ -83,25 +84,18 @@ cmd_bind_key_exec(struct cmd *self, struct cmdq_item *item)
value = args_value(args, 1);
if (count == 2 && value->type == ARGS_COMMANDS) {
key_bindings_add(tablename, key, note, repeat, value->cmdlist);
value->cmdlist->references++;
cmd = cmd_parse_add_ref(value->cmd);
key_bindings_add(tablename, key, note, repeat, cmd);
return (CMD_RETURN_NORMAL);
}
if (count == 2)
pr = cmd_parse_from_string(args_string(args, 1), NULL);
else {
pr = cmd_parse_from_arguments(args_values(args) + 1, count - 1,
NULL);
}
switch (pr->status) {
case CMD_PARSE_ERROR:
cmdq_error(item, "%s", pr->error);
free(pr->error);
return (CMD_RETURN_ERROR);
case CMD_PARSE_SUCCESS:
break;
}
key_bindings_add(tablename, key, note, repeat, pr->cmdlist);
memset(&pi, 0, sizeof pi);
cmd_get_source(self, &file, &pi.line);
if (file != NULL)
pi.file = file;
pi.flags = cmd_get_parse_flags(self);
cmd = cmd_parse_from_arguments(args_values(args) + 1, count - 1, &pi);
key_bindings_add(tablename, key, note, repeat, cmd);
return (CMD_RETURN_NORMAL);
}
+51 -23
View File
@@ -57,7 +57,7 @@ struct cmd_command_prompt_prompt {
struct cmd_command_prompt_cdata {
struct cmdq_item *item;
struct args_command_state *state;
struct cmd_parse_tree *tree;
int flags;
enum prompt_type prompt_type;
@@ -85,13 +85,13 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
struct args *args = cmd_get_args(self);
struct client *tc = cmdq_get_target_client(item);
struct cmd_find_state *target = cmdq_get_target(item);
const char *type, *s, *input;
const char *type, *s, *input, *cmd;
struct cmd_command_prompt_cdata *cdata;
char *tmp, *prompts, *prompt, *next_prompt;
char *inputs = NULL, *next_input;
char *inputs = NULL, *next_input, *cause = NULL;
struct window_pane *wp = target->wp;
u_int count = args_count(args);
int wait = !args_has(args, 'b'), space = 1;
int wait = !args_has(args, 'b'), space = 1, n;
int pane = args_has(args, 'P');
if (pane) {
@@ -107,14 +107,22 @@ cmd_command_prompt_exec(struct cmd *self, struct cmdq_item *item)
cdata->item = item;
if (pane)
cdata->wp = wp;
cdata->state = args_make_commands_prepare(self, item, 0, "%1", wait,
args_has(args, 'F'));
if (count != 0) {
cdata->tree = args_to_commands(self, item, 0, NULL,
args_has(args, 'F'), &cause);
if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(cdata);
return (CMD_RETURN_ERROR);
}
}
if ((s = args_get(args, 'p')) == NULL) {
if (count != 0) {
tmp = args_make_commands_get_command(cdata->state);
xasprintf(&prompts, "(%s)", tmp);
free(tmp);
cmd = args_string(args, 0);
n = strcspn(cmd, " ,");
xasprintf(&prompts, "(%.*s)", n, cmd);
} else {
prompts = xstrdup(":");
space = 0;
@@ -201,8 +209,11 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
struct cmd_command_prompt_cdata *cdata = data;
char *error;
struct cmdq_item *item = cdata->item, *new_item;
struct cmd_list *cmdlist;
struct cmdq_state *cs = NULL;
struct cmd_command_prompt_prompt *prompt;
struct cmd_invoke_input ci = { 0 };
struct cmd_parse_tree *tree;
struct cmd_parse_input pi = { 0 };
int argc = 0;
char **argv = NULL;
@@ -235,18 +246,35 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
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);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
if (item != NULL)
cs = cmdq_get_state(item);
if (cdata->tree != NULL) {
/* Explicit body: prompt inputs become the template argv. */
ci.argc = argc;
ci.argv = argv;
new_item = cmd_invoke_get(cdata->tree, cs, &ci);
if (item == NULL)
cmdq_append(c, new_item);
else
cmdq_insert_after(item, new_item);
} else if (argc > 0 && argv[0] != NULL) {
/* No body: parse the submitted text as command language. */
tree = cmd_parse_from_string(argv[0], &pi, &error);
if (tree == NULL) {
if (item == NULL)
cmdq_append(c, cmdq_get_error(error));
else
cmdq_error(item, "%s", error);
free(error);
} else {
new_item = cmd_invoke_get(tree, cs, NULL);
cmd_parse_free(tree);
if (item == NULL)
cmdq_append(c, new_item);
else
cmdq_insert_after(item, new_item);
}
}
cmd_free_argv(argc, argv);
@@ -283,6 +311,6 @@ cmd_command_prompt_free(void *data)
}
free(cdata->prompts);
cmd_free_argv(cdata->argc, cdata->argv);
args_make_commands_free(cdata->state);
cmd_parse_free(cdata->tree);
free(cdata);
}
+16 -13
View File
@@ -51,7 +51,7 @@ const struct cmd_entry cmd_confirm_before_entry = {
struct cmd_confirm_before_data {
struct cmdq_item *item;
struct cmd_list *cmdlist;
struct cmd_parse_tree *tree;
u_char confirm_key;
int default_yes;
};
@@ -70,13 +70,15 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_confirm_before_data *cdata;
struct client *tc = cmdq_get_target_client(item);
struct cmd_find_state *target = cmdq_get_target(item);
char *new_prompt;
char *new_prompt, *cause = NULL;
const char *confirm_key, *prompt, *cmd;
int wait = !args_has(args, 'b');
int wait = !args_has(args, 'b'), n;
cdata = xcalloc(1, sizeof *cdata);
cdata->cmdlist = args_make_commands_now(self, item, 0, 1);
if (cdata->cmdlist == NULL) {
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
free(cdata);
return (CMD_RETURN_ERROR);
}
@@ -92,8 +94,7 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
cdata->confirm_key = confirm_key[0];
else {
cmdq_error(item, "invalid confirm key");
cmd_list_free(cdata->cmdlist);
free(cdata);
cmd_confirm_before_free(cdata);
return (CMD_RETURN_ERROR);
}
}
@@ -103,8 +104,9 @@ cmd_confirm_before_exec(struct cmd *self, struct cmdq_item *item)
if ((prompt = args_get(args, 'p')) != NULL)
xasprintf(&new_prompt, "%s ", prompt);
else {
cmd = cmd_get_entry(cmd_list_first(cdata->cmdlist))->name;
xasprintf(&new_prompt, "Confirm '%s'? (%c/n) ", cmd,
cmd = args_string(args, 0);
n = strcspn(cmd, " ,");
xasprintf(&new_prompt, "Confirm '%.*s'? (%c/n) ", n, cmd,
cdata->confirm_key);
}
@@ -124,6 +126,7 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
{
struct cmd_confirm_before_data *cdata = data;
struct cmdq_item *item = cdata->item, *new_item;
struct cmdq_state *cs;
int retcode = 1;
if (c->flags & CLIENT_DEAD)
@@ -136,11 +139,11 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
retcode = 0;
if (item == NULL) {
new_item = cmdq_get_command(cdata->cmdlist, NULL);
new_item = cmd_invoke_get(cdata->tree, NULL, NULL);
cmdq_append(c, new_item);
} else {
new_item = cmdq_get_command(cdata->cmdlist,
cmdq_get_state(item));
cs = cmdq_get_state(item);
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
cmdq_insert_after(item, new_item);
}
@@ -159,6 +162,6 @@ cmd_confirm_before_free(void *data)
{
struct cmd_confirm_before_data *cdata = data;
cmd_list_free(cdata->cmdlist);
cmd_parse_free(cdata->tree);
free(cdata);
}
+50 -45
View File
@@ -53,11 +53,12 @@ const struct cmd_entry cmd_if_shell_entry = {
};
struct cmd_if_shell_data {
struct args_command_state *cmd_if;
struct args_command_state *cmd_else;
struct cmd_parse_tree *cmd_if;
struct cmd_parse_tree *cmd_else;
struct client *client;
struct cmdq_item *item;
struct client *client;
struct cmdq_item *item;
};
static enum args_parse_type
@@ -76,46 +77,58 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct cmd_if_shell_data *cdata;
struct cmdq_item *new_item;
char *shellcmd;
struct cmd_parse_tree *tree;
char *shellcmd, *cause = NULL;
struct client *tc = cmdq_get_target_client(item);
struct session *s = target->s;
struct cmd_list *cmdlist;
u_int count = args_count(args);
u_int count = args_count(args), idx;
int wait = !args_has(args, 'b');
shellcmd = format_single_from_target(item, args_string(args, 0));
if (args_has(args, 'F')) {
if (*shellcmd != '0' && *shellcmd != '\0')
cmdlist = args_make_commands_now(self, item, 1, 0);
idx = 1;
else if (count == 3)
cmdlist = args_make_commands_now(self, item, 2, 0);
idx = 2;
else {
free(shellcmd);
return (CMD_RETURN_NORMAL);
}
free(shellcmd);
if (cmdlist == NULL)
tree = args_to_commands(self, item, idx, NULL, 0, &cause);
if (tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
return (CMD_RETURN_ERROR);
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
}
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
cmd_parse_free(tree);
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
return (CMD_RETURN_NORMAL);
}
cdata = xcalloc(1, sizeof *cdata);
cdata->cmd_if = args_make_commands_prepare(self, item, 1, NULL, wait,
0);
cdata->cmd_if = args_to_commands(self, item, 1, NULL, 0, &cause);
if (cdata->cmd_if == NULL) {
cmdq_error(item, "%s", cause);
goto fail;
}
if (count == 3) {
cdata->cmd_else = args_make_commands_prepare(self, item, 2,
NULL, wait, 0);
cdata->cmd_else = args_to_commands(self, item, 2, NULL, 0,
&cause);
if (cdata->cmd_else == NULL) {
cmdq_error(item, "%s", cause);
goto fail;
}
}
if (wait) {
if (!wait)
cdata->client = tc;
else {
cdata->client = cmdq_get_client(item);
cdata->item = item;
} else
cdata->client = tc;
}
if (cdata->client != NULL)
cdata->client->references++;
@@ -124,15 +137,19 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0, -1,
-1) == NULL) {
cmdq_error(item, "failed to run command: %s", shellcmd);
free(shellcmd);
cmd_if_shell_free(cdata);
return (CMD_RETURN_ERROR);
goto fail;
}
free(shellcmd);
if (!wait)
return (CMD_RETURN_NORMAL);
return (CMD_RETURN_WAIT);
fail:
free(cause);
free(shellcmd);
cmd_if_shell_free(cdata);
return (CMD_RETURN_ERROR);
}
static void
@@ -141,35 +158,23 @@ cmd_if_shell_callback(struct job *job)
struct cmd_if_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client;
struct cmdq_item *item = cdata->item, *new_item;
struct args_command_state *state;
struct cmd_list *cmdlist;
char *error;
struct cmd_parse_tree *tree;
int status;
status = job_get_status(job);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
state = cdata->cmd_else;
tree = cdata->cmd_else;
else
state = cdata->cmd_if;
if (state == NULL)
tree = cdata->cmd_if;
if (tree == NULL)
goto out;
cmdlist = args_make_commands(state, 0, NULL, &error);
if (cmdlist == NULL) {
if (cdata->item == NULL) {
*error = toupper((u_char)*error);
status_message_set(c, -1, 1, 0, 0, "%s", error);
} else
cmdq_error(cdata->item, "%s", error);
free(error);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
if (item == NULL) {
new_item = cmd_invoke_get(tree, NULL, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
new_item = cmd_invoke_get(tree, cmdq_get_state(item), NULL);
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
}
out:
@@ -186,8 +191,8 @@ cmd_if_shell_free(void *data)
server_client_unref(cdata->client);
if (cdata->cmd_else != NULL)
args_make_commands_free(cdata->cmd_else);
args_make_commands_free(cdata->cmd_if);
cmd_parse_free(cdata->cmd_else);
if (cdata->cmd_if != NULL)
cmd_parse_free(cdata->cmd_if);
free(cdata);
}
+594
View File
@@ -0,0 +1,594 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
*
* 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 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 <pwd.h>
#include <stdlib.h>
#include <string.h>
#include "tmux.h"
#include "tmux-parser.h"
/* One frame in the parse tree stack. */
struct cmd_invoke_frame {
struct cmd_parse_tree *tree;
struct cmd_parse_node *node;
struct cmd_parse_node *next;
struct cmd_parse_node *end;
};
/* Shared state for a parsed command tree invocation. */
struct cmd_invoke_state {
u_int references;
struct cmd_parse_tree *tree;
struct cmd_parse_tree *current_tree;
struct cmd_invoke_frame *stack;
u_int nstack;
enum cmd_retval last;
int have_last;
int argc;
char **argv;
};
static void cmd_invoke_push(struct cmd_invoke_state *,
struct cmd_parse_tree *, struct cmd_parse_node *,
struct cmd_parse_node *, struct cmd_parse_node *);
static struct cmd_parse_tree *cmd_invoke_tree(struct cmd_invoke_state *);
static struct cmd_parse_node *cmd_invoke_next(struct cmd_invoke_state *);
static void cmd_invoke_skip_sequence(struct cmd_invoke_state *);
static int cmd_invoke_expand_string(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *,
char **);
static int cmd_invoke_assignment(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *);
static int cmd_invoke_if(struct cmdq_item *, struct cmd_invoke_state *,
struct cmd_parse_node *);
static struct cmd *cmd_invoke_build_command(struct cmdq_item *,
struct cmd_invoke_state *, struct cmd_parse_node *);
static int cmd_invoke_read_only(struct cmdq_item *, struct cmd *);
/* Push a node's child range onto the traversal stack. */
static void
cmd_invoke_push(struct cmd_invoke_state *is, struct cmd_parse_tree *tree,
struct cmd_parse_node *node, struct cmd_parse_node *first,
struct cmd_parse_node *end)
{
u_int n = is->nstack + 1;
is->stack = xreallocarray(is->stack, n, sizeof *is->stack);
is->stack[is->nstack].tree = cmd_parse_add_ref(tree);
is->stack[is->nstack].node = node;
is->stack[is->nstack].next = first;
is->stack[is->nstack].end = end;
is->nstack = n;
}
static struct cmd_parse_tree *
cmd_invoke_tree(struct cmd_invoke_state *is)
{
if (is->current_tree != NULL)
return (is->current_tree);
return (is->tree);
}
/* Return the next node to execute from the traversal stack. */
static struct cmd_parse_node *
cmd_invoke_next(struct cmd_invoke_state *is)
{
struct cmd_invoke_frame *frame;
struct cmd_parse_node *node;
for (;;) {
if (is->nstack == 0)
return (NULL);
frame = &is->stack[is->nstack - 1];
if (frame->next != NULL && frame->next != frame->end)
break;
cmd_parse_free(frame->tree);
is->nstack--;
}
node = frame->next;
frame->next = cmd_parse_node_next(node);
is->current_tree = frame->tree;
return (node);
}
/*
* Skip the rest of the active sequence after a command failure. Failure scope
* is the active CMD_PARSE_SEQUENCE. Discard nested frames and advance only as
* far as the end of that sequence.
*/
static void
cmd_invoke_skip_sequence(struct cmd_invoke_state *is)
{
enum cmd_parse_node_type type;
u_int i;
for (i = is->nstack; i > 0; i--) {
type = cmd_parse_node_type(is->stack[i - 1].node);
if (type == CMD_PARSE_SEQUENCE) {
is->stack[i - 1].next = is->stack[i - 1].end;
while (is->nstack > i)
cmd_parse_free(is->stack[--is->nstack].tree);
is->nstack = i;
break;
}
}
is->have_last = 0;
}
/* Append a string to a dynamically allocated buffer. */
static void
cmd_invoke_append(char **buf, size_t *len, const char *s)
{
size_t slen;
if (s != NULL) {
slen = strlen(s);
*buf = xrealloc(*buf, *len + slen + 1);
memcpy(*buf + *len, s, slen + 1);
*len += slen;
}
}
/* Look up an environment variable. */
static const char *
cmd_invoke_getenv(struct cmdq_item *item, const char *name)
{
struct client *c = cmdq_get_client(item);
struct environ_entry *envent;
if (c != NULL && c->environ != NULL) {
envent = environ_find(c->environ, name);
if (envent != NULL && envent->value != NULL)
return (envent->value);
}
envent = environ_find(global_environ, name);
if (envent != NULL && envent->value != NULL)
return (envent->value);
return ("");
}
/* Resolve a tilde expansion to a home directory. */
static const char *
cmd_invoke_tilde(const char *name)
{
struct passwd *pw;
if (name == NULL || *name == '\0')
return (find_home());
if ((pw = getpwnam(name)) == NULL)
return ("");
return (pw->pw_dir);
}
/* Expand a parsed string node into an argv string. */
static int
cmd_invoke_expand_string(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node, char **out)
{
struct cmd_parse_node *child;
const char *s, *value;
char *buf = NULL, *new;
size_t len = 0;
int i;
child = cmd_parse_node_first_child(node);
while (child != NULL) {
value = cmd_parse_node_value(child);
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_TEXT:
s = value;
break;
case CMD_PARSE_ENVIRONMENT:
s = cmd_invoke_getenv(item, value);
break;
case CMD_PARSE_TILDE:
s = cmd_invoke_tilde(value);
break;
default:
fatalx("unexpected node type in string");
}
cmd_invoke_append(&buf, &len, s);
child = cmd_parse_node_next(child);
}
if (buf == NULL)
buf = xstrdup("");
for (i = 0; i < is->argc; i++) {
new = cmd_template_replace(buf, is->argv[i], i + 1);
free(buf);
buf = new;
}
*out = buf;
return (0);
}
/* Execute an assignment node by updating the environment. */
static int
cmd_invoke_assignment(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_node *value;
const char *name;
char *expanded;
int flags = 0;
value = cmd_parse_node_first_child(node);
if (value == NULL)
return (-1);
if (cmd_invoke_expand_string(item, is, value, &expanded) != 0)
return (-1);
name = cmd_parse_node_value(node);
if (cmd_parse_node_type(node) == CMD_PARSE_HIDDEN_ASSIGN)
flags |= ENVIRON_HIDDEN;
environ_set(global_environ, name, flags, "%s", expanded);
free(expanded);
return (0);
}
/* Expand and evaluate a conditional expression. */
static int
cmd_invoke_is_true(struct cmdq_item *item,
struct cmd_invoke_state *is, struct cmd_parse_node *node, int *result)
{
struct format_tree *ft;
char *s, *expanded;
if (cmd_invoke_expand_string(item, is, node, &s) != 0)
return (-1);
ft = format_create_from_target(item);
expanded = format_expand(ft, s);
*result = format_true(expanded);
free(expanded);
format_free(ft);
free(s);
return (0);
}
/* Skip else branches. */
static struct cmd_parse_node *
cmd_invoke_if_branch_end(struct cmd_parse_node *first)
{
struct cmd_parse_node *node;
for (node = first; node != NULL; node = cmd_parse_node_next(node)) {
switch (cmd_parse_node_type(node)) {
case CMD_PARSE_ELIF:
case CMD_PARSE_ELSE:
return (node);
default:
break;
}
}
return (NULL);
}
/* Select and queue the branch for a conditional node. */
static int
cmd_invoke_if(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
struct cmd_parse_node *child, *first, *next;
int r;
next = cmd_parse_node_first_child(node);
if (next == NULL)
return (0);
first = cmd_parse_node_next(next);
if (cmd_invoke_is_true(item, is, next, &r) != 0)
return (-1);
if (r) {
next = cmd_invoke_if_branch_end(first);
cmd_invoke_push(is, tree, node, first, next);
return (0);
}
for (child = first; child != NULL; child = cmd_parse_node_next(child)) {
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_ELIF:
next = cmd_parse_node_first_child(child);
if (next == NULL)
break;
if (cmd_invoke_is_true(item, is, next, &r) != 0)
return (-1);
if (r) {
next = cmd_parse_node_next(next);
cmd_invoke_push(is, tree, child, next, NULL);
return (0);
}
break;
case CMD_PARSE_ELSE:
next = cmd_parse_node_first_child(child);
cmd_invoke_push(is, tree, child, next, NULL);
return (0);
default:
break;
}
}
return (0);
}
/* Report an error from an invoke item. */
static void
cmd_invoke_error(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node, const char *cause)
{
struct client *c = cmdq_get_client(item);
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
const char *file = cmd_parse_file(tree);
u_int line = cmd_parse_node_line(node);
if (c != NULL && (~c->flags & CLIENT_CONTROL || file == NULL)) {
cmdq_error(item, "%s", cause);
return;
}
if (file != NULL)
cfg_add_cause("%s:%u: %s", file, line, cause);
else
cfg_add_cause("%s", cause);
}
/* Build one command from a parsed command node. */
static struct cmd *
cmd_invoke_build_command(struct cmdq_item *item, struct cmd_invoke_state *is,
struct cmd_parse_node *node)
{
struct cmd_parse_tree *tree = cmd_invoke_tree(is);
struct cmd_parse_node *child;
struct args_value *values = NULL;
struct cmd *cmd;
const char *file = cmd_parse_file(tree);
u_int line = cmd_parse_node_line(node);
int flags = cmd_parse_flags(tree);
char *cause = NULL;
u_int count = 0;
child = cmd_parse_node_first_child(node);
while (child != NULL) {
values = xreallocarray(values, count + 1, sizeof *values);
memset(&values[count], 0, sizeof values[count]);
switch (cmd_parse_node_type(child)) {
case CMD_PARSE_STRING:
values[count].type = ARGS_STRING;
if (cmd_invoke_expand_string(item, is, child,
&values[count].string) != 0)
goto fail;
break;
case CMD_PARSE_COMMANDS:
values[count].type = ARGS_COMMANDS;
values[count].cmd = cmd_parse_from_node(tree, child);
break;
default:
fatalx("unexpected node type in command");
}
count++;
child = cmd_parse_node_next(child);
}
cmd = cmd_parse(values, count, file, line, flags, &cause);
if (cmd == NULL) {
cmd_invoke_error(item, is, node, cause);
free(cause);
goto fail;
}
args_free_values(values, count);
free(values);
return (cmd);
fail:
args_free_values(values, count);
free(values);
return (NULL);
}
static int
cmd_invoke_read_only(struct cmdq_item *item, struct cmd *cmd)
{
struct client *c = cmdq_get_client(item);
const struct cmd_entry *entry = cmd_get_entry(cmd);
if (c == NULL || (~c->flags & CLIENT_READONLY))
return (0);
if (entry->flags & CMD_READONLY)
return (0);
cmdq_error(item, "client is read-only");
return (-1);
}
/* Create the first invoke queue item for a parsed tree. */
struct cmdq_item *
cmd_invoke_get(struct cmd_parse_tree *tree, struct cmdq_state *state,
const struct cmd_invoke_input *input)
{
struct cmd_invoke_state *is;
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
struct cmdq_item *item;
struct cmd_invoke_input new_input = { 0 };
int i;
if (input == NULL)
input = &new_input;
is = xcalloc(1, sizeof *is);
is->references = 1;
is->tree = cmd_parse_add_ref(tree);
is->argc = input->argc;
if (input->argc != 0) {
is->argv = xreallocarray(NULL, input->argc, sizeof *is->argv);
for (i = 0; i < input->argc; i++)
is->argv[i] = xstrdup(input->argv[i]);
}
first = cmd_parse_node_first_child(root);
cmd_invoke_push(is, tree, root, first, NULL);
item = cmdq_get_invoke(is, state);
cmd_invoke_state_free(is);
return (item);
}
/* Add a reference to shared invoke state. */
struct cmd_invoke_state *
cmd_invoke_state_add_ref(struct cmd_invoke_state *is)
{
is->references++;
return (is);
}
/* Release a reference to shared invoke state. */
void
cmd_invoke_state_free(struct cmd_invoke_state *is)
{
int i;
u_int j;
if (is == NULL)
return;
if (--is->references != 0)
return;
for (i = 0; i < is->argc; i++)
free(is->argv[i]);
free(is->argv);
for (j = 0; j < is->nstack; j++)
cmd_parse_free(is->stack[j].tree);
cmd_parse_free(is->tree);
free(is->stack);
free(is);
}
/* Record the result from the last command item. */
void
cmd_invoke_result(struct cmd_invoke_state *is, enum cmd_retval retval)
{
is->last = retval;
is->have_last = 1;
}
/* Fire an invoke item and queue the next command plus continuation. */
enum cmd_retval
cmd_invoke_fire(struct cmdq_item *item, struct cmd_invoke_state *is)
{
struct cmd_parse_node *node, *first;
struct cmd_parse_tree *tree, *alias;
struct cmdq_item *new_item, *next;
struct cmdq_state *state;
struct client *c;
struct cmd *cmd;
const char *file;
char *cause;
int r;
if (is->have_last && is->last == CMD_RETURN_ERROR)
cmd_invoke_skip_sequence(is);
else
is->have_last = 0;
for (;;) {
node = cmd_invoke_next(is);
if (node == NULL)
return (CMD_RETURN_NORMAL);
cmd_parse_log_node(__func__, node);
tree = cmd_invoke_tree(is);
switch (cmd_parse_node_type(node)) {
case CMD_PARSE_ROOT:
case CMD_PARSE_SEQUENCE:
first = cmd_parse_node_first_child(node);
cmd_invoke_push(is, tree, node, first, NULL);
break;
case CMD_PARSE_ASSIGN:
case CMD_PARSE_HIDDEN_ASSIGN:
if (cmd_invoke_assignment(item, is, node) == 0)
break;
cmd_invoke_error(item, is, node, "bad assignment");
cmd_invoke_skip_sequence(is);
break;
case CMD_PARSE_IF:
if (cmd_invoke_if(item, is, node) == 0)
break;
cmd_invoke_error(item, is, node, "bad condition");
cmd_invoke_skip_sequence(is);
break;
case CMD_PARSE_ELIF:
case CMD_PARSE_ELSE:
break;
case CMD_PARSE_COMMAND:
r = cmd_parse_expand_alias(tree, node, &alias, &cause);
if (r == 1) {
node = cmd_parse_root(alias);
first = cmd_parse_node_first_child(node);
cmd_invoke_push(is, alias, node, first, NULL);
cmd_parse_free(alias);
break;
}
if (r == -1) {
c = cmdq_get_client(item);
file = cmd_parse_file(tree);
if (c != NULL &&
(~c->flags & CLIENT_CONTROL || file == NULL))
cmdq_error(item, "%s", cause);
else
cfg_add_cause("%s", cause);
free(cause);
cmd_invoke_skip_sequence(is);
break;
}
cmd = cmd_invoke_build_command(item, is, node);
if (cmd == NULL) {
cmd_invoke_skip_sequence(is);
break;
}
if (cmd_invoke_read_only(item, cmd) != 0) {
cmd_free(cmd);
cmd_invoke_skip_sequence(is);
break;
}
/*
* Queue one command followed by this walker. WAIT and
* command-inserted items therefore run before resume.
*/
state = cmdq_get_state(item);
new_item = cmdq_get_one_command(cmd, state, is);
next = cmdq_get_invoke(is, state);
cmdq_insert_after(item, next);
cmdq_insert_after(item, new_item);
return (CMD_RETURN_NORMAL);
case CMD_PARSE_STRING:
case CMD_PARSE_COMMANDS:
case CMD_PARSE_TEXT:
case CMD_PARSE_ENVIRONMENT:
case CMD_PARSE_TILDE:
fatalx("unexpected node type");
}
}
}
+28 -14
View File
@@ -37,6 +37,16 @@
"-T #{p|#{key_table_width}:key_table} " \
"#{p|#{key_string_width}:#{q|a:key_string}} " \
"#{key_command}}"
#define LIST_KEYS_TEMPLATE_PRETTY \
"#{?notes_only," \
"#{key_prefix} " \
"#{key_string} " \
"#{?key_note,#{key_note},#{key_command}}" \
"," \
"bind-key #{?key_repeat,-r ,}" \
"-T #{key_table} " \
"#{q|a:key_string} " \
"#{key_command}}"
static enum cmd_retval cmd_list_keys_exec(struct cmd *, struct cmdq_item *);
@@ -44,8 +54,8 @@ const struct cmd_entry cmd_list_keys_entry = {
.name = "list-keys",
.alias = "lsk",
.args = { "1aF:NO:P:rT:", 0, 1, NULL },
.usage = "[-1aNr] [-F format] [-O order] [-P prefix-string]"
.args = { "1aF:NO:P:prT:", 0, 1, NULL },
.usage = "[-1aNpr] [-F format] [-O order] [-P prefix-string]"
"[-T key-table] [key]",
.flags = CMD_STARTSERVER|CMD_AFTERHOOK,
@@ -119,7 +129,7 @@ cmd_list_keys_get_root_and_prefix(u_int *n, struct sort_criteria *sort_crit)
}
static void
cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
cmd_list_keys_do_filter(int filter_notes, int filter_key, key_code only,
struct key_binding **l, u_int *n)
{
key_code key;
@@ -137,8 +147,8 @@ cmd_list_keys_filter_key_list(int filter_notes, int filter_key, key_code only,
}
static void
cmd_list_keys_format_add_key_binding(struct format_tree *ft,
const struct key_binding *bd, const char *prefix)
cmd_list_keys_format_key_binding(struct format_tree *ft,
const struct key_binding *bd, const char *prefix, int flags)
{
char *s;
@@ -157,8 +167,7 @@ cmd_list_keys_format_add_key_binding(struct format_tree *ft,
format_add(ft, "key_string", "%s", key_string_lookup_key(bd->key, 0));
s = cmd_list_print(bd->cmdlist, CMD_LIST_PRINT_ESCAPED|
CMD_LIST_PRINT_NO_GROUPS);
s = cmd_parse_print(bd->cmd, flags);
format_add(ft, "key_command", "%s", s);
free(s);
}
@@ -177,6 +186,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
char *prefix = NULL;
u_int i, n;
int single, notes_only, filter_notes, filter_key;
int print_flags = 0;
struct sort_criteria sort_crit;
if ((keystr = args_string(args, 0)) != NULL) {
@@ -206,9 +216,15 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
prefix = cmd_list_keys_get_prefix(args);
single = args_has(args, '1');
notes_only = args_has(args, 'N');
if (args_has(args, 'p'))
print_flags |= CMD_PARSE_PRINT_MULTILINE;
if ((template = args_get(args, 'F')) == NULL)
template = LIST_KEYS_TEMPLATE;
if ((template = args_get(args, 'F')) == NULL) {
if (print_flags & CMD_PARSE_PRINT_MULTILINE)
template = LIST_KEYS_TEMPLATE_PRETTY;
else
template = LIST_KEYS_TEMPLATE;
}
if (table)
l = sort_get_key_bindings_table(table, &n, &sort_crit);
@@ -219,10 +235,8 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
filter_notes = notes_only && !args_has(args, 'a');
filter_key = only != KEYC_UNKNOWN;
if (filter_notes || filter_key) {
cmd_list_keys_filter_key_list(filter_notes, filter_key, only, l,
&n);
}
if (filter_notes || filter_key)
cmd_list_keys_do_filter(filter_notes, filter_key, only, l, &n);
if (filter_key && n == 0) {
cmdq_error(item, "unknown key: %s", keystr);
free(prefix);
@@ -239,7 +253,7 @@ cmd_list_keys_exec(struct cmd *self, struct cmdq_item *item)
format_add(ft, "key_table_width", "%u",
cmd_list_keys_get_table_width(l, n));
for (i = 0; i < n; i++) {
cmd_list_keys_format_add_key_binding(ft, l[i], prefix);
cmd_list_keys_format_key_binding(ft, l[i], prefix, print_flags);
line = format_expand(ft, template);
if (single && tc != NULL && (~tc->flags & CLIENT_CONTROL))
+1627 -1214
View File
File diff suppressed because it is too large Load Diff
+43 -72
View File
@@ -35,6 +35,7 @@
enum cmdq_type {
CMDQ_COMMAND,
CMDQ_CALLBACK,
CMDQ_INVOKE,
};
/* Command queue item. */
@@ -47,7 +48,6 @@ struct cmdq_item {
struct client *target_client;
enum cmdq_type type;
u_int group;
u_int number;
time_t time;
@@ -58,8 +58,8 @@ struct cmdq_item {
struct cmd_find_state source;
struct cmd_find_state target;
struct cmd_list *cmdlist;
struct cmd *cmd;
struct cmd_invoke_state *invoke_state;
cmdq_cb cb;
void *data;
@@ -428,8 +428,10 @@ cmdq_remove(struct cmdq_item *item)
{
if (item->client != NULL)
server_client_unref(item->client);
if (item->cmdlist != NULL)
cmd_list_free(item->cmdlist);
if (item->cmd != NULL)
cmd_free(item->cmd);
if (item->invoke_state != NULL)
cmd_invoke_state_free(item->invoke_state);
cmdq_free_state(item->state);
TAILQ_REMOVE(&item->queue->list, item, entry);
@@ -438,75 +440,46 @@ cmdq_remove(struct cmdq_item *item)
free(item);
}
/* Remove all subsequent items that match this item's group. */
static void
cmdq_remove_group(struct cmdq_item *item)
{
struct cmdq_item *this, *next;
if (item->group == 0)
return;
this = TAILQ_NEXT(item, entry);
while (this != NULL) {
next = TAILQ_NEXT(this, entry);
if (this->group == item->group)
cmdq_remove(this);
this = next;
}
}
/* Empty command callback. */
static enum cmd_retval
cmdq_empty_command(__unused struct cmdq_item *item, __unused void *data)
{
return (CMD_RETURN_NORMAL);
}
/* Get a command for the command queue. */
/* Get a single command for command queue with invoke state. */
struct cmdq_item *
cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
cmdq_get_one_command(struct cmd *cmd, struct cmdq_state *state,
struct cmd_invoke_state *invoke_state)
{
struct cmdq_item *item, *first = NULL, *last = NULL;
struct cmd *cmd;
const struct cmd_entry *entry;
int created = 0;
struct cmdq_item *item;
const struct cmd_entry *entry = cmd_get_entry(cmd);
if ((cmd = cmd_list_first(cmdlist)) == NULL)
return (cmdq_get_callback(cmdq_empty_command, NULL));
item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[%s/%p]", entry->name, item);
item->type = CMDQ_COMMAND;
item->cmd = cmd;
if (state == NULL) {
state = cmdq_new_state(NULL, NULL, 0);
created = 1;
}
while (cmd != NULL) {
entry = cmd_get_entry(cmd);
item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[%s/%p]", entry->name, item);
item->type = CMDQ_COMMAND;
item->group = cmd_get_group(cmd);
if (state == NULL)
item->state = cmdq_new_state(NULL, NULL, 0);
else
item->state = cmdq_link_state(state);
item->cmdlist = cmdlist;
item->cmd = cmd;
if (invoke_state != NULL)
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
cmdlist->references++;
log_debug("%s: %s group %u", __func__, item->name, item->group);
log_debug("%s: %s", __func__, item->name);
return (item);
}
if (first == NULL)
first = item;
if (last != NULL)
last->next = item;
last = item;
/* Get an invoke continuation for command queue. */
struct cmdq_item *
cmdq_get_invoke(struct cmd_invoke_state *invoke_state, struct cmdq_state *state)
{
struct cmdq_item *item;
cmd = cmd_list_next(cmd);
}
if (created)
cmdq_free_state(state);
return (first);
item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[invoke/%p]", item);
item->type = CMDQ_INVOKE;
if (state == NULL)
item->state = cmdq_new_state(NULL, NULL, 0);
else
item->state = cmdq_link_state(state);
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
return (item);
}
/* Fill in flag for a command. */
@@ -584,7 +557,7 @@ cmdq_fire_command(struct cmdq_item *item)
cmdq_add_message(item);
if (log_get_level() > 1) {
tmp = cmd_print(cmd);
log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp);
log_debug("%s %s: %s", __func__, name, tmp);
free(tmp);
}
@@ -663,7 +636,6 @@ cmdq_get_callback1(const char *name, cmdq_cb cb, void *data)
xasprintf(&item->name, "[%s/%p]", name, item);
item->type = CMDQ_CALLBACK;
item->group = 0;
item->state = cmdq_new_state(NULL, NULL, 0);
item->cb = cb;
@@ -745,17 +717,16 @@ cmdq_next(struct client *c)
switch (item->type) {
case CMDQ_COMMAND:
retval = cmdq_fire_command(item);
if (item->invoke_state != NULL)
cmd_invoke_result(item->invoke_state, retval);
/*
* If a command returns an error, remove any
* subsequent commands in the same group.
*/
if (retval == CMD_RETURN_ERROR)
cmdq_remove_group(item);
break;
case CMDQ_CALLBACK:
retval = cmdq_fire_callback(item);
break;
case CMDQ_INVOKE:
retval = cmd_invoke_fire(item, item->invoke_state);
break;
default:
retval = CMD_RETURN_ERROR;
break;
+37 -39
View File
@@ -55,15 +55,15 @@ const struct cmd_entry cmd_run_shell_entry = {
};
struct cmd_run_shell_data {
struct client *client;
char *cmd;
struct args_command_state *state;
char *cwd;
struct cmdq_item *item;
struct session *s;
int wp_id;
struct event timer;
int flags;
struct client *client;
char *cmd;
struct cmd_parse_tree *tree;
char *cwd;
struct cmdq_item *item;
struct session *s;
int wp_id;
struct event timer;
int flags;
};
static enum args_parse_type
@@ -119,7 +119,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
struct format_tree *ft;
double d;
struct timeval tv;
char *end, key[16];
char *end, key[16], *cause = NULL;
u_int i;
int wait = !args_has(args, 'b');
@@ -129,8 +129,10 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
cmdq_error(item, "invalid delay time: %s", delay);
return (CMD_RETURN_ERROR);
}
} else if (args_count(args) == 0)
return (CMD_RETURN_NORMAL);
} else {
if (args_count(args) == 0)
return (CMD_RETURN_NORMAL);
}
cdata = xcalloc(1, sizeof *cdata);
if (!args_has(args, 'C')) {
@@ -145,8 +147,13 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
format_free(ft);
}
} else {
cdata->state = args_make_commands_prepare(self, item, 0, NULL,
wait, 1);
cdata->tree = args_to_commands(self, item, 0, NULL, 1, &cause);
if (cdata->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
cmd_run_shell_free(cdata);
return (CMD_RETURN_ERROR);
}
}
if (args_has(args, 't') && wp != NULL)
@@ -163,6 +170,7 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
}
if (cdata->client != NULL)
cdata->client->references++;
if (args_has(args, 'c'))
cdata->cwd = xstrdup(args_get(args, 'c'));
else
@@ -171,18 +179,20 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (args_has(args, 'E'))
cdata->flags |= JOB_SHOWSTDERR;
cdata->s = s;
if (s != NULL)
if (s != NULL) {
cdata->s = s;
session_add_ref(s, __func__);
}
evtimer_set(&cdata->timer, cmd_run_shell_timer, cdata);
if (delay != NULL) {
if (delay == NULL)
event_active(&cdata->timer, EV_TIMEOUT, 1);
else {
timerclear(&tv);
tv.tv_sec = (time_t)d;
tv.tv_usec = (d - (double)tv.tv_sec) * 1000000U;
evtimer_add(&cdata->timer, &tv);
} else
event_active(&cdata->timer, EV_TIMEOUT, 1);
}
if (!wait)
return (CMD_RETURN_NORMAL);
@@ -196,10 +206,9 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
struct client *c = cdata->client;
const char *cmd = cdata->cmd;
struct cmdq_item *item = cdata->item, *new_item;
struct cmd_list *cmdlist;
char *error;
struct cmdq_state *cs = NULL;
if (cdata->state == NULL) {
if (cdata->tree == NULL) {
if (cmd == NULL) {
if (cdata->item != NULL)
cmdq_continue(cdata->item);
@@ -222,23 +231,13 @@ cmd_run_shell_timer(__unused int fd, __unused short events, void* arg)
return;
}
cmdlist = args_make_commands(cdata->state, 0, NULL, &error);
if (cmdlist == NULL) {
if (cdata->item == NULL) {
*error = toupper((u_char)*error);
status_message_set(c, -1, 1, 0, 0, "%s", error);
} else
cmdq_error(cdata->item, "%s", error);
free(error);
} else if (item == NULL) {
new_item = cmdq_get_command(cmdlist, NULL);
if (item != NULL)
cs = cmdq_get_state(item);
new_item = cmd_invoke_get(cdata->tree, cs, NULL);
if (item == NULL)
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
} else {
new_item = cmdq_get_command(cmdlist, cmdq_get_state(item));
else
cmdq_insert_after(item, new_item);
cmd_list_free(cmdlist);
}
if (cdata->item != NULL)
cmdq_continue(cdata->item);
@@ -306,8 +305,7 @@ cmd_run_shell_free(void *data)
session_remove_ref(cdata->s, __func__);
if (cdata->client != NULL)
server_client_unref(cdata->client);
if (cdata->state != NULL)
args_make_commands_free(cdata->state);
cmd_parse_free(cdata->tree);
free(cdata->cwd);
free(cdata->cmd);
free(cdata);
+1 -1
View File
@@ -131,7 +131,7 @@ cmd_source_file_done(__unused struct client *oc, const char *path,
if (error != 0)
cmdq_error(item, "%s: %s", strerror(error), path);
else if (bsize != 0) {
if (load_cfg_from_buffer(bdata, bsize, path, c, cdata->after,
if (load_cfg_from_buffer(bdata, bsize, path, cdata->after,
target, cdata->flags, &new_item) < 0)
cdata->retval = CMD_RETURN_ERROR;
else if (new_item != NULL)
-199
View File
@@ -220,18 +220,11 @@ const struct cmd_entry *cmd_table[] = {
struct cmd {
const struct cmd_entry *entry;
struct args *args;
u_int group;
char *file;
u_int line;
int parse_flags;
TAILQ_ENTRY(cmd) qentry;
};
TAILQ_HEAD(cmds, cmd);
/* Next group number for new command list. */
static u_int cmd_list_next_group = 1;
/* Log an argument vector. */
void printflike(3, 4)
@@ -402,13 +395,6 @@ cmd_get_args(struct cmd *cmd)
return (cmd->args);
}
/* Get group for command. */
u_int
cmd_get_group(struct cmd *cmd)
{
return (cmd->group);
}
/* Get file and line for command. */
void
cmd_get_source(struct cmd *cmd, const char **file, u_int *line)
@@ -558,23 +544,6 @@ 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)
@@ -591,174 +560,6 @@ cmd_print(struct cmd *cmd)
return (out);
}
/* Create a new command list. */
struct cmd_list *
cmd_list_new(void)
{
struct cmd_list *cmdlist;
cmdlist = xcalloc(1, sizeof *cmdlist);
cmdlist->references = 1;
cmdlist->group = cmd_list_next_group++;
cmdlist->list = xcalloc(1, sizeof *cmdlist->list);
TAILQ_INIT(cmdlist->list);
return (cmdlist);
}
/* Append a command to a command list. */
void
cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
{
cmd->group = cmdlist->group;
TAILQ_INSERT_TAIL(cmdlist->list, cmd, qentry);
}
/* Append all commands from one list to another. */
void
cmd_list_append_all(struct cmd_list *cmdlist, struct cmd_list *from)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, from->list, qentry)
cmd->group = cmdlist->group;
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
}
/* Move all commands from one command list to another. */
void
cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
{
TAILQ_CONCAT(cmdlist->list, from->list, qentry);
cmdlist->group = cmd_list_next_group++;
}
/* Free a command list. */
void
cmd_list_free(struct cmd_list *cmdlist)
{
struct cmd *cmd, *cmd1;
if (--cmdlist->references != 0)
return;
TAILQ_FOREACH_SAFE(cmd, cmdlist->list, qentry, cmd1) {
TAILQ_REMOVE(cmdlist->list, cmd, qentry);
cmd_free(cmd);
}
free(cmdlist->list);
free(cmdlist);
}
/* Copy a command list, expanding %s in arguments. */
struct cmd_list *
cmd_list_copy(const 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(const struct cmd_list *cmdlist, int flags)
{
struct cmd *cmd, *next;
char *buf, *this;
size_t len;
const char *separator;
int escaped = flags & CMD_LIST_PRINT_ESCAPED;
int no_groups = flags & CMD_LIST_PRINT_NO_GROUPS;
const char *single_separator = escaped ? " \\; " : " ; ";
const char *double_separator = escaped ? " \\;\\; " : " ;; ";
len = 1;
buf = xcalloc(1, len);
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
this = cmd_print(cmd);
len += strlen(this) + 6;
buf = xrealloc(buf, len);
strlcat(buf, this, len);
next = TAILQ_NEXT(cmd, qentry);
if (next != NULL) {
if (!no_groups && cmd->group != next->group)
separator = double_separator;
else
separator = single_separator;
strlcat(buf, separator, len);
}
free(this);
}
return (buf);
}
/* Get first command in list. */
struct cmd *
cmd_list_first(struct cmd_list *cmdlist)
{
return (TAILQ_FIRST(cmdlist->list));
}
/* Get next command in list. */
struct cmd *
cmd_list_next(struct cmd *cmd)
{
return (TAILQ_NEXT(cmd, qentry));
}
/* Do all of the commands in this command list have this flag? */
int
cmd_list_all_have(struct cmd_list *cmdlist, int flag)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
if (~cmd->entry->flags & flag)
return (0);
}
return (1);
}
/* Do any of the commands in this command list have this flag? */
int
cmd_list_any_have(struct cmd_list *cmdlist, int flag)
{
struct cmd *cmd;
TAILQ_FOREACH(cmd, cmdlist->list, qentry) {
if (cmd->entry->flags & flag)
return (1);
}
return (0);
}
/* Adjust current mouse position for a pane. */
int
cmd_mouse_at(struct window_pane *wp, struct mouse_event *m, u_int *xp,
+9 -3
View File
@@ -641,8 +641,9 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
struct control_state *cs = c->control_state;
struct evbuffer *buffer = cs->read_event->input;
char *line, *error;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
struct cmdq_state *state;
enum cmd_parse_status status;
for (;;) {
line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
@@ -656,9 +657,14 @@ control_read_callback(__unused struct bufferevent *bufev, void *data)
}
state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
status = cmd_parse_and_append(line, NULL, c, state, &error);
if (status == CMD_PARSE_ERROR)
tree = cmd_parse_from_string(line, NULL, &error);
if (tree == NULL)
cmdq_append(c, cmdq_get_callback(control_error, error));
else {
new_item = cmd_invoke_get(tree, state, NULL);
cmdq_append(c, new_item);
cmd_parse_free(tree);
}
cmdq_free_state(state);
free(line);
+6 -12
View File
@@ -36,7 +36,8 @@ int
LLVMFuzzerTestOneInput(const u_char *data, size_t size)
{
struct cmd_parse_input pi;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
char *cause = NULL;
if (size > 2048 || size == 0)
return 0;
@@ -44,17 +45,10 @@ LLVMFuzzerTestOneInput(const u_char *data, size_t size)
memset(&pi, 0, sizeof pi);
pi.flags = CMD_PARSE_QUIET;
pr = cmd_parse_from_buffer(data, size, &pi);
switch (pr->status) {
case CMD_PARSE_SUCCESS:
cmd_list_free(pr->cmdlist);
break;
case CMD_PARSE_ERROR:
free(pr->error);
break;
default:
break;
}
tree = cmd_parse_from_buffer(data, size, &pi, &cause);
if (tree != NULL)
cmd_parse_free(tree);
free(cause);
return 0;
}
+32 -35
View File
@@ -59,35 +59,35 @@ static struct hooks_events hooks_events = TAILQ_HEAD_INITIALIZER(hooks_events);
/* Insert one hook command list. */
static struct cmdq_item *
hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd,
struct cmd_list *cmdlist, struct cmdq_state *state)
struct cmd_parse_tree *tree, struct cmdq_state *state)
{
struct cmdq_item *new_item;
char *s;
if (cmdlist == NULL)
if (tree == NULL)
return (item);
if (log_get_level() != 0) {
s = cmd_list_print(cmdlist, 0);
s = cmd_parse_print(tree, 0);
log_debug("%s: hook %s is: %s", __func__, hd->name, s);
free(s);
}
new_item = cmdq_get_command(cmdlist, state);
new_item = cmd_invoke_get(tree, state, NULL);
if (item != NULL)
return (cmdq_insert_after(item, new_item));
return (cmdq_append(NULL, new_item));
}
/* Parse a hook command. */
static struct cmd_parse_result *
static struct cmd_parse_tree *
hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
const char *value)
const char *value, char **cause)
{
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
struct format_tree *ft;
char *expanded;
if (!hd->expand)
return (cmd_parse_from_string(value, NULL));
return (cmd_parse_from_string(value, NULL, cause));
ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp);
if (hd->formats != NULL)
@@ -95,9 +95,9 @@ hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs,
expanded = format_expand(ft, value);
format_free(ft);
pr = cmd_parse_from_string(expanded, NULL);
tree = cmd_parse_from_string(expanded, NULL, cause);
free(expanded);
return (pr);
return (tree);
}
/* Insert commands for a hook. */
@@ -109,9 +109,9 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
struct cmdq_state *state;
struct options_entry *o;
struct options_array_item *a;
struct cmd_list *cmdlist;
struct cmd_parse_tree *tree;
const char *value;
struct cmd_parse_result *pr;
char *cause = NULL;
log_debug("%s: inserting hook %s", __func__, hd->name);
@@ -155,38 +155,35 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd)
if (*hd->name == '@') {
value = options_get_string(oo, hd->name);
pr = hooks_parse(hd, &fs, value);
switch (pr->status) {
case CMD_PARSE_ERROR:
cause = NULL;
tree = hooks_parse(hd, &fs, value, &cause);
if (tree == NULL) {
log_debug("%s: can't parse hook %s: %s", __func__,
hd->name, pr->error);
free(pr->error);
break;
case CMD_PARSE_SUCCESS:
hooks_insert_one(item, hd, pr->cmdlist, state);
break;
hd->name, cause);
free(cause);
} else {
hooks_insert_one(item, hd, tree, state);
cmd_parse_free(tree);
}
} else {
a = options_array_first(o);
while (a != NULL) {
if (hd->expand) {
value = options_array_item_value(a)->string;
pr = hooks_parse(hd, &fs, value);
switch (pr->status) {
case CMD_PARSE_ERROR:
if (pr->error != NULL) {
cmdq_error(item, "%s",
pr->error);
}
break;
case CMD_PARSE_SUCCESS:
item = hooks_insert_one(item, hd,
pr->cmdlist, state);
break;
cause = NULL;
tree = hooks_parse(hd, &fs, value, &cause);
if (tree == NULL) {
if (cause != NULL)
cmdq_error(item, "%s", cause);
free(cause);
} else {
item = hooks_insert_one(item, hd, tree,
state);
cmd_parse_free(tree);
}
} else {
cmdlist = options_array_item_value(a)->cmdlist;
item = hooks_insert_one(item, hd, cmdlist,
tree = options_array_item_value(a)->cmd;
item = hooks_insert_one(item, hd, tree,
state);
}
a = options_array_next(a);
+32 -45
View File
@@ -130,7 +130,7 @@ key_bindings_cmp(struct key_binding *bd1, struct key_binding *bd2)
static void
key_bindings_free(struct key_binding *bd)
{
cmd_list_free(bd->cmdlist);
cmd_parse_free(bd->cmd);
free((void *)bd->note);
free(bd);
}
@@ -222,16 +222,15 @@ key_bindings_next(__unused struct key_table *table, struct key_binding *bd)
void
key_bindings_add(const char *name, key_code key, const char *note, int repeat,
struct cmd_list *cmdlist)
struct cmd_parse_tree *cmd)
{
struct key_table *table;
struct key_table *table = key_bindings_get_table(name, 1);
struct key_binding *bd;
const char *k;
char *s;
table = key_bindings_get_table(name, 1);
bd = key_bindings_get(table, key & ~KEYC_MASK_FLAGS);
if (cmdlist == NULL) {
if (cmd == NULL) {
if (bd != NULL) {
if (note != NULL) {
free((void *)bd->note);
@@ -256,11 +255,11 @@ key_bindings_add(const char *name, key_code key, const char *note, int repeat,
if (repeat)
bd->flags |= KEY_BINDING_REPEAT;
bd->cmdlist = cmdlist;
bd->cmd = cmd;
s = cmd_list_print(bd->cmdlist, 0);
log_debug("%s: %#llx %s = %s", __func__, bd->key,
key_string_lookup_key(bd->key, 1), s);
k = key_string_lookup_key(bd->key, 1);
s = cmd_parse_print(bd->cmd, 0);
log_debug("%s: %#llx %s = %s", __func__, bd->key, k, s);
free(s);
}
@@ -269,6 +268,7 @@ key_bindings_remove(const char *name, key_code key)
{
struct key_table *table;
struct key_binding *bd;
const char *k;
table = key_bindings_get_table(name, 0);
if (table == NULL)
@@ -278,8 +278,8 @@ key_bindings_remove(const char *name, key_code key)
if (bd == NULL)
return;
log_debug("%s: %#llx %s", __func__, bd->key,
key_string_lookup_key(bd->key, 1));
k = key_string_lookup_key(bd->key, 1);
log_debug("%s: %#llx %s", __func__, bd->key, k);
RB_REMOVE(key_bindings, &table->key_bindings, bd);
key_bindings_free(bd);
@@ -311,9 +311,8 @@ key_bindings_reset(const char *name, key_code key)
return;
}
cmd_list_free(bd->cmdlist);
bd->cmdlist = dd->cmdlist;
bd->cmdlist->references++;
cmd_parse_free(bd->cmd);
bd->cmd = cmd_parse_add_ref(dd->cmd);
free((void *)bd->note);
if (dd->note != NULL)
@@ -370,8 +369,7 @@ key_bindings_init_done(__unused struct cmdq_item *item, __unused void *data)
if (bd->note != NULL)
new_bd->note = xstrdup(bd->note);
new_bd->flags = bd->flags;
new_bd->cmdlist = bd->cmdlist;
new_bd->cmdlist->references++;
new_bd->cmd = cmd_parse_add_ref(bd->cmd);
RB_INSERT(key_bindings, &table->default_key_bindings,
new_bd);
}
@@ -733,50 +731,39 @@ key_bindings_init(void)
"bind -Tcopy-mode-vi C-Up { send -X scroll-up }",
"bind -Tcopy-mode-vi C-Down { send -X scroll-down }",
};
u_int i;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
char *cause = NULL;
for (i = 0; i < nitems(defaults); i++) {
pr = cmd_parse_from_string(defaults[i], NULL);
if (pr->status != CMD_PARSE_SUCCESS) {
log_debug("%s", pr->error);
tree = cmd_parse_from_string(defaults[i], NULL, &cause);
if (tree == NULL)
fatalx("bad default key: %s", defaults[i]);
}
cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL));
cmd_list_free(pr->cmdlist);
new_item = cmd_invoke_get(tree, NULL, NULL);
cmdq_append(NULL, new_item);
cmd_parse_free(tree);
}
cmdq_append(NULL, cmdq_get_callback(key_bindings_init_done, NULL));
}
static enum cmd_retval
key_bindings_read_only(struct cmdq_item *item, __unused void *data)
{
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
struct cmdq_item *
key_bindings_dispatch(struct key_binding *bd, struct cmdq_item *item,
struct client *c, struct key_event *event, struct cmd_find_state *fs)
{
struct cmdq_item *new_item;
struct cmdq_state *new_state;
int readonly, flags = 0;
int flags = 0;
if (c == NULL || (~c->flags & CLIENT_READONLY))
readonly = 1;
else
readonly = cmd_list_all_have(bd->cmdlist, CMD_READONLY);
if (bd->flags & KEY_BINDING_REPEAT)
flags |= CMDQ_STATE_REPEAT;
new_state = cmdq_new_state(fs, event, flags);
new_item = cmd_invoke_get(bd->cmd, new_state, NULL);
cmdq_free_state(new_state);
if (!readonly)
new_item = cmdq_get_callback(key_bindings_read_only, NULL);
else {
if (bd->flags & KEY_BINDING_REPEAT)
flags |= CMDQ_STATE_REPEAT;
new_state = cmdq_new_state(fs, event, flags);
new_item = cmdq_get_command(bd->cmdlist, new_state);
cmdq_free_state(new_state);
}
if (item != NULL)
new_item = cmdq_insert_after(item, new_item);
else
+8 -4
View File
@@ -333,9 +333,10 @@ menu_key(struct client *c, struct menu_data *md, struct key_event *event)
int move;
const char *name = NULL;
const struct menu_item *item;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
struct key_event saved_event;
struct cmdq_state *state;
enum cmd_parse_status status;
char *error;
key_code key;
@@ -528,11 +529,14 @@ chosen:
} else
event = NULL;
state = cmdq_new_state(&md->fs, event, 0);
status = cmd_parse_and_append(item->command, NULL, c, state, &error);
if (status == CMD_PARSE_ERROR) {
tree = cmd_parse_from_string(item->command, NULL, &error);
if (tree == NULL) {
cmdq_append(c, cmdq_get_error(error));
free(error);
} else {
new_item = cmd_invoke_get(tree, state, NULL);
cmdq_append(c, new_item);
cmd_parse_free(tree);
}
cmdq_free_state(state);
+8 -3
View File
@@ -1815,20 +1815,25 @@ void
mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
const char *template, const char *name)
{
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
struct cmdq_state *state;
char *command, *error;
enum cmd_parse_status status;
command = cmd_template_replace(template, name, 1);
if (command != NULL && *command != '\0') {
state = cmdq_new_state(fs, NULL, 0);
status = cmd_parse_and_append(command, NULL, c, state, &error);
if (status == CMD_PARSE_ERROR) {
tree = cmd_parse_from_string(command, NULL, &error);
if (tree == NULL) {
if (c != NULL) {
*error = toupper((u_char)*error);
status_message_set(c, -1, 1, 0, 0, "%s", error);
}
free(error);
} else {
new_item = cmd_invoke_get(tree, state, NULL);
cmdq_append(c, new_item);
cmd_parse_free(tree);
}
cmdq_free_state(state);
}
+28 -39
View File
@@ -180,8 +180,8 @@ options_value_free(struct options_entry *o, union options_value *ov)
{
if (OPTIONS_IS_STRING(o))
free(ov->string);
if (OPTIONS_IS_COMMAND(o) && ov->cmdlist != NULL)
cmd_list_free(ov->cmdlist);
if (OPTIONS_IS_COMMAND(o))
cmd_parse_free(ov->cmd);
}
static char *
@@ -191,7 +191,7 @@ options_value_to_string(struct options_entry *o, union options_value *ov,
char *s;
if (OPTIONS_IS_COMMAND(o))
return (cmd_list_print(ov->cmdlist, 0));
return (cmd_parse_print(ov->cmd, 0));
if (OPTIONS_IS_NUMBER(o)) {
switch (o->tableentry->type) {
case OPTIONS_TABLE_NUMBER:
@@ -315,8 +315,8 @@ options_default(struct options *oo, const struct options_table_entry *oe)
struct options_entry *o;
union options_value *ov;
char key[32];
char *error = NULL;
u_int i;
struct cmd_parse_result *pr;
o = options_empty(oo, oe);
ov = &o->value;
@@ -338,15 +338,9 @@ options_default(struct options *oo, const struct options_table_entry *oe)
ov->string = xstrdup(oe->default_str);
break;
case OPTIONS_TABLE_COMMAND:
pr = cmd_parse_from_string(oe->default_str, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
free(pr->error);
break;
case CMD_PARSE_SUCCESS:
ov->cmdlist = pr->cmdlist;
break;
}
ov->cmd = cmd_parse_from_string(oe->default_str, NULL, &error);
if (ov->cmd == NULL)
fatalx("bad default option %s", oe->name);
break;
default:
ov->number = oe->default_num;
@@ -549,8 +543,9 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
{
struct options_array_item *a;
char *new, *new_key;
struct cmd_parse_result *pr;
long long number;
struct cmd_parse_tree *tree;
char *error = NULL;
if (!OPTIONS_IS_ARRAY(o)) {
if (cause != NULL)
@@ -574,17 +569,14 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
}
if (OPTIONS_IS_COMMAND(o)) {
pr = cmd_parse_from_string(value, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
tree = cmd_parse_from_string(value, NULL, &error);
if (tree == NULL) {
if (cause != NULL)
*cause = pr->error;
*cause = error;
else
free(pr->error);
free(error);
free(new_key);
return (-1);
case CMD_PARSE_SUCCESS:
break;
}
a = options_array_item(o, new_key);
@@ -592,7 +584,7 @@ options_array_set(struct options_entry *o, const char *key, const char *value,
a = options_array_new(o, new_key);
else
options_value_free(o, &a->value);
a->value.cmdlist = pr->cmdlist;
a->value.cmd = tree;
free(new_key);
return (0);
}
@@ -912,7 +904,7 @@ options_get_number(struct options *oo, const char *name)
return (o->value.number);
}
struct cmd_list *
struct cmd_parse_tree *
options_get_command(struct options *oo, const char *name)
{
struct options_entry *o;
@@ -922,7 +914,7 @@ options_get_command(struct options *oo, const char *name)
fatalx("missing option %s", name);
if (!OPTIONS_IS_COMMAND(o))
fatalx("option %s is not a command", name);
return (o->value.cmdlist);
return (o->value.cmd);
}
struct options_entry *
@@ -988,7 +980,7 @@ options_set_number(struct options *oo, const char *name, long long value)
struct options_entry *
options_set_command(struct options *oo, const char *name,
struct cmd_list *value)
struct cmd_parse_tree *value)
{
struct options_entry *o;
@@ -1004,9 +996,8 @@ options_set_command(struct options *oo, const char *name,
if (!OPTIONS_IS_COMMAND(o))
fatalx("option %s is not a command", name);
if (o->value.cmdlist != NULL)
cmd_list_free(o->value.cmdlist);
o->value.cmdlist = value;
cmd_parse_free(o->value.cmd);
o->value.cmd = cmd_parse_add_ref(value);
return (o);
}
@@ -1282,7 +1273,8 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
const char *errstr, *new;
char *old;
key_code key;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
struct cmd_parse_input pi = { 0 };
if (oe != NULL) {
if (value == NULL &&
@@ -1340,17 +1332,14 @@ options_from_string(struct options *oo, const struct options_table_entry *oe,
return (options_from_string_flag(oo, name, value, cause));
case OPTIONS_TABLE_CHOICE:
return (options_from_string_choice(oe, oo, name, value, cause));
case OPTIONS_TABLE_COMMAND:
pr = cmd_parse_from_string(value, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
*cause = pr->error;
case OPTIONS_TABLE_COMMAND: {
tree = cmd_parse_from_string(value, &pi, cause);
if (tree == NULL)
return (-1);
case CMD_PARSE_SUCCESS:
options_set_command(oo, name, pr->cmdlist);
return (0);
}
break;
options_set_command(oo, name, tree);
cmd_parse_free(tree);
return (0);
}
}
return (-1);
}
+50
View File
@@ -0,0 +1,50 @@
#!/bin/sh
# Client argv parsing and the start-server boundary.
#
# The command line given to the client is parsed and invoked as one sequence.
# This checks: a multi-command argv runs every command and starts the server
# (new-session carries CMD_STARTSERVER); a command without start-server fails
# cleanly against no server; and a parse error leaves no stray server behind.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
# Multi-command argv: starts the server and runs all commands in order.
$TMUX new-session -d -swork -nbase \; set -g @a one \; new-window -dn W2 \; \
set -g @b two || exit 1
[ "$($TMUX show -gv @a)" = one ] || { echo "argv cmd 2 did not run" >&2; exit 1; }
[ "$($TMUX show -gv @b)" = two ] || { echo "argv cmd 4 did not run" >&2; exit 1; }
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
[ "$got" = "base,W2," ] || { echo "argv windows: got [$got]" >&2; exit 1; }
# kill-server is asynchronous; wait for the server to actually exit before the
# checks below that require no server to be running.
$TMUX kill-server 2>/dev/null
i=0
while $TMUX has-session 2>/dev/null; do
sleep 0.05
i=$((i + 1))
[ $i -gt 100 ] && break
done
# A command without start-server fails cleanly when no server is running and
# does not fork one.
$TMUX new-window -dn ZZ 2>/dev/null && { echo "new-window unexpectedly succeeded" >&2; exit 1; }
if $TMUX has-session 2>/dev/null; then
echo "non-start-server command left a stray server" >&2
exit 1
fi
# A parse error with no server running starts no server.
$TMUX 'if-shell true {' 2>/dev/null && { echo "parse error did not fail" >&2; exit 1; }
if $TMUX has-session 2>/dev/null; then
echo "parse error left a stray server" >&2
exit 1
fi
$TMUX kill-server 2>/dev/null
exit 0
+87
View File
@@ -0,0 +1,87 @@
#!/bin/sh
# command-alias expansion, against a running server and at server start.
#
# An alias replaces a command name with parsed command text; arguments after the
# alias name are appended to the last command of the expansion. This covers a
# single-command alias, a multi-command alias, argument appending, a built-in
# default alias, and aliases that are defined and used as the server starts
# (both from the startup config and from the client command line that starts
# the server).
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
CONF=$(mktemp)
trap "rm -f $CONF" 0 1 15
wait_gone() {
i=0
while $TMUX has-session 2>/dev/null; do
sleep 0.05
i=$((i + 1))
[ $i -gt 100 ] && break
done
}
# --- Against a running server. ---------------------------------------------
$TMUX -f/dev/null start \; new-session -d -swork -nbase 2>/dev/null || exit 1
# Single-command alias.
$TMUX split-window -dt work || exit 1
$TMUX set -s command-alias[200] 'zoomit=resize-pane -Z' || exit 1
$TMUX zoomit -t work:.0 || exit 1
[ "$($TMUX display-message -p -t work '#{window_zoomed_flag}')" = 1 ] || {
echo "single-command alias did not zoom" >&2; exit 1; }
# Multi-command alias: both commands run.
$TMUX set -s command-alias[201] 'twowin=new-window -dn AA ; new-window -dn BB' || exit 1
$TMUX run-shell -C 'twowin' || exit 1
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
[ "$got" = "base,AA,BB," ] || { echo "multi-command alias: got [$got]" >&2; exit 1; }
# Arguments after the alias name are appended to the expansion.
$TMUX set -s command-alias[202] 'namewin=new-window -d -n' || exit 1
$TMUX run-shell -C 'namewin CC' || exit 1
$TMUX list-windows -t work -F '#{window_name}' | grep -qx CC || {
echo "alias argument append did not create window CC" >&2; exit 1; }
# A built-in default alias (splitp -> split-window).
before=$($TMUX list-panes -t work | wc -l)
$TMUX run-shell -C 'splitp -d -t work' || exit 1
after=$($TMUX list-panes -t work | wc -l)
[ "$after" -gt "$before" ] || { echo "built-in alias splitp did not split" >&2; exit 1; }
$TMUX kill-server 2>/dev/null
wait_gone
# --- At server start: alias defined and used in the startup config. --------
cat <<'EOF' >$CONF
set -s command-alias[100] greet='set -g @g started'
set -s command-alias[101] mkwins='new-window -dn SW1 ; new-window -dn SW2'
new-session -d -swork -nbase
greet
mkwins
EOF
$TMUX -f$CONF start 2>/dev/null || exit 1
[ "$($TMUX show -gv @g)" = started ] || { echo "startup-config alias did not run" >&2; exit 1; }
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
[ "$got" = "base,SW1,SW2," ] || { echo "startup-config multi alias: got [$got]" >&2; exit 1; }
$TMUX kill-server 2>/dev/null
wait_gone
# --- At server start: alias from config, used on the command line that starts
# the server. ---------------------------------------------------------
cat <<'EOF' >$CONF
set -s command-alias[100] greet='set -g @g argv'
EOF
$TMUX -f$CONF new-session -d -swork \; greet 2>/dev/null || exit 1
[ "$($TMUX show -gv @g)" = argv ] || { echo "argv-start alias did not run" >&2; exit 1; }
$TMUX kill-server 2>/dev/null
exit 0
+102
View File
@@ -0,0 +1,102 @@
#!/bin/sh
# Deferred command callbacks.
#
# Several commands store a parsed tree and invoke it later through
# cmd_invoke_get: if-shell (then/else, sync and background), run-shell -C, and -
# once a key is pressed by an attached client - a key binding, confirm-before and
# command-prompt. The headless cases run directly on the inner server; the
# client cases use the nested-tmux pattern from prompt-mechanics.sh: an outer
# server hosts the inner client in a pane and keys are injected with send-keys.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
OUT="$TEST_TMUX -Ltest -f/dev/null"
IN="$TEST_TMUX -Ltest2 -f/dev/null"
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
fail() { echo "[FAIL] $1" >&2; exit 1; }
settle() { sleep 0.5; }
CONF=$(mktemp)
trap "rm -f $CONF; $OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
# --- Headless deferred callbacks: no client needed. ------------------------
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
$IN set -g status on || exit 1
$IN set -g status-keys emacs || exit 1
$IN set -g window-size manual || exit 1
$IN if-shell true 'set -g @t then' 'set -g @t else' || exit 1
[ "$($IN show -gv @t)" = then ] || fail "if-shell true ran wrong branch"
$IN if-shell false 'set -g @f then' 'set -g @f else' || exit 1
[ "$($IN show -gv @f)" = else ] || fail "if-shell false ran wrong branch"
# Background (-b) if-shell evaluates its condition in a shell asynchronously.
$IN if-shell -b 'true' 'set -g @b yes' 'set -g @b no' || exit 1
settle
[ "$($IN show -gv @b)" = yes ] || fail "background if-shell ran wrong branch"
# A multi-command then-body runs both commands.
$IN if-shell true 'new-window -dn IF1 ; new-window -dn IF2' '' || exit 1
$IN list-windows -F '#{window_name}' | grep -qx IF1 || fail "if-shell body cmd1 missing"
$IN list-windows -F '#{window_name}' | grep -qx IF2 || fail "if-shell body cmd2 missing"
# run-shell -C invokes its argument as tmux commands.
$IN run-shell -C 'set -g @rc ran' || exit 1
[ "$($IN show -gv @rc)" = ran ] || fail "run-shell -C did not run command"
# --- Client deferred callbacks: key binding, confirm-before, command-prompt. -
$IN bind -n M-c confirm-before -p '(ok) ' 'set -g @cb confirmed' || exit 1
$IN bind -n M-d command-prompt -I pre -p '(cmd) ' 'set -g @cp %%' || exit 1
# A brace body must come through the lexer, so bind it from a config.
cat <<'EOF' >$CONF
bind -n M-k { new-window -dn K1 ; new-window -dn K2 }
EOF
$IN source-file $CONF || exit 1
$OUT new -d -x80 -y24 || exit 1
$OUT set -g status off || exit 1
$OUT set -g window-size manual || exit 1
$OUT send-keys -l "$IN attach" || exit 1
$OUT send-keys Enter || exit 1
sleep 1
# Key binding with a stored multi-command body fires on keypress.
$OUT send-keys M-k || exit 1
settle
$IN list-windows -F '#{window_name}' | grep -qx K1 || fail "key binding body cmd1 missing"
$IN list-windows -F '#{window_name}' | grep -qx K2 || fail "key binding body cmd2 missing"
# confirm-before runs its command only when confirmed.
$IN set -g @cb none || exit 1
$OUT send-keys M-c || exit 1
settle
$OUT capture-pane -p | tail -1 | grep -qF '(ok)' || fail "confirm-before prompt not shown"
$OUT send-keys n || exit 1
settle
[ "$($IN show -gv @cb)" = none ] || fail "confirm-before ran command after 'n'"
$OUT send-keys M-c || exit 1
settle
$OUT send-keys y || exit 1
settle
[ "$($IN show -gv @cb)" = confirmed ] || fail "confirm-before did not run command after 'y'"
# command-prompt feeds the typed line (with -I prefill) into its template.
$IN set -g @cp none || exit 1
$OUT send-keys M-d || exit 1
settle
$OUT capture-pane -p | tail -1 | grep -qF '(cmd)' || fail "command-prompt not shown"
$OUT send-keys -l X || exit 1
$OUT send-keys Enter || exit 1
settle
[ "$($IN show -gv @cp)" = preX ] || fail "command-prompt recovered '$($IN show -gv @cp)'"
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
exit 0
+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
# Invoke-time expansion and conditionals.
#
# The parser builds syntax only; environment/tilde expansion, assignments and
# %if/%elif/%else are all evaluated when the tree is invoked. This drives every
# one of those through a single config and checks the resulting option values:
# - FOO=bar assignments set the environment, read back with ${FOO} and $FOO
# - %hidden assignments behave the same
# - ~ expands to the home directory
# - %if / %elif / %else selects the correct branch
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
CONF=$(mktemp)
trap "rm -f $CONF" 0 1 15
cat <<'EOF' >$CONF
FOO=hello
set -g @assign "${FOO}"
set -g @dollar "$FOO"
%hidden BAR=secret
set -g @hidden "${BAR}"
set -g @undef "x${NOSUCHVAR_ZZZ}y"
set -g @tilde ~
set -g @baduser ~nosuchuser_zzz9
%if 1
set -g @iftrue then
%else
set -g @iftrue else
%endif
%if 0
set -g @iffalse then
%else
set -g @iffalse else
%endif
%if 0
set -g @elif a
%elif 1
set -g @elif b
%else
set -g @elif c
%endif
EOF
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
$TMUX source-file $CONF || exit 1
check() {
got=$($TMUX show -gv "$1")
[ "$got" = "$2" ] || { echo "$1: got [$got] expected [$2]" >&2; exit 1; }
}
check @assign hello
check @dollar hello
check @hidden secret
check @undef xy # undefined environment variable expands to nothing
check @baduser "" # unknown user in ~user expands to nothing
check @iftrue then
check @iffalse else
check @elif b
# ~ expands to an absolute home directory (value is machine dependent).
tilde=$($TMUX show -gv @tilde)
case "$tilde" in
/*) ;;
*) echo "@tilde did not expand to an absolute path: [$tilde]" >&2; exit 1;;
esac
$TMUX kill-server 2>/dev/null
exit 0
+72
View File
@@ -0,0 +1,72 @@
#!/bin/sh
# Command failure scope.
#
# Failure scope is the active sequence (CMD_PARSE_SEQUENCE): commands joined by
# ';' share one scope, so a failure skips the rest of that sequence; commands on
# separate lines are independent sequences and are not skipped. After a failed
# inner brace the enclosing sequence still resumes.
#
# Each case below creates windows around a deliberately invalid command and the
# resulting window list shows exactly which commands ran.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
TMP=$(mktemp)
CONF=$(mktemp)
trap "rm -f $TMP $CONF" 0 1 15
# kill-server is asynchronous, so wait for the old server to actually exit
# before starting a fresh one (otherwise the next start races a dying server).
fresh_server() {
$TMUX kill-server 2>/dev/null
i=0
while $TMUX list-sessions >/dev/null 2>&1; do
sleep 0.05
i=$((i + 1))
[ $i -gt 100 ] && break
done
$TMUX -f/dev/null start \; new-session -d -swork -nbase || exit 1
}
# $1 label, $2 config body, $3 expected comma-separated window list.
run() {
fresh_server
printf '%s\n' "$2" >$CONF
$TMUX source-file $CONF >/dev/null 2>&1
got=$($TMUX list-windows -t work -F '#{window_name}' | tr '\n' ',')
if [ "$got" != "$3" ]; then
echo "$1: got [$got] expected [$3]" >&2
exit 1
fi
}
# ';' sequence in a brace body: A runs, the bad command skips B (same sequence),
# the outer sequence resumes so C runs.
run "semicolon body" 'if-shell true { new-window -dn A ; nonexistent_cmd ; new-window -dn B }
new-window -dn C' 'base,A,C,'
# Newlines are independent sequences: the bad command skips only itself, so both
# B and C still run.
run "newline body" 'if-shell true {
new-window -dn A
nonexistent_cmd
new-window -dn B
}
new-window -dn C' 'base,A,B,C,'
# Top-level ';' sequence: failure skips the rest of the line.
run "top-level semicolon" 'new-window -dn A ; nonexistent_cmd ; new-window -dn B' 'base,A,'
# Top-level newlines: independent, both run.
run "top-level newline" 'new-window -dn A
nonexistent_cmd
new-window -dn B' 'base,A,B,'
$TMUX kill-server 2>/dev/null
exit 0
+46
View File
@@ -0,0 +1,46 @@
#!/bin/sh
# Hooks run stored command trees.
#
# A hook stores a parsed command tree that is invoked through cmd_invoke_get when
# the hook fires. Covers a single-command hook, a multi-entry hook array (every
# entry fires), and that show-hooks prints the stored commands back.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX -f/dev/null start \; new-session -d -sfirst -nbase 2>/dev/null || exit 1
# Single-command hook fires on session creation.
$TMUX set-hook -g session-created 'set -g @hook done' || exit 1
$TMUX new-session -d -ssecond || exit 1
[ "$($TMUX show -gv @hook)" = done ] || {
echo "single-command hook did not fire" >&2
exit 1
}
# A hook array fires every entry.
$TMUX set-hook -g session-created[10] 'new-window -dn H1' || exit 1
$TMUX set-hook -g session-created[11] 'set -g @hook2 two' || exit 1
$TMUX new-session -d -sthird || exit 1
[ "$($TMUX show -gv @hook2)" = two ] || {
echo "hook array entry 11 did not fire" >&2
exit 1
}
echo "$($TMUX list-windows -t third -F '#{window_name}')" | grep -qx H1 || {
echo "hook array entry 10 did not create window" >&2
exit 1
}
# show-hooks prints the stored hook commands.
$TMUX show-hooks -g | grep -q 'session-created.* set -g @hook done' || {
echo "show-hooks did not print stored hook command" >&2
exit 1
}
$TMUX kill-server 2>/dev/null
exit 0
+56
View File
@@ -0,0 +1,56 @@
#!/bin/sh
# Read-only client enforcement.
#
# When the invoking client is read-only, cmd_invoke builds each command and
# rejects it (without running it) unless the command entry is marked read-only.
# A read-only client is created by attaching with -r; the nested-tmux pattern
# from cmd-invoke-deferred.sh is used so a real read-only client presses a key.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
OUT="$TEST_TMUX -Ltest -f/dev/null"
IN="$TEST_TMUX -Ltest2 -f/dev/null"
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
trap "$OUT kill-server 2>/dev/null; $IN kill-server 2>/dev/null" 0 1 15
fail() { echo "[FAIL] $1" >&2; exit 1; }
settle() { sleep 0.5; }
# Inner session: a key bound to a non-read-only command.
$IN new -d -x80 -y23 -nbase "sh -c 'exec sleep 1000'" || exit 1
$IN set -g status on || exit 1
$IN set -g status-keys emacs || exit 1
$IN set -g window-size manual || exit 1
$IN bind -n M-w new-window -dn ROTRY || exit 1
# Outer session hosts the inner client, attached read-only with -r.
$OUT new -d -x80 -y24 || exit 1
$OUT set -g status off || exit 1
$OUT set -g window-size manual || exit 1
$OUT send-keys -l "$IN attach -r" || exit 1
$OUT send-keys Enter || exit 1
sleep 1
[ "$($IN list-clients -F '#{client_readonly}')" = 1 ] || fail "client is not read-only"
# Pressing the key runs new-window through the read-only client: it must be
# rejected, leave no window, and report the error.
$OUT send-keys M-w || exit 1
settle
$IN list-windows -F '#{window_name}' | grep -qx ROTRY && \
fail "read-only client was allowed to create a window"
$OUT capture-pane -p | grep -qi 'read-only' || fail "no read-only error was shown"
# Positive control: the same command with no client is not read-only and runs,
# proving the command itself is valid and only the read-only client blocked it.
$IN new-window -dn OKWIN || exit 1
$IN list-windows -F '#{window_name}' | grep -qx OKWIN || fail "control new-window did not run"
$OUT kill-server 2>/dev/null
$IN kill-server 2>/dev/null
exit 0
+51
View File
@@ -0,0 +1,51 @@
#!/bin/sh
# Malformed input rejection and crash/leak canary.
#
# Each input below is a syntax error. For every one we require that:
# - parsing reports failure (non-zero exit), and
# - the server is still alive afterwards (a follow-up command succeeds).
# The second check is the important one: a parser that crashes or corrupts state
# on bad input would take the server down here.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest -f/dev/null"
$TMUX kill-server 2>/dev/null
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
n=0
check() {
n=$((n + 1))
# Expect a parse failure.
if $TMUX run-shell -C "$1" >/dev/null 2>&1; then
echo "case $n: expected failure but succeeded: $1" >&2
exit 1
fi
# Expect the server to still be responsive.
if ! $TMUX list-sessions >/dev/null 2>&1; then
echo "case $n: server died after: $1" >&2
exit 1
fi
}
check 'if-shell true {' # unterminated brace body
check 'display-message a }' # stray close brace
check 'if-shell true { display-message a' # unclosed nested brace
check 'if-shell true { ; }' # empty sequence in body
check '%elif 1' # %elif with no %if
check '%endif' # %endif with no %if
check '%zzz' # unknown % directive
check '; display-message a' # leading separator
input='%if 1
display-message a'
check "$input" # unterminated %if
input='%if 1
%endif'
check "$input" # empty %if body
$TMUX kill-server 2>/dev/null
exit 0
+120
View File
@@ -0,0 +1,120 @@
#!/bin/sh
# Parse/print golden suite for the command parser.
#
# Binds a spread of command syntax forms into a dedicated key table, then prints
# them back with list-keys (which calls cmd_parse_print on the stored tree). A
# command-valued option is printed too. Three things are checked:
# - the default (single line) form is byte-for-byte as expected: quoting,
# escaped \; separators, inline braces, and preservation of unexpanded
# ${env}, ~ and #{format} inside stored bodies;
# - the -p (multiline) form is as expected;
# - the default form round-trips: sourcing it again reproduces the same keys.
PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
SOCKET=$(mktemp -u testXXXXXX)
TMUX="$TEST_TMUX -L$SOCKET -f/dev/null"
TMUX2=
$TMUX kill-server 2>/dev/null
TMP=$(mktemp)
CONF=$(mktemp)
EXP=$(mktemp)
RT1=$(mktemp)
RT2=$(mktemp)
trap "[ -n \"\$TMUX\" ] && \$TMUX kill-server 2>/dev/null; [ -n \"\$TMUX2\" ] && \$TMUX2 kill-server 2>/dev/null; rm -f $TMP $CONF $EXP $RT1 $RT2" 0 1 15
cat <<'EOF' >$CONF
bind -T parsetest a display-message hello
bind -T parsetest b display-message "hello world"
bind -T parsetest c display-message 'literal $HOME #{p} ~'
bind -T parsetest d display-message ""
bind -T parsetest e display-message "#{pane_id}"
bind -T parsetest f display-message a \; display-message b
bind -T parsetest g {
display-message one
display-message two
}
bind -T parsetest h if-shell true { display-message yes } { display-message no }
bind -T parsetest m if-shell true { display-message "${HOME}" "~" "~root" "#{pane_id}" }
bind -T parsetest n if-shell true { display-message "a\nb" "x;y" '#literal' }
EOF
# Expected output. The default form is single line (round-trippable); the -p
# form is multiline, with lines inside braced bodies indented by a single tab.
cat <<'EOF' >$EXP
bind-key -T parsetest a display-message hello
bind-key -T parsetest b display-message 'hello world'
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
bind-key -T parsetest d display-message ''
bind-key -T parsetest e display-message '#{pane_id}'
bind-key -T parsetest f display-message a \; display-message b
bind-key -T parsetest g display-message one \; display-message two
bind-key -T parsetest h if-shell true { display-message yes } { display-message no }
bind-key -T parsetest m if-shell true { display-message ${HOME} ~ ~root '#{pane_id}' }
bind-key -T parsetest n if-shell true { display-message a\nb 'x;y' '#literal' }
--- options ---
display-message 'hi there'
--- multiline ---
bind-key -T parsetest a display-message hello
bind-key -T parsetest b display-message 'hello world'
bind-key -T parsetest c display-message 'literal $HOME #{p} ~'
bind-key -T parsetest d display-message ''
bind-key -T parsetest e display-message '#{pane_id}'
bind-key -T parsetest f display-message a \; display-message b
bind-key -T parsetest g display-message one
display-message two
bind-key -T parsetest h if-shell true {
display-message yes
} {
display-message no
}
bind-key -T parsetest m if-shell true {
display-message ${HOME} ~ ~root '#{pane_id}'
}
bind-key -T parsetest n if-shell true {
display-message a\nb 'x;y' '#literal'
}
EOF
$TMUX -f/dev/null start \; new-session -d 2>/dev/null || exit 1
$TMUX source-file $CONF || exit 1
$TMUX set -g default-client-command 'display-message "hi there"' || exit 1
set +x
{
$TMUX list-keys -T parsetest
echo "--- options ---"
$TMUX show -gv default-client-command
echo "--- multiline ---"
$TMUX list-keys -p -T parsetest
} >$TMP 2>&1 || exit 1
set -x
cmp -s $TMP $EXP || {
echo "cmd-parse-print: output differs from expected" >&2
diff -u $EXP $TMP >&2
exit 1
}
# Round-trip: the default form re-sourced into a fresh server must reproduce the
# identical key list.
$TMUX list-keys -T parsetest >$RT1 || exit 1
$TMUX kill-server 2>/dev/null
SOCKET2=$(mktemp -u testXXXXXX)
TMUX2="$TEST_TMUX -L$SOCKET2 -f/dev/null"
$TMUX2 -f/dev/null start \; new-session -d 2>/dev/null || exit 1
$TMUX2 source-file $RT1 || exit 1
$TMUX2 list-keys -T parsetest >$RT2 || exit 1
$TMUX2 kill-server 2>/dev/null
cmp -s $RT1 $RT2 || {
echo "cmd-parse-print: default form does not round-trip" >&2
diff -u $RT1 $RT2 >&2
exit 1
}
exit 0
+2 -1
View File
@@ -19,7 +19,8 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -Ltest"
SOCKET=$(mktemp -u testXXXXXX)
TMUX="$TEST_TMUX -L$SOCKET"
$TMUX kill-server 2>/dev/null
DIR=$(mktemp -d)
+19 -8
View File
@@ -7,24 +7,35 @@ PATH=/bin:/usr/bin
TERM=screen
[ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux)
TMUX="$TEST_TMUX -LtestA$$ -f/dev/null"
SOCKET=$(mktemp -u testAXXXXXX)
TMUX="$TEST_TMUX -L$SOCKET -f/dev/null"
$TMUX kill-server 2>/dev/null
TMP=$(mktemp)
OUT=$(mktemp)
trap "rm -f $TMP $OUT" 0 1 15
cleanup() {
$TMUX kill-server 2>/dev/null
rm -f "$TMP" "$OUT"
}
trap cleanup 0 1 15
cat <<EOF >$TMP
cat <<EOF >"$TMP"
if 'true' 'wibble wobble'
EOF
$TMUX -f$TMP -C new <<EOF >$OUT
$TMUX -f"$TMP" -C new <<EOF >"$OUT"
EOF
grep -q "^%config-error $TMP:1: $TMP:1: unknown command: wibble$" $OUT
if ! grep -q "^%config-error $TMP:1: unknown command: wibble$" "$OUT"; then
cat "$OUT" >&2
exit 1
fi
cat <<EOF >$TMP
cat <<EOF >"$TMP"
wibble wobble
EOF
echo "source $TMP" | $TMUX -C new >$OUT
grep -q "^%config-error $TMP:1: unknown command: wibble$" $OUT
echo "source $TMP" | $TMUX -C new >"$OUT"
if ! grep -q "^%config-error $TMP:1: unknown command: wibble$" "$OUT"; then
cat "$OUT" >&2
exit 1
fi
+20 -3
View File
@@ -506,6 +506,7 @@ screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width,
va_list ap;
char *tmp;
u_int cy = s->cy, i, end, next, idx = 0, at, left;
u_int w, pos, n;
struct utf8_data *text;
struct grid_cell gc;
@@ -525,9 +526,14 @@ screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width,
for (end = idx; text[end].size != 0; end++) {
if (text[end].size == 1 && text[end].data[0] == '\n')
break;
if (at + text[end].width > left)
/* A tab expands to the next multiple of eight columns. */
if (text[end].size == 1 && text[end].data[0] == '\t')
w = 8 - (at % 8);
else
w = text[end].width;
if (at + w > left)
break;
at += text[end].width;
at += w;
}
/*
@@ -552,10 +558,21 @@ screen_write_text(struct screen_write_ctx *ctx, u_int cx, u_int width,
next = end;
}
/* Print the line. */
/* Print the line, expanding any tabs to spaces. */
pos = 0;
for (i = idx; i < end; i++) {
if (text[i].size == 1 && text[i].data[0] == '\t') {
n = 8 - (pos % 8);
utf8_set(&gc.data, ' ');
while (n-- > 0) {
screen_write_cell(ctx, &gc);
pos++;
}
continue;
}
utf8_copy(&gc.data, &text[i]);
screen_write_cell(ctx, &gc);
pos += text[i].width;
}
/* If at the bottom, stop. */
+13 -38
View File
@@ -2760,28 +2760,15 @@ bad:
proc_kill_peer(c->peer);
}
/* Callback when command is not allowed. */
static enum cmd_retval
server_client_read_only(struct cmdq_item *item, __unused void *data)
{
cmdq_error(item, "client is read-only");
return (CMD_RETURN_ERROR);
}
/* Callback for default command. */
static enum cmd_retval
server_client_default_command(struct cmdq_item *item, __unused void *data)
{
struct client *c = cmdq_get_client(item);
struct cmd_list *cmdlist;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
cmdlist = options_get_command(global_options, "default-client-command");
if ((c->flags & CLIENT_READONLY) &&
!cmd_list_all_have(cmdlist, CMD_READONLY))
new_item = cmdq_get_callback(server_client_read_only, NULL);
else
new_item = cmdq_get_command(cmdlist, NULL);
tree = options_get_command(global_options, "default-client-command");
new_item = cmd_invoke_get(tree, NULL, NULL);
cmdq_insert_after(item, new_item);
return (CMD_RETURN_NORMAL);
}
@@ -2811,9 +2798,9 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
size_t len;
int argc = 0;
char **argv, *cause;
struct cmd_parse_result *pr;
struct cmdq_item *item;
struct args_value *values;
struct cmdq_item *new_item;
struct cmd_parse_tree *tree;
if (c->flags & CLIENT_EXIT)
return (0);
@@ -2833,31 +2820,19 @@ server_client_dispatch_command(struct client *c, struct imsg *imsg)
}
argc = data.argc;
if (argc == 0) {
new_item = cmdq_get_callback(server_client_default_command,
NULL);
} else {
if (argc == 0)
item = cmdq_get_callback(server_client_default_command, NULL);
else {
values = args_from_vector(argc, argv);
pr = cmd_parse_from_arguments(values, argc, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
cause = pr->error;
goto error;
case CMD_PARSE_SUCCESS:
break;
}
tree = cmd_parse_from_arguments(values, argc, NULL);
args_free_values(values, argc);
free(values);
cmd_free_argv(argc, argv);
if ((c->flags & CLIENT_READONLY) &&
!cmd_list_all_have(pr->cmdlist, CMD_READONLY)) {
new_item = cmdq_get_callback(server_client_read_only,
NULL);
} else
new_item = cmdq_get_command(pr->cmdlist, NULL);
cmd_list_free(pr->cmdlist);
item = cmd_invoke_get(tree, NULL, NULL);
cmd_parse_free(tree);
}
cmdq_append(c, new_item);
cmdq_append(c, item);
cmdq_append(c, cmdq_get_callback(server_client_command_done, NULL));
return (0);
+106
View File
@@ -0,0 +1,106 @@
/* $OpenBSD$ */
/*
* Copyright (c) 2026 Nicholas Marriott <nicm@users.sourceforge.net>
*
* 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.
*/
#ifndef TMUX_PARSER_H
#define TMUX_PARSER_H
#include <sys/types.h>
#include <stdio.h>
/*
* Parsed command tree.
*
* The parser builds syntax only. It does not expand formats, environment
* variables, or ~, does not evaluate %if/%elif, does not expand aliases, and
* does not build struct cmd. That is an execution-time operation.
*
* Command failure scope is represented by CMD_PARSE_SEQUENCE: if an invoked
* command or assignment fails, the invoker skips the remaining children of
* that sequence. No explicit group ID is stored in the tree.
*/
struct cmd_parse_tree;
struct cmd_parse_node;
struct args_value;
struct cmd_parse_input {
int flags;
#define CMD_PARSE_QUIET 0x1
#define CMD_PARSE_PARSEONLY 0x2
#define CMD_PARSE_NOALIAS 0x4
#define CMD_PARSE_VERBOSE 0x8
#define CMD_PARSE_ONEGROUP 0x10
const char *file;
u_int line;
};
enum cmd_parse_node_type {
CMD_PARSE_ROOT,
CMD_PARSE_SEQUENCE,
CMD_PARSE_COMMAND,
CMD_PARSE_STRING,
CMD_PARSE_COMMANDS,
CMD_PARSE_TEXT,
CMD_PARSE_ENVIRONMENT,
CMD_PARSE_TILDE,
CMD_PARSE_ASSIGN,
CMD_PARSE_HIDDEN_ASSIGN,
CMD_PARSE_IF,
CMD_PARSE_ELIF,
CMD_PARSE_ELSE
};
#define CMD_PARSE_PRINT_MULTILINE 0x1
struct cmd_parse_tree *cmd_parse_from_file(FILE *, struct cmd_parse_input *,
char **);
struct cmd_parse_tree *cmd_parse_from_buffer(const void *, size_t,
struct cmd_parse_input *, char **);
struct cmd_parse_tree *cmd_parse_from_string(const char *,
struct cmd_parse_input *, char **);
struct cmd_parse_tree *cmd_parse_from_node(struct cmd_parse_tree *,
struct cmd_parse_node *);
struct cmd_parse_tree *cmd_parse_from_arguments(struct args_value *, u_int,
struct cmd_parse_input *);
int cmd_parse_expand_alias(struct cmd_parse_tree *,
struct cmd_parse_node *, struct cmd_parse_tree **,
char **);
struct cmd_parse_tree *cmd_parse_add_ref(struct cmd_parse_tree *);
int cmd_parse_any_have(struct cmd_parse_tree *, int);
void cmd_parse_free(struct cmd_parse_tree *);
struct cmd_parse_node *cmd_parse_root(struct cmd_parse_tree *);
const char *cmd_parse_file(struct cmd_parse_tree *);
int cmd_parse_flags(struct cmd_parse_tree *);
char *cmd_parse_print(struct cmd_parse_tree *, int);
void cmd_parse_log(const char *, struct cmd_parse_tree *);
void cmd_parse_log_node(const char *,
struct cmd_parse_node *);
enum cmd_parse_node_type cmd_parse_node_type(struct cmd_parse_node *);
const char *cmd_parse_node_type_string(enum cmd_parse_node_type);
const char *cmd_parse_node_value(struct cmd_parse_node *);
u_int cmd_parse_node_line(struct cmd_parse_node *);
struct cmd_parse_node *cmd_parse_node_first_child(struct cmd_parse_node *);
struct cmd_parse_node *cmd_parse_node_next(struct cmd_parse_node *);
#endif /* TMUX_PARSER_H */
+100 -71
View File
@@ -444,16 +444,15 @@ are arguments.
.Pp
.Nm
distinguishes between command parsing and execution.
In order to execute a command,
.Nm
needs it to be split up into its name and arguments.
This is command parsing.
If a command is run from the shell, the shell parses it; from inside
When commands are run from the shell, the shell parses its command line first;
from inside
.Nm
or from a configuration file,
.Nm
does.
Examples of when
parses the
.Nm
command language.
Examples of places where
.Nm
parses commands are:
.Bl -dash -offset indent
@@ -472,42 +471,60 @@ or
.Ic confirm\-before .
.El
.Pp
To execute commands, each client has a
.Ql command queue .
A global command queue not attached to any client is used on startup
for configuration files like
.Pa \[ti]/.tmux.conf .
Parsed commands added to the queue are executed in order.
Some commands, like
.Ic if\-shell
and
.Ic confirm\-before ,
parse their argument to create a new command which is inserted immediately
after themselves.
This means that arguments can be parsed twice or more - once when the parent
command (such as
.Ic if\-shell )
is parsed and again when it parses and executes its command.
Commands like
Some commands accept other
.Nm
commands as arguments.
For example,
.Ic if\-shell ,
.Ic run\-shell
.Fl C
and
.Ic display\-panes
stop execution of subsequent commands on the queue until something happens -
.Ic confirm\-before
take a command to run later.
That command may be given as a string or as a command list in braces.
Braces avoid the need for extra escaping when the argument contains
.Nm
commands.
For example:
.Bd -literal -offset indent
if\-shell true { display \-p \[aq]it worked\[aq] }
.Ed
.Pp
is equivalent to:
.Bd -literal -offset indent
if\-shell true "display \-p \[aq]it worked\[aq]"
.Ed
.Pp
Commands given as strings to commands such as
.Ic if\-shell ,
.Ic run\-shell
.Fl C
or
.Ic confirm\-before
are parsed when they are run.
Commands given in braces are parsed when the outer command is parsed.
.Pp
Commands are executed in order.
Commands like
.Ic if\-shell
and
.Ic run\-shell
until a shell command finishes and
may run other commands before later commands continue.
Commands such as
.Ic if\-shell
and
.Ic run\-shell
wait for a shell command to finish before continuing, and
.Ic display\-panes
until a key is pressed.
waits for a key to be pressed.
For example, the following commands:
.Bd -literal -offset indent
new\-session; new\-window
if\-shell "true" "split\-window"
if\-shell "true" { split\-window }
kill\-session
.Ed
.Pp
Will execute
execute
.Ic new\-session ,
.Ic new\-window ,
.Ic if\-shell ,
@@ -518,6 +535,14 @@ and
.Ic kill\-session
in that order.
.Pp
Commands separated by semicolons are in the same command sequence.
If a command fails, any following commands in that sequence are skipped, but
later command sequences continue.
Newlines normally start a new command sequence, so a failure on one line does
not skip commands on later lines.
When a string argument is parsed as commands, its commands are parsed as one
sequence even if the string contains newlines.
.Pp
The
.Sx COMMANDS
section lists the
@@ -534,10 +559,8 @@ or
.Xr csh 1 .
.Pp
Each command is terminated by a newline or a semicolon (;).
Commands separated by semicolons together form a
.Ql command sequence
- if a command in the sequence encounters an error, no subsequent commands are
executed.
A newline starts a new command sequence; commands separated by semicolons are
in the same command sequence.
.Pp
It is recommended that a semicolon used as a command separator should be
written as an individual token, for example from
@@ -606,33 +629,38 @@ comment is ignored until the end of the line.
If the last character of a line is \e, the line is joined with the following
line (the \e and the newline are completely removed).
This is called line continuation and applies both inside and outside quoted
strings and in comments, but not inside braces.
strings, in comments and inside braces.
.Pp
Command arguments may be specified as strings surrounded by single (\[aq])
quotes or double quotes (\[dq]), or as command lists surrounded by braces ({}).
Command arguments may be strings surrounded by single (\[aq]) quotes or double
quotes (\[dq]).
.\" "
This is required when the argument contains any special character.
Quoting or escaping is required when a string argument contains any special
character.
Single and double quoted strings cannot span multiple lines except with line
continuation.
Braces can span multiple lines.
Some arguments may be command lists surrounded by braces ({}).
Braces can span multiple lines and are parsed as
.Nm
command syntax.
.Pp
Outside of quotes and inside double quotes, these replacements are performed:
Outside single quotes, these replacements are recognized:
.Bl -dash -offset indent
.It
Environment variables preceded by $ are replaced with their value from the
global environment (see the
Environment variables preceded by $ are replaced when the command is run
with their value from the client environment, if present, or the global
environment (see the
.Sx GLOBAL AND SESSION ENVIRONMENT
section).
.It
A leading \[ti] or \[ti]user is expanded to the home directory of the current or
specified user.
A leading \[ti] or \[ti]user is replaced when the command is run by the
home directory of the current or specified user.
.It
\euXXXX or \euXXXXXXXX is replaced by the Unicode codepoint corresponding to
the given four or eight digit hexadecimal number.
.It
When preceded (escaped) by a \e, the following characters are replaced: \ee by
the escape character; \er by a carriage return; \en by a newline; and \et by a
tab.
When preceded (escaped) by a \e, the following characters are replaced: \ea by
bell; \eb by backspace; \ee by escape; \ef by form feed; \er by carriage
return; \en by newline; \es by space; \et by tab; and \ev by vertical tab.
.It
\eooo is replaced by a character of the octal value ooo.
Three octal digits are required, for example \e001.
@@ -644,16 +672,12 @@ is removed) and are not treated as having any special meaning - so for example
variable.
.El
.Pp
Braces are parsed as a configuration file (so conditions such as
.Ql %if
are processed) and then converted into a string.
They are designed to avoid the need for additional escaping when passing a
group of
Braces are designed to avoid additional escaping when passing a group of
.Nm
commands as an argument (for example to
.Ic if\-shell ) .
These two examples produce an identical command - note that no escaping is
needed when using {}:
These two examples have the same effect - note that no escaping is needed when
using {}:
.Bd -literal -offset indent
if\-shell true {
display \-p \[aq]brace\-dollar\-foo: }$foo\[aq]
@@ -675,7 +699,8 @@ Environment variables may be set by using the syntax
.Ql name=value ,
for example
.Ql HOME=/home/user .
Variables set during parsing are added to the global environment.
Variables set this way are added to the global environment when the command is
run.
A hidden variable may be set with
.Ql %hidden ,
for example:
@@ -689,7 +714,7 @@ See the
.Sx GLOBAL AND SESSION ENVIRONMENT
section.
.Pp
Commands may be parsed conditionally by surrounding them with
Commands may be made conditional by surrounding them with
.Ql %if ,
.Ql %elif ,
.Ql %else
@@ -699,14 +724,11 @@ The argument to
.Ql %if
and
.Ql %elif
is expanded as a format (see
is expanded as a format when it is evaluated (see
.Sx FORMATS )
and if it evaluates to false (zero or empty), subsequent text is ignored until
the closing
.Ql %elif ,
.Ql %else
or
.Ql %endif .
and the first true branch is executed.
Branches which are not selected are not executed.
All branches must still be syntactically valid.
For example:
.Bd -literal -offset indent
%if "#{==:#{host},myhost}"
@@ -4375,7 +4397,7 @@ To view the default bindings and possible commands, see the
command.
.Tg lsk
.It Xo Ic list\-keys
.Op Fl 1aNr
.Op Fl 1aNpr
.Op Fl F Ar format
.Op Fl O Ar sort\-order
.Op Fl P Ar prefix\-string
@@ -4395,6 +4417,8 @@ specifies a
to list from.
.Fl 1
lists only the first matching key.
.Fl p
prints each command using multiple lines where possible.
.Fl O
specifies the sort order: one of
.Ql key ,
@@ -4767,10 +4791,16 @@ scrolled into the scrollback buffer so it remains accessible.
.Ar name=value
.Xc
This is an array of custom aliases for commands.
If an unknown command matches
If a command name matches
.Ar name ,
it is replaced with
.Ar value .
the alias
.Ar value
is parsed as
.Nm
commands and used instead.
Any arguments after
.Ar name
are appended to the last command in the alias.
For example, after:
.Pp
.Dl set \-s command\-alias[zoom] zoom=\[aq]resize\-pane \-Z\[aq]
@@ -4783,10 +4813,9 @@ Is equivalent to:
.Pp
.Dl resize\-pane \-Z \-t:.1
.Pp
Note that aliases are expanded when a command is parsed rather than when it is
executed, so binding an alias with
.Ic bind\-key
will bind the expanded form.
Aliases are expanded when commands are run, so a change to
.Ic command\-alias[]
affects stored commands such as key bindings when they are next run.
.It Ic codepoint\-widths[] Ar string
An array option allowing widths of Unicode codepoints to be overridden.
Note the new width applies to all clients.
+37 -86
View File
@@ -33,16 +33,19 @@
#endif
#include "compat.h"
#include "tmux-parser.h"
#include "tmux-protocol.h"
#include "xmalloc.h"
extern char **environ;
struct args;
struct args_command_state;
struct client;
struct cmd;
struct cmd_find_state;
struct cmd_invoke_state;
struct cmd_parse_node;
struct cmd_parse_tree;
struct cmdq_item;
struct cmdq_list;
struct cmdq_state;
@@ -1931,13 +1934,13 @@ enum args_type {
/* Argument value. */
struct args_value {
enum args_type type;
enum args_type type;
union {
char *string;
struct cmd_list *cmdlist;
char *string;
struct cmd_parse_tree *cmd;
};
char *cached;
TAILQ_ENTRY(args_value) entry;
char *cached;
TAILQ_ENTRY(args_value) entry;
};
/* Arguments set. */
@@ -1987,13 +1990,6 @@ struct cmd_find_state {
#define CMD_FIND_EXACT_WINDOW 0x20
#define CMD_FIND_CANFAIL 0x40
/* List of commands. */
struct cmd_list {
int references;
u_int group;
struct cmds *list;
};
/* Command return values. */
enum cmd_retval {
CMD_RETURN_ERROR = -1,
@@ -2002,32 +1998,6 @@ enum cmd_retval {
CMD_RETURN_STOP
};
/* Command parse result. */
enum cmd_parse_status {
CMD_PARSE_ERROR,
CMD_PARSE_SUCCESS
};
struct cmd_parse_result {
enum cmd_parse_status status;
struct cmd_list *cmdlist;
char *error;
};
struct cmd_parse_input {
int flags;
#define CMD_PARSE_QUIET 0x1
#define CMD_PARSE_PARSEONLY 0x2
#define CMD_PARSE_NOALIAS 0x4
#define CMD_PARSE_VERBOSE 0x8
#define CMD_PARSE_ONEGROUP 0x10
const char *file;
u_int line;
struct cmdq_item *item;
struct client *c;
struct cmd_find_state fs;
};
/* Command queue flags. */
#define CMDQ_STATE_REPEAT 0x1
#define CMDQ_STATE_CONTROL 0x2
@@ -2065,6 +2035,12 @@ struct cmd_entry {
enum cmd_retval (*exec)(struct cmd *, struct cmdq_item *);
};
/* Command invocation input. */
struct cmd_invoke_input {
int argc;
char **argv;
};
/* Status line. */
#define STATUS_LINES_LIMIT 5
struct status_line {
@@ -2413,7 +2389,7 @@ typedef void (*event_payload_print_cb)(void *, struct evbuffer *);
/* Key binding and key table. */
struct key_binding {
key_code key;
struct cmd_list *cmdlist;
struct cmd_parse_tree *cmd;
const char *note;
const char *tablename;
@@ -2443,7 +2419,7 @@ union options_value {
long long number;
struct style style;
struct options_array array;
struct cmd_list *cmdlist;
struct cmd_parse_tree *cmd;
};
/* Option table entries. */
@@ -2621,11 +2597,11 @@ extern char **cfg_files;
extern u_int cfg_nfiles;
extern int cfg_quiet;
void start_cfg(void);
int load_cfg(const char *, struct client *, struct cmdq_item *,
struct cmd_find_state *, int, struct cmdq_item **);
int load_cfg_from_buffer(const void *, size_t, const char *,
struct client *, struct cmdq_item *, struct cmd_find_state *,
int load_cfg(const char *, struct cmdq_item *, struct cmd_find_state *,
int, struct cmdq_item **);
int load_cfg_from_buffer(const void *, size_t, const char *,
struct cmdq_item *, struct cmd_find_state *, int,
struct cmdq_item **);
void printflike(1, 2) cfg_add_cause(const char *, ...);
void cfg_print_causes(struct cmdq_item *);
void cfg_show_causes(struct session *);
@@ -2847,13 +2823,13 @@ struct options_entry *options_match_get(struct options *, const char *,
char **, int, int *);
const char *options_get_string(struct options *, const char *);
long long options_get_number(struct options *, const char *);
struct cmd_list *options_get_command(struct options *, const char *);
struct cmd_parse_tree *options_get_command(struct options *, const char *);
struct options_entry * printflike(4, 5) options_set_string(struct options *,
const char *, int, const char *, ...);
struct options_entry *options_set_number(struct options *, const char *,
long long);
struct options_entry *options_set_command(struct options *, const char *,
struct cmd_list *);
struct cmd_parse_tree *);
int options_scope_from_name(struct args *, int,
const char *, struct cmd_find_state *, struct options **,
char **);
@@ -3072,14 +3048,8 @@ 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 *,
u_int, int);
struct args_command_state *args_make_commands_prepare(struct cmd *,
struct cmdq_item *, u_int, const char *, int, int);
struct cmd_list *args_make_commands(struct args_command_state *, int, char **,
char **);
void args_make_commands_free(struct args_command_state *);
char *args_make_commands_get_command(struct args_command_state *);
struct cmd_parse_tree *args_to_commands(struct cmd *, struct cmdq_item *,
u_int, const char *, int, char **);
struct args_value *args_first_value(struct args *, u_char);
struct args_value *args_next_value(struct args_value *);
long long args_strtonum(struct args *, u_char, long long, long long,
@@ -3137,27 +3107,12 @@ char *cmd_stringify_argv(int, char **);
char *cmd_get_alias(const char *);
const struct cmd_entry *cmd_get_entry(struct cmd *);
struct args *cmd_get_args(struct cmd *);
u_int cmd_get_group(struct cmd *);
void cmd_get_source(struct cmd *, const char **, u_int *);
int cmd_get_parse_flags(struct cmd *);
struct cmd *cmd_parse(struct args_value *, u_int, const char *, u_int, 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(const 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 *);
void cmd_list_free(struct cmd_list *);
#define CMD_LIST_PRINT_ESCAPED 0x1
#define CMD_LIST_PRINT_NO_GROUPS 0x2
char *cmd_list_print(const struct cmd_list *, int);
struct cmd *cmd_list_first(struct cmd_list *);
struct cmd *cmd_list_next(struct cmd *);
int cmd_list_all_have(struct cmd_list *, int);
int cmd_list_any_have(struct cmd_list *, int);
int cmd_mouse_at(struct window_pane *, struct mouse_event *,
u_int *, u_int *, int);
struct winlink *cmd_mouse_window(struct mouse_event *, struct session **);
@@ -3169,20 +3124,13 @@ char *cmd_template_replace(const char *, const char *, int);
enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int,
int, const char *, int, const char *);
/* cmd-parse.c */
struct cmd_parse_result *cmd_parse_from_file(FILE *, struct cmd_parse_input *);
struct cmd_parse_result *cmd_parse_from_string(const char *,
struct cmd_parse_input *);
enum cmd_parse_status cmd_parse_and_insert(const char *,
struct cmd_parse_input *, struct cmdq_item *,
struct cmdq_state *, char **);
enum cmd_parse_status cmd_parse_and_append(const char *,
struct cmd_parse_input *, struct client *,
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(struct args_value *, u_int,
struct cmd_parse_input *);
/* cmd-invoke.c */
struct cmdq_item *cmd_invoke_get(struct cmd_parse_tree *, struct cmdq_state *,
const struct cmd_invoke_input *);
struct cmd_invoke_state *cmd_invoke_state_add_ref(struct cmd_invoke_state *);
void cmd_invoke_state_free(struct cmd_invoke_state *);
void cmd_invoke_result(struct cmd_invoke_state *, enum cmd_retval);
enum cmd_retval cmd_invoke_fire(struct cmdq_item *, struct cmd_invoke_state *);
/* cmd-queue.c */
struct cmdq_state *cmdq_new_state(struct cmd_find_state *, struct key_event *,
@@ -3207,7 +3155,10 @@ struct cmd_find_state *cmdq_get_source(struct cmdq_item *);
struct key_event *cmdq_get_event(struct cmdq_item *);
struct cmd_find_state *cmdq_get_current(struct cmdq_item *);
int cmdq_get_flags(struct cmdq_item *);
struct cmdq_item *cmdq_get_command(struct cmd_list *, struct cmdq_state *);
struct cmdq_item *cmdq_get_one_command(struct cmd *, struct cmdq_state *,
struct cmd_invoke_state *);
struct cmdq_item *cmdq_get_invoke(struct cmd_invoke_state *,
struct cmdq_state *);
#define cmdq_get_callback(cb, data) cmdq_get_callback1(#cb, cb, data)
struct cmdq_item *cmdq_get_callback1(const char *, cmdq_cb, void *);
struct cmdq_item *cmdq_get_error(const char *);
@@ -3239,7 +3190,7 @@ struct key_binding *key_bindings_get_default(struct key_table *, key_code);
struct key_binding *key_bindings_first(struct key_table *);
struct key_binding *key_bindings_next(struct key_table *, struct key_binding *);
void key_bindings_add(const char *, key_code, const char *, int,
struct cmd_list *);
struct cmd_parse_tree *);
void key_bindings_remove(const char *, key_code);
void key_bindings_reset(const char *, key_code);
void key_bindings_remove_table(const char *);
+71 -34
View File
@@ -495,26 +495,64 @@ window_customize_option_editable(struct window_customize_modedata *data,
return (1);
}
static int
window_customize_check_command_node(struct cmd_parse_node *node, char **cause)
{
struct cmd_parse_node *child, *first, *text;
char *error = NULL;
if (cmd_parse_node_type(node) == CMD_PARSE_COMMAND) {
first = cmd_parse_node_first_child(node);
if (first != NULL &&
cmd_parse_node_type(first) == CMD_PARSE_STRING) {
text = cmd_parse_node_first_child(first);
if (text != NULL &&
cmd_parse_node_next(text) == NULL &&
cmd_parse_node_type(text) == CMD_PARSE_TEXT &&
cmd_find(cmd_parse_node_value(text), &error) ==
NULL) {
*cause = error;
return (-1);
}
}
}
child = cmd_parse_node_first_child(node);
while (child != NULL) {
if (window_customize_check_command_node(child, cause) != 0)
return (-1);
child = cmd_parse_node_next(child);
}
return (0);
}
static int
window_customize_check_command(struct cmd_parse_tree *tree, char **cause)
{
*cause = NULL;
return (window_customize_check_command_node(cmd_parse_root(tree), cause));
}
static int
window_customize_set_command_value(struct window_customize_itemdata *item,
const char *s, char **cause)
{
struct key_binding *bd;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
if (!window_customize_get_key(item, NULL, &bd))
return (-1);
pr = cmd_parse_from_string(s, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
*cause = pr->error;
tree = cmd_parse_from_string(s, NULL, cause);
if (tree == NULL)
return (-1);
if (window_customize_check_command(tree, cause) != 0) {
cmd_parse_free(tree);
return (-1);
case CMD_PARSE_SUCCESS:
break;
}
cmd_list_free(bd->cmdlist);
bd->cmdlist = pr->cmdlist;
cmd_parse_free(bd->cmd);
bd->cmd = tree;
return (0);
}
@@ -611,8 +649,8 @@ window_customize_key_is_changed(struct key_table *kt, struct key_binding *bd)
if (bd->note != NULL && strcmp(bd->note, default_bd->note) != 0)
return (1);
cmd = cmd_list_print(bd->cmdlist, 0);
default_cmd = cmd_list_print(default_bd->cmdlist, 0);
cmd = cmd_parse_print(bd->cmd, 0);
default_cmd = cmd_parse_print(default_bd->cmd, 0);
changed = (strcmp(cmd, default_cmd) != 0);
free(cmd);
free(default_cmd);
@@ -943,7 +981,7 @@ window_customize_build_keys(struct window_customize_modedata *data,
window_customize_key_tag(bd, 0), expanded, NULL, 0);
free(expanded);
tmp = cmd_list_print(bd->cmdlist, 0);
tmp = cmd_parse_print(bd->cmd, 0);
xasprintf(&text, "#[fg=themelightgrey]#[ignore]%s", tmp);
free(tmp);
mti = mode_tree_add(data->data, child, item,
@@ -1177,7 +1215,7 @@ window_customize_draw_key(__unused struct window_customize_modedata *data,
if (s->cy >= cy + sy - 1)
return;
cmd = cmd_list_print(bd->cmdlist, 0);
cmd = cmd_parse_print(bd->cmd, CMD_PARSE_PRINT_MULTILINE);
if (!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy), 0,
"Command: ", "%s", cmd)) {
free(cmd);
@@ -1185,7 +1223,8 @@ window_customize_draw_key(__unused struct window_customize_modedata *data,
}
default_bd = key_bindings_get_default(kt, bd->key);
if (default_bd != NULL) {
default_cmd = cmd_list_print(default_bd->cmdlist, 0);
default_cmd = cmd_parse_print(default_bd->cmd,
CMD_PARSE_PRINT_MULTILINE);
if (strcmp(cmd, default_cmd) != 0 &&
!window_customize_write_value(ctx, cx, sx, sy - (s->cy - cy),
0, "The default is: ", "%s", default_cmd)) {
@@ -2072,7 +2111,7 @@ window_customize_start_edit(struct window_customize_modedata *data,
if (!window_customize_get_key(item, NULL, &bd))
return;
if (strcmp(name, "Command") == 0) {
value = cmd_list_print(bd->cmdlist, 0);
value = cmd_parse_print(bd->cmd, 0);
edit_type = WINDOW_CUSTOMIZE_EDIT_KEY_COMMAND;
} else if (strcmp(name, "Note") == 0) {
if (bd->note == NULL)
@@ -2390,7 +2429,7 @@ window_customize_set_command_callback(struct client *c, void *itemdata,
struct window_customize_itemdata *item = itemdata;
struct window_customize_modedata *data = item->data;
struct key_binding *bd;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
char *error;
if (s == NULL || *s == '\0' || data->dead)
@@ -2398,17 +2437,16 @@ window_customize_set_command_callback(struct client *c, void *itemdata,
if (item == NULL || !window_customize_get_key(item, NULL, &bd))
return (PROMPT_CLOSE);
pr = cmd_parse_from_string(s, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
error = pr->error;
tree = cmd_parse_from_string(s, NULL, &error);
if (tree == NULL)
goto fail;
if (window_customize_check_command(tree, &error) != 0) {
cmd_parse_free(tree);
goto fail;
case CMD_PARSE_SUCCESS:
break;
}
cmd_list_free(bd->cmdlist);
bd->cmdlist = pr->cmdlist;
cmd_parse_free(bd->cmd);
bd->cmd = tree;
mode_tree_build(data->data);
mode_tree_draw(data->data);
data->wp->flags |= PANE_REDRAW;
@@ -2464,7 +2502,7 @@ window_customize_set_key(struct client *c,
bd->flags ^= KEY_BINDING_REPEAT;
else if (strcmp(s, "Command") == 0) {
xasprintf(&prompt, "(%s) ", key_string_lookup_key(key, 0));
value = cmd_list_print(bd->cmdlist, 0);
value = cmd_parse_print(bd->cmd, 0);
new_item = xcalloc(1, sizeof *new_item);
new_item->data = data;
@@ -2507,7 +2545,7 @@ window_customize_add_key_callback(struct client *c, void *itemdata,
struct window_customize_itemdata *item = itemdata;
struct window_customize_modedata *data = item->data;
key_code key;
struct cmd_parse_result *pr;
struct cmd_parse_tree *tree;
const char *command;
char *error, *keystr;
size_t keylen;
@@ -2540,15 +2578,14 @@ window_customize_add_key_callback(struct client *c, void *itemdata,
}
free(keystr);
pr = cmd_parse_from_string(command, NULL);
switch (pr->status) {
case CMD_PARSE_ERROR:
error = pr->error;
tree = cmd_parse_from_string(command, NULL, &error);
if (tree == NULL)
goto fail;
if (window_customize_check_command(tree, &error) != 0) {
cmd_parse_free(tree);
goto fail;
case CMD_PARSE_SUCCESS:
break;
}
key_bindings_add(item->table, key, NULL, 0, pr->cmdlist);
key_bindings_add(item->table, key, NULL, 0, tree);
mode_tree_build(data->data);
mode_tree_draw(data->data);
@@ -2612,7 +2649,7 @@ window_customize_reset_key(struct window_customize_modedata *data,
return;
dd = key_bindings_get_default(kt, bd->key);
if (dd != NULL && bd->cmdlist == dd->cmdlist)
if (dd != NULL && bd->cmd == dd->cmd)
return;
if (dd == NULL && item == mode_tree_get_current(data->data))
mode_tree_up(data->data, 0);
+19 -16
View File
@@ -62,7 +62,7 @@ struct window_panes_modedata {
struct event timer;
struct args_command_state *state;
struct cmd_parse_tree *tree;
u_int delay;
int ignore_keys;
int zoomed;
@@ -925,8 +925,16 @@ window_panes_init(struct window_mode_entry *wme, struct cmdq_item *item,
screen_init(&data->screen, sx, sy, 0);
data->screen.mode &= ~MODE_CURSOR;
data->state = args_make_commands_prepare(self, item, 0,
"select-pane -t \"%%%\"", 0, 0);
data->tree = args_to_commands(self, item, 0, "select-pane -t \"%%%\"",
0, &cause);
if (data->tree == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
screen_free(&data->screen);
free(data);
wme->data = NULL;
return (NULL);
}
if (args_has(args, 's')) {
data->source_session = source->s->id;
data->source_window = source->w->id;
@@ -973,8 +981,7 @@ window_panes_free(struct window_mode_entry *wme)
server_redraw_window_borders(w);
server_status_window(w);
if (data->state != NULL)
args_make_commands_free(data->state);
cmd_parse_free(data->tree);
window_panes_free_areas(data);
if (data->preview != NULL) {
screen_free(data->preview);
@@ -998,19 +1005,15 @@ window_panes_run_command(struct window_panes_modedata *data, struct client *c,
struct window_pane *wp)
{
struct cmdq_item *new_item;
struct cmd_list *cmdlist;
char *expanded, *error;
struct cmd_invoke_input ci = { 0 };
char *expanded;
xasprintf(&expanded, "%%%u", wp->id);
cmdlist = args_make_commands(data->state, 1, &expanded, &error);
if (cmdlist == NULL) {
cmdq_append(c, cmdq_get_error(error));
free(error);
} else {
new_item = cmdq_get_command(cmdlist, NULL);
cmdq_append(c, new_item);
cmd_list_free(cmdlist);
}
ci.argc = 1;
ci.argv = &expanded;
new_item = cmd_invoke_get(data->tree, NULL, &ci);
cmdq_append(c, new_item);
free(expanded);
}
+8 -3
View File
@@ -434,10 +434,11 @@ window_switch_run_command(struct window_switch_modedata *data, struct client *c)
struct cmd_find_state fs;
struct session *s;
struct winlink *wl;
struct cmd_parse_tree *tree;
struct cmdq_item *new_item;
char *target = NULL;
struct cmdq_state *state;
char *command, *error;
enum cmd_parse_status status;
if (data->matches_size == 0)
return (0);
@@ -469,13 +470,17 @@ window_switch_run_command(struct window_switch_modedata *data, struct client *c)
command = cmd_template_replace(data->command, target, 1);
if (command != NULL && *command != '\0') {
state = cmdq_new_state(&fs, NULL, 0);
status = cmd_parse_and_append(command, NULL, c, state, &error);
if (status == CMD_PARSE_ERROR) {
tree = cmd_parse_from_string(command, NULL, &error);
if (tree == NULL) {
if (c != NULL) {
*error = toupper((u_char)*error);
status_message_set(c, -1, 1, 0, 0, "%s", error);
}
free(error);
} else {
new_item = cmd_invoke_get(tree, state, NULL);
cmdq_append(c, new_item);
cmd_parse_free(tree);
}
cmdq_free_state(state);
}