mirror of
https://github.com/tmux/tmux.git
synced 2026-09-25 06:06:29 +00:00
Compare commits
63
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c624de9f3c | ||
|
|
cf3ab3cad9 | ||
|
|
c6f62289b0 | ||
|
|
b114b7e089 | ||
|
|
f85b015dc1 | ||
|
|
899b4b5471 | ||
|
|
16064a410e | ||
|
|
fcc7366e86 | ||
|
|
5d2b6542b6 | ||
|
|
66e6b2e1c3 | ||
|
|
baf71480d0 | ||
|
|
5838b45315 | ||
|
|
cfa755a961 | ||
|
|
82683707bc | ||
|
|
3ee24bde1a | ||
|
|
4f7bef5083 | ||
|
|
80b1f20e7d | ||
|
|
111a11ac6a | ||
|
|
1853c220a4 | ||
|
|
115002748b | ||
|
|
db8f9b4c52 | ||
|
|
4951eac7fa | ||
|
|
185ae656e0 | ||
|
|
ab3da84e34 | ||
|
|
bce30eefd3 | ||
|
|
1302bbde07 | ||
|
|
e935342702 | ||
|
|
6d56dd9e7b | ||
|
|
8ca7434d65 | ||
|
|
a9cff084c0 | ||
|
|
186f51b7ee | ||
|
|
298b35c8a9 | ||
|
|
7a1cc1d443 | ||
|
|
fefb926291 | ||
|
|
36273fde4f | ||
|
|
8ac29f6587 | ||
|
|
db9b54f232 | ||
|
|
54f5c82500 | ||
|
|
b3bb8bbd9f | ||
|
|
c92720e3b0 | ||
|
|
08500f844d | ||
|
|
a97912a4f8 | ||
|
|
91ea9e4beb | ||
|
|
b30f5f82e0 | ||
|
|
d4eca8d586 | ||
|
|
8908f50524 | ||
|
|
eb2e26f4cd | ||
|
|
9569c57fc3 | ||
|
|
b48e2ea8e9 | ||
|
|
f7cd6ab547 | ||
|
|
401793eba6 | ||
|
|
fcfa0b6db4 | ||
|
|
5f7a4b0f52 | ||
|
|
bcc2b7ad1e | ||
|
|
9e8cf6f0e2 | ||
|
|
b8a62979ac | ||
|
|
45f4000017 | ||
|
|
572b32766c | ||
|
|
e6ad44c8a3 | ||
|
|
96cd98560b | ||
|
|
ac23a47a5f | ||
|
|
22b6ad6fce | ||
|
|
efe2453eac |
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+43
-72
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Executable
+50
@@ -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
|
||||
Executable
+87
@@ -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
|
||||
Executable
+102
@@ -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
|
||||
Executable
+75
@@ -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
|
||||
Executable
+72
@@ -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
|
||||
Executable
+46
@@ -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
|
||||
Executable
+56
@@ -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
|
||||
Executable
+51
@@ -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
|
||||
Executable
+120
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 */
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user