aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.am2
-rw-r--r--cfg.c238
-rw-r--r--cmd-command-prompt.c32
-rw-r--r--cmd-confirm-before.c33
-rw-r--r--cmd-display-panes.c35
-rw-r--r--cmd-if-shell.c91
-rw-r--r--cmd-list.c28
-rw-r--r--cmd-parse.y1207
-rw-r--r--cmd-queue.c14
-rw-r--r--cmd-source-file.c10
-rw-r--r--cmd-string.c393
-rw-r--r--cmd.c188
-rw-r--r--control.c22
-rw-r--r--key-bindings.c13
-rw-r--r--menu.c35
-rw-r--r--mode-tree.c27
-rw-r--r--options.c19
-rw-r--r--tmux.1268
-rw-r--r--tmux.h64
19 files changed, 1820 insertions, 899 deletions
diff --git a/Makefile.am b/Makefile.am
index bfba99b6..9db08a02 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -94,6 +94,7 @@ dist_tmux_SOURCES = \
cmd-move-window.c \
cmd-new-session.c \
cmd-new-window.c \
+ cmd-parse.y \
cmd-paste-buffer.c \
cmd-pipe-pane.c \
cmd-queue.c \
@@ -119,7 +120,6 @@ dist_tmux_SOURCES = \
cmd-show-options.c \
cmd-source-file.c \
cmd-split-window.c \
- cmd-string.c \
cmd-swap-pane.c \
cmd-swap-window.c \
cmd-switch-client.c \
diff --git a/cfg.c b/cfg.c
index f4942b22..68c8f533 100644
--- a/cfg.c
+++ b/cfg.c
@@ -26,17 +26,6 @@
#include "tmux.h"
-/* Condition for %if, %elif, %else and %endif. */
-struct cfg_cond {
- size_t line; /* line number of %if */
- int met; /* condition was met */
- int skip; /* skip later %elif/%else */
- int saw_else; /* saw a %else */
-
- TAILQ_ENTRY(cfg_cond) entry;
-};
-TAILQ_HEAD(cfg_conds, cfg_cond);
-
struct client *cfg_client;
static char *cfg_file;
int cfg_finished;
@@ -85,9 +74,8 @@ start_cfg(void)
struct client *c;
/*
- * Configuration files are loaded without a client, so NULL is passed
- * into load_cfg() and commands run in the global queue with
- * item->client NULL.
+ * Configuration files are loaded without a client, so commands are run
+ * in the global queue with item->client NULL.
*
* However, we must block the initial client (but just the initial
* client) so that its command runs after the configuration is loaded.
@@ -102,11 +90,11 @@ start_cfg(void)
}
if (cfg_file == NULL)
- load_cfg(TMUX_CONF, NULL, NULL, CFG_QUIET, NULL);
+ load_cfg(TMUX_CONF, NULL, NULL, CMD_PARSE_QUIET, NULL);
if (cfg_file == NULL && (home = find_home()) != NULL) {
xasprintf(&cfg_file, "%s/.tmux.conf", home);
- flags = CFG_QUIET;
+ flags = CMD_PARSE_QUIET;
}
if (cfg_file != NULL)
load_cfg(cfg_file, NULL, NULL, flags, NULL);
@@ -114,214 +102,54 @@ start_cfg(void)
cmdq_append(NULL, cmdq_get_callback(cfg_done, NULL));
}
-static int
-cfg_check_cond(const char *path, size_t line, const char *p, int *skip,
- struct client *c, struct cmd_find_state *fs)
-{
- struct format_tree *ft;
- char *s;
- int result;
-
- while (isspace((u_char)*p))
- p++;
- if (p[0] == '\0') {
- cfg_add_cause("%s:%zu: invalid condition", path, line);
- *skip = 1;
- return (0);
- }
-
- ft = format_create(NULL, NULL, FORMAT_NONE, FORMAT_NOJOBS);
- if (fs != NULL)
- format_defaults(ft, c, fs->s, fs->wl, fs->wp);
- else
- format_defaults(ft, c, NULL, NULL, NULL);
- s = format_expand(ft, p);
- result = format_true(s);
- free(s);
- format_free(ft);
-
- *skip = result;
- return (result);
-}
-
-static void
-cfg_handle_if(const char *path, size_t line, struct cfg_conds *conds,
- const char *p, struct client *c, struct cmd_find_state *fs)
-{
- struct cfg_cond *cond;
- struct cfg_cond *parent = TAILQ_FIRST(conds);
-
- /*
- * Add a new condition. If a previous condition exists and isn't
- * currently met, this new one also can't be met.
- */
- cond = xcalloc(1, sizeof *cond);
- cond->line = line;
- if (parent == NULL || parent->met)
- cond->met = cfg_check_cond(path, line, p, &cond->skip, c, fs);
- else
- cond->skip = 1;
- cond->saw_else = 0;
- TAILQ_INSERT_HEAD(conds, cond, entry);
-}
-
-static void
-cfg_handle_elif(const char *path, size_t line, struct cfg_conds *conds,
- const char *p, struct client *c, struct cmd_find_state *fs)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * If a previous condition exists and wasn't met, check this
- * one instead and change the state.
- */
- if (cond == NULL || cond->saw_else)
- cfg_add_cause("%s:%zu: unexpected %%elif", path, line);
- else if (!cond->skip)
- cond->met = cfg_check_cond(path, line, p, &cond->skip, c, fs);
- else
- cond->met = 0;
-}
-
-static void
-cfg_handle_else(const char *path, size_t line, struct cfg_conds *conds)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * If a previous condition exists and wasn't met and wasn't already
- * %else, use this one instead.
- */
- if (cond == NULL || cond->saw_else) {
- cfg_add_cause("%s:%zu: unexpected %%else", path, line);
- return;
- }
- cond->saw_else = 1;
- cond->met = !cond->skip;
- cond->skip = 1;
-}
-
-static void
-cfg_handle_endif(const char *path, size_t line, struct cfg_conds *conds)
-{
- struct cfg_cond *cond = TAILQ_FIRST(conds);
-
- /*
- * Remove previous condition if one exists.
- */
- if (cond == NULL) {
- cfg_add_cause("%s:%zu: unexpected %%endif", path, line);
- return;
- }
- TAILQ_REMOVE(conds, cond, entry);
- free(cond);
-}
-
-static void
-cfg_handle_directive(const char *p, const char *path, size_t line,
- struct cfg_conds *conds, struct client *c, struct cmd_find_state *fs)
-{
- int n = 0;
-
- while (p[n] != '\0' && !isspace((u_char)p[n]))
- n++;
- if (strncmp(p, "%if", n) == 0)
- cfg_handle_if(path, line, conds, p + n, c, fs);
- else if (strncmp(p, "%elif", n) == 0)
- cfg_handle_elif(path, line, conds, p + n, c, fs);
- else if (strcmp(p, "%else") == 0)
- cfg_handle_else(path, line, conds);
- else if (strcmp(p, "%endif") == 0)
- cfg_handle_endif(path, line, conds);
- else
- cfg_add_cause("%s:%zu: invalid directive: %s", path, line, p);
-}
-
int
load_cfg(const char *path, struct client *c, struct cmdq_item *item, int flags,
struct cmdq_item **new_item)
{
FILE *f;
- const char delim[3] = { '\\', '\\', '\0' };
- u_int found = 0;
- size_t line = 0;
- char *buf, *cause1, *p, *q;
- struct cmd_list *cmdlist;
+ struct cmd_parse_input pi;
+ struct cmd_parse_result *pr;
struct cmdq_item *new_item0;
- struct cfg_cond *cond, *cond1;
- struct cfg_conds conds;
- struct cmd_find_state *fs = NULL;
- struct client *fc = NULL;
-
- if (item != NULL) {
- fs = &item->target;
- fc = cmd_find_client(item, NULL, 1);
- }
- TAILQ_INIT(&conds);
+ if (new_item != NULL)
+ *new_item = NULL;
log_debug("loading %s", path);
if ((f = fopen(path, "rb")) == NULL) {
- if (errno == ENOENT && (flags & CFG_QUIET))
+ if (errno == ENOENT && (flags & CMD_PARSE_QUIET))
return (0);
cfg_add_cause("%s: %s", path, strerror(errno));
return (-1);
}
- while ((buf = fparseln(f, NULL, &line, delim, 0)) != NULL) {
- log_debug("%s: %s", path, buf);
-
- p = buf;
- while (isspace((u_char)*p))
- p++;
- if (*p == '\0') {
- free(buf);
- continue;
- }
- q = p + strlen(p) - 1;
- while (q != p && isspace((u_char)*q))
- *q-- = '\0';
-
- if (*p == '%') {
- cfg_handle_directive(p, path, line, &conds, fc, fs);
- continue;
- }
- cond = TAILQ_FIRST(&conds);
- if (cond != NULL && !cond->met)
- continue;
-
- cmdlist = cmd_string_parse(p, path, line, &cause1);
- if (cmdlist == NULL) {
- free(buf);
- if (cause1 == NULL)
- continue;
- cfg_add_cause("%s:%zu: %s", path, line, cause1);
- free(cause1);
- continue;
- }
- free(buf);
-
- new_item0 = cmdq_get_command(cmdlist, NULL, NULL, 0);
- if (item != NULL) {
- cmdq_insert_after(item, new_item0);
- item = new_item0;
- } else
- cmdq_append(c, new_item0);
- cmd_list_free(cmdlist);
-
- found++;
- }
- fclose(f);
+ memset(&pi, 0, sizeof pi);
+ pi.flags = flags;
+ pi.file = path;
- TAILQ_FOREACH_REVERSE_SAFE(cond, &conds, cfg_conds, entry, cond1) {
- cfg_add_cause("%s:%zu: unterminated %%if", path, cond->line);
- TAILQ_REMOVE(&conds, cond, entry);
- free(cond);
+ pr = cmd_parse_from_file(f, &pi);
+ fclose(f);
+ if (pr->status == CMD_PARSE_EMPTY)
+ return (0);
+ if (pr->status == CMD_PARSE_ERROR) {
+ cfg_add_cause("%s", pr->error);
+ free(pr->error);
+ return (-1);
+ }
+ if (flags & CMD_PARSE_PARSEONLY) {
+ cmd_list_free(pr->cmdlist);
+ return (0);
}
+ new_item0 = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ if (item != NULL)
+ cmdq_insert_after(item, new_item0);
+ else
+ cmdq_append(c, new_item0);
+ cmd_list_free(pr->cmdlist);
+
if (new_item != NULL)
- *new_item = item;
- return (found);
+ *new_item = new_item0;
+ return (0);
}
void
diff --git a/cmd-command-prompt.c b/cmd-command-prompt.c
index a3cc22c8..603ddb0a 100644
--- a/cmd-command-prompt.c
+++ b/cmd-command-prompt.c
@@ -134,10 +134,10 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
int done)
{
struct cmd_command_prompt_cdata *cdata = data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cause, *new_template, *prompt, *ptr;
+ char *new_template, *prompt, *ptr;
char *input = NULL;
+ struct cmd_parse_result *pr;
if (s == NULL)
return (0);
@@ -164,20 +164,22 @@ cmd_command_prompt_callback(struct client *c, void *data, const char *s,
return (1);
}
- cmdlist = cmd_string_parse(new_template, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
-
- if (new_item != NULL)
+ pr = cmd_parse_from_string(new_template, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
+ }
if (!done)
free(new_template);
diff --git a/cmd-confirm-before.c b/cmd-confirm-before.c
index 4017a6f9..be21a78b 100644
--- a/cmd-confirm-before.c
+++ b/cmd-confirm-before.c
@@ -87,32 +87,33 @@ cmd_confirm_before_callback(struct client *c, void *data, const char *s,
__unused int done)
{
struct cmd_confirm_before_data *cdata = data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cause;
+ struct cmd_parse_result *pr;
if (c->flags & CLIENT_DEAD)
return (0);
if (s == NULL || *s == '\0')
return (0);
- if (tolower((u_char) s[0]) != 'y' || s[1] != '\0')
+ if (tolower((u_char)s[0]) != 'y' || s[1] != '\0')
return (0);
- cmdlist = cmd_string_parse(cdata->cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
-
- if (new_item != NULL)
+ pr = cmd_parse_from_string(cdata->cmd, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
+ }
return (0);
}
diff --git a/cmd-display-panes.c b/cmd-display-panes.c
index 6e331ae1..aeeb6936 100644
--- a/cmd-display-panes.c
+++ b/cmd-display-panes.c
@@ -197,11 +197,11 @@ static int
cmd_display_panes_key(struct client *c, struct key_event *event)
{
struct cmd_display_panes_data *cdata = c->overlay_data;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cmd, *expanded, *cause;
+ char *cmd, *expanded;
struct window *w = c->session->curw->window;
struct window_pane *wp;
+ struct cmd_parse_result *pr;
if (event->key < '0' || event->key > '9')
return (1);
@@ -214,22 +214,21 @@ cmd_display_panes_key(struct client *c, struct key_event *event)
xasprintf(&expanded, "%%%u", wp->id);
cmd = cmd_template_replace(cdata->command, expanded, 1);
- cmdlist = cmd_string_parse(cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
- if (new_item != NULL) {
- if (cdata->item != NULL)
- cmdq_insert_after(cdata->item, new_item);
- else
- cmdq_append(c, new_item);
+ pr = cmd_parse_from_string(cmd, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
+ cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
}
free(cmd);
diff --git a/cmd-if-shell.c b/cmd-if-shell.c
index 480912df..40e2b1c3 100644
--- a/cmd-if-shell.c
+++ b/cmd-if-shell.c
@@ -49,8 +49,7 @@ const struct cmd_entry cmd_if_shell_entry = {
};
struct cmd_if_shell_data {
- char *file;
- u_int line;
+ struct cmd_parse_input input;
char *cmd_if;
char *cmd_else;
@@ -64,51 +63,62 @@ static enum cmd_retval
cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
{
struct args *args = self->args;
- struct cmdq_shared *shared = item->shared;
+ struct mouse_event *m = &item->shared->mouse;
struct cmd_if_shell_data *cdata;
- char *shellcmd, *cmd, *cause;
- struct cmd_list *cmdlist;
+ char *shellcmd, *cmd;
struct cmdq_item *new_item;
struct client *c = cmd_find_client(item, NULL, 1);
struct session *s = item->target.s;
struct winlink *wl = item->target.wl;
struct window_pane *wp = item->target.wp;
+ struct cmd_parse_input pi;
+ struct cmd_parse_result *pr;
shellcmd = format_single(item, args->argv[0], c, s, wl, wp);
if (args_has(args, 'F')) {
- cmd = NULL;
if (*shellcmd != '0' && *shellcmd != '\0')
cmd = args->argv[1];
else if (args->argc == 3)
cmd = args->argv[2];
+ else
+ cmd = NULL;
free(shellcmd);
if (cmd == NULL)
return (CMD_RETURN_NORMAL);
- cmdlist = cmd_string_parse(cmd, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL) {
- cmdq_error(item, "%s", cause);
- free(cause);
- }
+
+ memset(&pi, 0, sizeof pi);
+ if (self->file != NULL)
+ pi.file = self->file;
+ pi.line = self->line;
+ pi.item = item;
+ pi.c = c;
+ cmd_find_copy_state(&pi.fs, &item->target);
+
+ pr = cmd_parse_from_string(cmd, &pi);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ break;
+ case CMD_PARSE_ERROR:
+ cmdq_error(item, "%s", pr->error);
+ free(pr->error);
return (CMD_RETURN_ERROR);
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, m, 0);
+ cmdq_insert_after(item, new_item);
+ cmd_list_free(pr->cmdlist);
+ break;
}
- new_item = cmdq_get_command(cmdlist, NULL, &shared->mouse, 0);
- cmdq_insert_after(item, new_item);
- cmd_list_free(cmdlist);
return (CMD_RETURN_NORMAL);
}
cdata = xcalloc(1, sizeof *cdata);
- if (self->file != NULL) {
- cdata->file = xstrdup(self->file);
- cdata->line = self->line;
- }
cdata->cmd_if = xstrdup(args->argv[1]);
if (args->argc == 3)
cdata->cmd_else = xstrdup(args->argv[2]);
else
cdata->cmd_else = NULL;
+ memcpy(&cdata->mouse, m, sizeof cdata->mouse);
cdata->client = item->client;
if (cdata->client != NULL)
@@ -118,7 +128,16 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
cdata->item = item;
else
cdata->item = NULL;
- memcpy(&cdata->mouse, &shared->mouse, sizeof cdata->mouse);
+
+ memset(&cdata->input, 0, sizeof cdata->input);
+ if (self->file != NULL)
+ cdata->input.file = xstrdup(self->file);
+ cdata->input.line = self->line;
+ cdata->input.item = cdata->item;
+ cdata->input.c = c;
+ if (cdata->input.c != NULL)
+ cdata->input.c->references++;
+ cmd_find_copy_state(&cdata->input.fs, &item->target);
if (job_run(shellcmd, s, server_client_get_cwd(item->client, s), NULL,
cmd_if_shell_callback, cmd_if_shell_free, cdata, 0) == NULL) {
@@ -139,11 +158,11 @@ cmd_if_shell_callback(struct job *job)
{
struct cmd_if_shell_data *cdata = job_get_data(job);
struct client *c = cdata->client;
- struct cmd_list *cmdlist;
+ struct mouse_event *m = &cdata->mouse;
struct cmdq_item *new_item;
- char *cause, *cmd, *file = cdata->file;
- u_int line = cdata->line;
+ char *cmd;
int status;
+ struct cmd_parse_result *pr;
status = job_get_status(job);
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
@@ -153,17 +172,20 @@ cmd_if_shell_callback(struct job *job)
if (cmd == NULL)
goto out;
- cmdlist = cmd_string_parse(cmd, file, line, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL && cdata->item != NULL)
- cmdq_error(cdata->item, "%s", cause);
- free(cause);
+ pr = cmd_parse_from_string(cmd, &cdata->input);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
new_item = NULL;
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, &cdata->mouse, 0);
- cmd_list_free(cmdlist);
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, m, 0);
+ cmd_list_free(pr->cmdlist);
+ break;
}
-
if (new_item != NULL) {
if (cdata->item == NULL)
cmdq_append(c, new_item);
@@ -187,6 +209,9 @@ cmd_if_shell_free(void *data)
free(cdata->cmd_else);
free(cdata->cmd_if);
- free(cdata->file);
+ if (cdata->input.c != NULL)
+ server_client_unref(cdata->input.c);
+ free((void *)cdata->input.file);
+
free(cdata);
}
diff --git a/cmd-list.c b/cmd-list.c
index 282533cf..ead0fb61 100644
--- a/cmd-list.c
+++ b/cmd-list.c
@@ -23,17 +23,39 @@
#include "tmux.h"
-static struct cmd_list *
+static u_int cmd_list_next_group = 1;
+
+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++;
TAILQ_INIT(&cmdlist->list);
return (cmdlist);
}
+void
+cmd_list_append(struct cmd_list *cmdlist, struct cmd *cmd)
+{
+ cmd->group = cmdlist->group;
+ TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
+}
+
+void
+cmd_list_move(struct cmd_list *cmdlist, struct cmd_list *from)
+{
+ struct cmd *cmd, *cmd1;
+
+ TAILQ_FOREACH_SAFE(cmd, &from->list, qentry, cmd1) {
+ TAILQ_REMOVE(&from->list, cmd, qentry);
+ TAILQ_INSERT_TAIL(&cmdlist->list, cmd, qentry);
+ }
+ cmdlist->group = cmd_list_next_group++;
+}
+
struct cmd_list *
cmd_list_parse(int argc, char **argv, const char *file, u_int line,
char **cause)
@@ -100,9 +122,7 @@ cmd_list_free(struct cmd_list *cmdlist)
TAILQ_FOREACH_SAFE(cmd, &cmdlist->list, qentry, cmd1) {
TAILQ_REMOVE(&cmdlist->list, cmd, qentry);
- args_free(cmd->args);
- free(cmd->file);
- free(cmd);
+ cmd_free(cmd);
}
free(cmdlist);
diff --git a/cmd-parse.y b/cmd-parse.y
new file mode 100644
index 00000000..8b8f33ab
--- /dev/null
+++ b/cmd-parse.y
@@ -0,0 +1,1207 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+%{
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <pwd.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+static int yylex(void);
+static int yyparse(void);
+static int printflike(1,2) yyerror(const char *, ...);
+
+static char *yylex_token(int);
+static char *yylex_format(void);
+
+struct cmd_parse_scope {
+ int flag;
+ TAILQ_ENTRY (cmd_parse_scope) entry;
+};
+
+struct cmd_parse_command {
+ char *name;
+ u_int line;
+
+ int argc;
+ char **argv;
+
+ TAILQ_ENTRY(cmd_parse_command) entry;
+};
+TAILQ_HEAD(cmd_parse_commands, cmd_parse_command);
+
+struct cmd_parse_state {
+ FILE *f;
+ int eof;
+ struct cmd_parse_input *input;
+ u_int escapes;
+
+ char *error;
+ struct cmd_parse_commands commands;
+
+ struct cmd_parse_scope *scope;
+ TAILQ_HEAD(, cmd_parse_scope) stack;
+};
+static struct cmd_parse_state parse_state;
+
+static char *cmd_parse_get_error(const char *, u_int, const char *);
+static char *cmd_parse_get_strerror(const char *, u_int);
+static void cmd_parse_free_command(struct cmd_parse_command *);
+static void cmd_parse_free_commands(struct cmd_parse_commands *);
+
+%}
+
+%union
+{
+ char *token;
+ struct {
+ int argc;
+ char **argv;
+ } arguments;
+ int flag;
+ struct {
+ int flag;
+ struct cmd_parse_commands commands;
+ } elif;
+ struct cmd_parse_commands commands;
+ struct cmd_parse_command *command;
+}
+
+%token ERROR
+%token IF
+%token ELSE
+%token ELIF
+%token ENDIF
+%token <token> FORMAT TOKEN EQUALS
+
+%type <token> argument expanded
+%type <arguments> arguments
+%type <flag> if_open if_elif
+%type <elif> elif elif1
+%type <commands> statements statement commands condition condition1
+%type <command> command
+
+%%
+
+lines : /* empty */
+ | statements
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_CONCAT(&ps->commands, &$1, entry);
+ }
+
+statements : statement '\n'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ }
+ | statements statement '\n'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_CONCAT(&$$, &$2, entry);
+ }
+
+
+statement : condition
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_CONCAT(&$$, &$1, entry);
+ else
+ cmd_parse_free_commands(&$1);
+ }
+ | assignment
+ {
+ TAILQ_INIT(&$$);
+ }
+ | commands
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_CONCAT(&$$, &$1, entry);
+ else
+ cmd_parse_free_commands(&$1);
+ }
+
+expanded : FORMAT
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ struct format_tree *ft;
+ struct client *c = pi->c;
+ struct cmd_find_state *fs;
+ int flags = FORMAT_NOJOBS;
+
+ if (cmd_find_valid_state(&pi->fs))
+ fs = &pi->fs;
+ else
+ fs = NULL;
+ ft = format_create(NULL, pi->item, FORMAT_NONE, flags);
+ if (fs != NULL)
+ format_defaults(ft, c, fs->s, fs->wl, fs->wp);
+ else
+ format_defaults(ft, c, NULL, NULL, NULL);
+
+ $$ = format_expand(ft, $1);
+ format_free(ft);
+ free($1);
+ }
+
+assignment : /* empty */
+ | EQUALS
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ int flags = ps->input->flags;
+
+ if ((~flags & CMD_PARSE_PARSEONLY) &&
+ (ps->scope == NULL || ps->scope->flag))
+ environ_put(global_environ, $1);
+ free($1);
+ }
+
+if_open : IF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ if (ps->scope != NULL)
+ TAILQ_INSERT_HEAD(&ps->stack, ps->scope, entry);
+ ps->scope = scope;
+ }
+
+if_else : ELSE
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ scope->flag = !ps->scope->flag;
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_elif : ELIF expanded
+ {
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_scope *scope;
+
+ scope = xmalloc(sizeof *scope);
+ $$ = scope->flag = format_true($2);
+ free($2);
+
+ free(ps->scope);
+ ps->scope = scope;
+ }
+
+if_close : ENDIF
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ free(ps->scope);
+ ps->scope = TAILQ_FIRST(&ps->stack);
+ if (ps->scope != NULL)
+ TAILQ_REMOVE(&ps->stack, ps->scope, entry);
+ }
+
+condition : if_open '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1)
+ TAILQ_CONCAT(&$$, &$3, entry);
+ else
+ cmd_parse_free_commands(&$3);
+ }
+ | if_open '\n' statements if_else '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$6);
+ } else {
+ TAILQ_CONCAT(&$$, &$6, entry);
+ cmd_parse_free_commands(&$3);
+ }
+ }
+ | if_open '\n' statements elif if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ } else if ($4.flag) {
+ TAILQ_CONCAT(&$$, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ } else {
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$4.commands);
+ }
+ }
+ | if_open '\n' statements elif if_else '\n' statements if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ cmd_parse_free_commands(&$7);
+ } else if ($4.flag) {
+ TAILQ_CONCAT(&$$, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$7);
+ } else {
+ TAILQ_CONCAT(&$$, &$7, entry);
+ cmd_parse_free_commands(&$3);
+ cmd_parse_free_commands(&$4.commands);
+ }
+ }
+
+elif : if_elif '\n' statements
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1)
+ TAILQ_CONCAT(&$$.commands, &$3, entry);
+ else
+ cmd_parse_free_commands(&$3);
+ $$.flag = $1;
+ }
+ | if_elif '\n' statements elif
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1) {
+ $$.flag = 1;
+ TAILQ_CONCAT(&$$.commands, &$3, entry);
+ cmd_parse_free_commands(&$4.commands);
+ } else {
+ $$.flag = $4.flag;
+ TAILQ_CONCAT(&$$.commands, &$4.commands, entry);
+ cmd_parse_free_commands(&$3);
+ }
+ }
+
+
+commands : command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag)
+ TAILQ_INSERT_TAIL(&$$, $1, entry);
+ else
+ cmd_parse_free_command($1);
+ }
+ | commands ';'
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ }
+ | commands ';' condition1
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_CONCAT(&$$, &$3, entry);
+ }
+ | commands ';' command
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ TAILQ_INIT(&$$);
+ if (ps->scope == NULL || ps->scope->flag) {
+ TAILQ_CONCAT(&$$, &$1, entry);
+ TAILQ_INSERT_TAIL(&$$, $3, entry);
+ } else {
+ cmd_parse_free_commands(&$1);
+ cmd_parse_free_command($3);
+ }
+ }
+ | condition1
+ {
+ TAILQ_INIT(&$$);
+ TAILQ_CONCAT(&$$, &$1, entry);
+ }
+
+command : assignment TOKEN
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ $$ = xcalloc(1, sizeof *$$);
+ $$->name = $2;
+ $$->line = ps->input->line;
+
+ }
+ | assignment TOKEN arguments
+ {
+ struct cmd_parse_state *ps = &parse_state;
+
+ $$ = xcalloc(1, sizeof *$$);
+ $$->name = $2;
+ $$->line = ps->input->line;
+
+ $$->argc = $3.argc;
+ $$->argv = $3.argv;
+ }
+
+condition1 : if_open commands if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1)
+ TAILQ_CONCAT(&$$, &$2, entry);
+ else
+ cmd_parse_free_commands(&$2);
+ }
+ | if_open commands if_else commands if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$2, entry);
+ cmd_parse_free_commands(&$4);
+ } else {
+ TAILQ_CONCAT(&$$, &$4, entry);
+ cmd_parse_free_commands(&$2);
+ }
+ }
+ | if_open commands elif1 if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$2, entry);
+ cmd_parse_free_commands(&$3.commands);
+ } else if ($3.flag) {
+ TAILQ_CONCAT(&$$, &$3.commands, entry);
+ cmd_parse_free_commands(&$2);
+ } else {
+ cmd_parse_free_commands(&$2);
+ cmd_parse_free_commands(&$3.commands);
+ }
+ }
+ | if_open commands elif1 if_else commands if_close
+ {
+ TAILQ_INIT(&$$);
+ if ($1) {
+ TAILQ_CONCAT(&$$, &$2, entry);
+ cmd_parse_free_commands(&$3.commands);
+ cmd_parse_free_commands(&$5);
+ } else if ($3.flag) {
+ TAILQ_CONCAT(&$$, &$3.commands, entry);
+ cmd_parse_free_commands(&$2);
+ cmd_parse_free_commands(&$5);
+ } else {
+ TAILQ_CONCAT(&$$, &$5, entry);
+ cmd_parse_free_commands(&$2);
+ cmd_parse_free_commands(&$3.commands);
+ }
+
+ }
+
+elif1 : if_elif commands
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1)
+ TAILQ_CONCAT(&$$.commands, &$2, entry);
+ else
+ cmd_parse_free_commands(&$2);
+ $$.flag = $1;
+ }
+ | if_elif commands elif1
+ {
+ TAILQ_INIT(&$$.commands);
+ if ($1) {
+ $$.flag = 1;
+ TAILQ_CONCAT(&$$.commands, &$2, entry);
+ cmd_parse_free_commands(&$3.commands);
+ } else {
+ $$.flag = $3.flag;
+ TAILQ_CONCAT(&$$.commands, &$3.commands, entry);
+ cmd_parse_free_commands(&$2);
+ }
+ }
+
+arguments : argument
+ {
+ $$.argc = 1;
+ $$.argv = xreallocarray(NULL, 1, sizeof *$$.argv);
+
+ $$.argv[0] = $1;
+ }
+ | argument arguments
+ {
+ cmd_prepend_argv(&$2.argc, &$2.argv, $1);
+ free($1);
+ $$ = $2;
+ }
+
+argument : TOKEN
+ {
+ $$ = $1;
+ }
+ | EQUALS
+ {
+ $$ = $1;
+ }
+
+%%
+
+static char *
+cmd_parse_get_error(const char *file, u_int line, const char *error)
+{
+ char *s;
+
+ if (file == NULL)
+ s = xstrdup(error);
+ else
+ xasprintf (&s, "%s:%u: %s", file, line, error);
+ return (s);
+}
+
+static char *
+cmd_parse_get_strerror(const char *file, u_int line)
+{
+ return (cmd_parse_get_error(file, line, strerror(errno)));
+}
+
+static void
+cmd_parse_free_command(struct cmd_parse_command *cmd)
+{
+ free(cmd->name);
+ cmd_free_argv(cmd->argc, cmd->argv);
+ free(cmd);
+}
+
+static void
+cmd_parse_free_commands(struct cmd_parse_commands *cmds)
+{
+ struct cmd_parse_command *cmd, *cmd1;
+
+ TAILQ_FOREACH_SAFE(cmd, cmds, entry, cmd1) {
+ TAILQ_REMOVE(cmds, cmd, entry);
+ cmd_parse_free_command(cmd);
+ }
+}
+
+static struct cmd_parse_commands *
+cmd_parse_run_parser(FILE *f, struct cmd_parse_input *pi, char **cause)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_commands *cmds;
+ struct cmd_parse_scope *scope, *scope1;
+ int retval;
+
+ memset(ps, 0, sizeof *ps);
+
+ ps->f = f;
+ ps->eof = 0;
+ ps->input = pi;
+
+ TAILQ_INIT(&ps->commands);
+ TAILQ_INIT(&ps->stack);
+
+ retval = yyparse();
+ TAILQ_FOREACH_SAFE(scope, &ps->stack, entry, scope1) {
+ TAILQ_REMOVE(&ps->stack, scope, entry);
+ free(scope);
+ }
+ if (retval != 0) {
+ *cause = ps->error;
+ return (NULL);
+ }
+
+ cmds = xmalloc(sizeof *cmds);
+ TAILQ_INIT(cmds);
+ TAILQ_CONCAT(cmds, &ps->commands, entry);
+ return (cmds);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_file(FILE *f, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_input input;
+ struct cmd_parse_commands *cmds, *cmds2;
+ struct cmd_parse_command *cmd, *cmd2, *next, *next2, *after;
+ u_int line = UINT_MAX;
+ int i;
+ struct cmd_list *cmdlist = NULL, *result;
+ struct cmd *add;
+ char *alias, *cause, *s;
+
+ if (pi == NULL) {
+ memset(&input, 0, sizeof input);
+ pi = &input;
+ }
+ memset(&pr, 0, sizeof pr);
+
+ /*
+ * Parse the file into a list of commands.
+ */
+ cmds = cmd_parse_run_parser(f, pi, &cause);
+ if (cmds == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ return (&pr);
+ }
+ if (TAILQ_EMPTY(cmds)) {
+ free(cmds);
+ pr.status = CMD_PARSE_EMPTY;
+ return (&pr);
+ }
+
+ /*
+ * Walk the commands and expand any aliases. Each alias is parsed
+ * individually to a new command list, any trailing arguments appended
+ * to the last command, and all commands inserted into the original
+ * command list.
+ */
+ TAILQ_FOREACH_SAFE(cmd, cmds, entry, next) {
+ alias = cmd_get_alias(cmd->name);
+ if (alias == NULL)
+ continue;
+
+ line = cmd->line;
+ log_debug("%s: %u %s = %s", __func__, line, cmd->name, alias);
+
+ f = fmemopen(alias, strlen(alias), "r");
+ if (f == NULL) {
+ free(alias);
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cmd_parse_get_strerror(pi->file, line);
+ goto out;
+ }
+ pi->line = line;
+ cmds2 = cmd_parse_run_parser(f, pi, &cause);
+ fclose(f);
+ free(alias);
+ if (cmds2 == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cause;
+ goto out;
+ }
+
+ cmd2 = TAILQ_LAST(cmds2, cmd_parse_commands);
+ if (cmd2 == NULL) {
+ TAILQ_REMOVE(cmds, cmd, entry);
+ cmd_parse_free_command(cmd);
+ continue;
+ }
+ for (i = 0; i < cmd->argc; i++)
+ cmd_append_argv(&cmd2->argc, &cmd2->argv, cmd->argv[i]);
+
+ after = cmd;
+ TAILQ_FOREACH_SAFE(cmd2, cmds2, entry, next2) {
+ cmd2->line = line;
+ TAILQ_REMOVE(cmds2, cmd2, entry);
+ TAILQ_INSERT_AFTER(cmds, after, cmd2, entry);
+ after = cmd2;
+ }
+ cmd_parse_free_commands(cmds2);
+
+ TAILQ_REMOVE(cmds, cmd, entry);
+ cmd_parse_free_command(cmd);
+ }
+
+ /*
+ * Parse each command into a command list. Create a new command list
+ * for each line so they get a new group (so the queue knows which ones
+ * to remove if a command fails when executed).
+ */
+ result = cmd_list_new();
+ TAILQ_FOREACH(cmd, cmds, entry) {
+ log_debug("%s: %u %s", __func__, cmd->line, cmd->name);
+ cmd_log_argv(cmd->argc, cmd->argv, __func__);
+
+ if (cmdlist == NULL || cmd->line != line) {
+ if (cmdlist != NULL) {
+ cmd_list_move(result, cmdlist);
+ cmd_list_free(cmdlist);
+ }
+ cmdlist = cmd_list_new();
+ }
+ line = cmd->line;
+
+ cmd_prepend_argv(&cmd->argc, &cmd->argv, cmd->name);
+ add = cmd_parse(cmd->argc, cmd->argv, pi->file, line, &cause);
+ if (add == NULL) {
+ cmd_list_free(result);
+ pr.status = CMD_PARSE_ERROR;
+ pr.error = cmd_parse_get_error(pi->file, line, cause);
+ free(cause);
+ goto out;
+ }
+ cmd_list_append(cmdlist, add);
+ }
+ if (cmdlist != NULL) {
+ cmd_list_move(result, cmdlist);
+ cmd_list_free(cmdlist);
+ }
+
+ s = cmd_list_print(result);
+ log_debug("%s: %s", __func__, s);
+ free(s);
+
+ pr.status = CMD_PARSE_SUCCESS;
+ pr.cmdlist = result;
+
+out:
+ cmd_parse_free_commands(cmds);
+ free(cmds);
+
+ return (&pr);
+}
+
+struct cmd_parse_result *
+cmd_parse_from_string(const char *s, struct cmd_parse_input *pi)
+{
+ static struct cmd_parse_result pr;
+ struct cmd_parse_result *prp;
+ FILE *f;
+
+ if (*s == '\0') {
+ pr.status = CMD_PARSE_EMPTY;
+ pr.cmdlist = NULL;
+ pr.error = NULL;
+ return (&pr);
+ }
+
+ f = fmemopen((void *)s, strlen(s), "r");
+ if (f == NULL) {
+ pr.status = CMD_PARSE_ERROR;
+ pr.cmdlist = NULL;
+ pr.error = cmd_parse_get_strerror(pi->file, pi->line);
+ return (NULL);
+ }
+ prp = cmd_parse_from_file(f, pi);
+ fclose(f);
+ return (prp);
+}
+
+static int printflike(1, 2)
+yyerror(const char *fmt, ...)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct cmd_parse_input *pi = ps->input;
+ va_list ap;
+ char *error;
+
+ if (ps->error != NULL)
+ return (0);
+
+ va_start(ap, fmt);
+ xvasprintf(&error, fmt, ap);
+ va_end(ap);
+
+ ps->error = cmd_parse_get_error(pi->file, pi->line, error);
+ free(error);
+ return (0);
+}
+
+static int
+yylex_is_var(char ch, int first)
+{
+ if (ch == '=')
+ return (0);
+ if (first && isdigit((u_char)ch))
+ return (0);
+ return (isalnum((u_char)ch) || ch == '_');
+}
+
+static void
+yylex_append(char **buf, size_t *len, const char *add, size_t addlen)
+{
+ if (addlen > SIZE_MAX - 1 || *len > SIZE_MAX - 1 - addlen)
+ fatalx("buffer is too big");
+ *buf = xrealloc(*buf, (*len) + 1 + addlen);
+ memcpy((*buf) + *len, add, addlen);
+ (*len) += addlen;
+}
+
+static void
+yylex_append1(char **buf, size_t *len, char add)
+{
+ yylex_append(buf, len, &add, 1);
+}
+
+static int
+yylex_getc(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ int ch;
+
+ if (ps->escapes != 0) {
+ ps->escapes--;
+ return ('\\');
+ }
+ for (;;) {
+ ch = getc(ps->f);
+ if (ch == '\\') {
+ ps->escapes++;
+ continue;
+ }
+ if (ch == '\n' && (ps->escapes % 2) == 1) {
+ ps->input->line++;
+ ps->escapes--;
+ continue;
+ }
+
+ if (ps->escapes != 0) {
+ ungetc(ch, ps->f);
+ ps->escapes--;
+ return ('\\');
+ }
+ return (ch);
+ }
+}
+
+static char *
+yylex_get_word(int ch)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ char *buf;
+ size_t len;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ do
+ yylex_append1(&buf, &len, ch);
+ while ((ch = yylex_getc()) != EOF && strchr(" \t\n", ch) == NULL);
+ ungetc(ch, ps->f);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+}
+
+static int
+yylex(void)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ char *token, *cp;
+ int ch, next;
+
+ for (;;) {
+ ch = yylex_getc();
+
+ if (ch == EOF) {
+ /*
+ * Ensure every file or string is terminated by a
+ * newline. This keeps the parser simpler and avoids
+ * having to add a newline to each string.
+ */
+ if (ps->eof)
+ break;
+ ps->eof = 1;
+ return ('\n');
+ }
+
+ if (ch == ' ' || ch == '\t') {
+ /*
+ * Ignore whitespace.
+ */
+ continue;
+ }
+
+ if (ch == '\n') {
+ /*
+ * End of line. Update the line number.
+ */
+ ps->input->line++;
+ return ('\n');
+ }
+
+ if (ch == ';') {
+ /*
+ * A semicolon is itself.
+ */
+ return (';');
+ }
+
+ if (ch == '#') {
+ /*
+ * #{ opens a format; anything else is a comment,
+ * ignore up to the end of the line.
+ */
+ next = yylex_getc();
+ if (next == '{') {
+ yylval.token = yylex_format();
+ if (yylval.token == NULL)
+ return (ERROR);
+ return (FORMAT);
+ }
+ while (next != '\n' && next != EOF)
+ next = yylex_getc();
+ if (next == '\n') {
+ ps->input->line++;
+ return ('\n');
+ }
+ continue;
+ }
+
+ if (ch == '%') {
+ /*
+ * % is a condition unless it is alone, then it is a
+ * token.
+ */
+ yylval.token = yylex_get_word('%');
+ if (strcmp(yylval.token, "%") == 0)
+ return (TOKEN);
+ if (strcmp(yylval.token, "%if") == 0) {
+ free(yylval.token);
+ return (IF);
+ }
+ if (strcmp(yylval.token, "%else") == 0) {
+ free(yylval.token);
+ return (ELSE);
+ }
+ if (strcmp(yylval.token, "%elif") == 0) {
+ free(yylval.token);
+ return (ELIF);
+ }
+ if (strcmp(yylval.token, "%endif") == 0) {
+ free(yylval.token);
+ return (ENDIF);
+ }
+ free(yylval.token);
+ return (ERROR);
+ }
+
+ /*
+ * Otherwise this is a token.
+ */
+ token = yylex_token(ch);
+ if (token == NULL)
+ return (ERROR);
+ yylval.token = token;
+
+ if (strchr(token, '=') != NULL && yylex_is_var(*token, 1)) {
+ for (cp = token + 1; *cp != '='; cp++) {
+ if (!yylex_is_var(*cp, 0))
+ break;
+ }
+ if (*cp == '=')
+ return (EQUALS);
+ }
+ return (TOKEN);
+ }
+ return (0);
+}
+
+static char *
+yylex_format(void)
+{
+ char *buf;
+ size_t len;
+ int ch, brackets = 1;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ yylex_append(&buf, &len, "#{", 2);
+ for (;;) {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '#') {
+ if ((ch = yylex_getc()) == EOF || ch == '\n')
+ goto error;
+ if (ch == '{')
+ brackets++;
+ yylex_append1(&buf, &len, '#');
+ } else if (ch == '}') {
+ if (brackets != 0 && --brackets == 0) {
+ yylex_append1(&buf, &len, ch);
+ break;
+ }
+ }
+ yylex_append1(&buf, &len, ch);
+ }
+ if (brackets != 0)
+ goto error;
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
+
+static int
+yylex_token_escape(char **buf, size_t *len)
+{
+ int ch, type;
+ u_int size, i, tmp;
+ char s[9];
+ struct utf8_data ud;
+
+ switch (ch = yylex_getc()) {
+ case EOF:
+ return (0);
+ case 'e':
+ ch = '\033';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 'n':
+ ch = '\n';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'u':
+ type = 'u';
+ size = 4;
+ goto unicode;
+ case 'U':
+ type = 'U';
+ size = 8;
+ goto unicode;
+ }
+
+ yylex_append1(buf, len, ch);
+ return (1);
+
+unicode:
+ for (i = 0; i < size; i++) {
+ ch = yylex_getc();
+ if (ch == EOF || ch == '\n')
+ return (0);
+ if (!isxdigit((u_char)ch)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ s[i] = ch;
+ }
+ s[i] = '\0';
+
+ if ((size == 4 && sscanf(s, "%4x", &tmp) != 1) ||
+ (size == 8 && sscanf(s, "%8x", &tmp) != 1)) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ if (utf8_split(tmp, &ud) != UTF8_DONE) {
+ yyerror("invalid \\%c argument", type);
+ return (0);
+ }
+ yylex_append(buf, len, ud.data, ud.size);
+ return (1);
+}
+
+static int
+yylex_token_variable(char **buf, size_t *len)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct environ_entry *envent;
+ int ch, brackets = 0;
+ char name[BUFSIZ];
+ size_t namelen = 0;
+ const char *value;
+
+ ch = yylex_getc();
+ if (ch == EOF)
+ return (0);
+ if (ch == '{')
+ brackets = 1;
+ else {
+ if (!yylex_is_var(ch, 1)) {
+ yylex_append1(buf, len, '$');
+ ungetc(ch, ps->f);
+ return (1);
+ }
+ name[namelen++] = ch;
+ }
+
+ for (;;) {
+ ch = yylex_getc();
+ if (brackets && ch == '}')
+ break;
+ if (ch == EOF || !yylex_is_var(ch, 0)) {
+ if (!brackets) {
+ ungetc(ch, ps->f);
+ break;
+ }
+ yyerror("invalid environment variable");
+ return (0);
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("environment variable is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ envent = environ_find(global_environ, name);
+ if (envent != NULL) {
+ value = envent->value;
+ log_debug("%s: %s -> %s", __func__, name, value);
+ yylex_append(buf, len, value, strlen(value));
+ }
+ return (1);
+}
+
+static int
+yylex_token_tilde(char **buf, size_t *len)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ struct environ_entry *envent;
+ int ch;
+ char name[BUFSIZ];
+ size_t namelen = 0;
+ struct passwd *pw;
+ const char *home = NULL;
+
+ for (;;) {
+ ch = yylex_getc();
+ if (ch == EOF || strchr("/ \t\n\"'", ch) != NULL) {
+ ungetc(ch, ps->f);
+ break;
+ }
+ if (namelen == (sizeof name) - 2) {
+ yyerror("user name is too long");
+ return (0);
+ }
+ name[namelen++] = ch;
+ }
+ name[namelen] = '\0';
+
+ if (*name == '\0') {
+ envent = environ_find(global_environ, "HOME");
+ if (envent != NULL && *envent->value != '\0')
+ home = envent->value;
+ else if ((pw = getpwuid(getuid())) != NULL)
+ home = pw->pw_dir;
+ } else {
+ if ((pw = getpwnam(name)) != NULL)
+ home = pw->pw_dir;
+ }
+ if (home == NULL)
+ return (0);
+
+ log_debug("%s: ~%s -> %s", __func__, name, home);
+ yylex_append(buf, len, home, strlen(home));
+ return (1);
+}
+
+static char *
+yylex_token(int ch)
+{
+ struct cmd_parse_state *ps = &parse_state;
+ char *buf;
+ size_t len;
+ enum { START,
+ NONE,
+ DOUBLE_QUOTES,
+ SINGLE_QUOTES } state = NONE, last = START;
+
+ len = 0;
+ buf = xmalloc(1);
+
+ for (;;) {
+ /*
+ * EOF or \n are always the end of the token. If inside quotes
+ * they are an error.
+ */
+ if (ch == EOF || ch == '\n') {
+ if (state != NONE)
+ goto error;
+ break;
+ }
+
+ /* Whitespace or ; ends a token unless inside quotes. */
+ if ((ch == ' ' || ch == '\t' || ch == ';') && state == NONE)
+ break;
+
+ /*
+ * \ ~ and $ are expanded except in single quotes.
+ */
+ if (ch == '\\' && state != SINGLE_QUOTES) {
+ if (!yylex_token_escape(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '~' && last != state && state != SINGLE_QUOTES) {
+ if (!yylex_token_tilde(&buf, &len))
+ goto error;
+ goto skip;
+ }
+ if (ch == '$' && state != SINGLE_QUOTES) {
+ if (!yylex_token_variable(&buf, &len))
+ goto error;
+ goto skip;
+ }
+
+ /*
+ * ' and " starts or end quotes (and is consumed).
+ */
+ if (ch == '\'') {
+ if (state == NONE) {
+ state = SINGLE_QUOTES;
+ goto next;
+ }
+ if (state == SINGLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+ if (ch == '"') {
+ if (state == NONE) {
+ state = DOUBLE_QUOTES;
+ goto next;
+ }
+ if (state == DOUBLE_QUOTES) {
+ state = NONE;
+ goto next;
+ }
+ }
+
+ /*
+ * Otherwise add the character to the buffer.
+ */
+ yylex_append1(&buf, &len, ch);
+
+ skip:
+ last = state;
+
+ next:
+ ch = yylex_getc();
+ }
+ ungetc(ch, ps->f);
+
+ buf[len] = '\0';
+ log_debug("%s: %s", __func__, buf);
+ return (buf);
+
+error:
+ free(buf);
+ return (NULL);
+}
diff --git a/cmd-queue.c b/cmd-queue.c
index 68bedae8..c343a212 100644
--- a/cmd-queue.c
+++ b/cmd-queue.c
@@ -175,15 +175,6 @@ cmdq_remove(struct cmdq_item *item)
free(item);
}
-/* Set command group. */
-static u_int
-cmdq_next_group(void)
-{
- static u_int group;
-
- return (++group);
-}
-
/* Remove all subsequent items that match this item's group. */
static void
cmdq_remove_group(struct cmdq_item *item)
@@ -206,7 +197,6 @@ cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
{
struct cmdq_item *item, *first = NULL, *last = NULL;
struct cmd *cmd;
- u_int group = cmdq_next_group();
struct cmdq_shared *shared;
shared = xcalloc(1, sizeof *shared);
@@ -222,13 +212,15 @@ cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
xasprintf(&item->name, "[%s/%p]", cmd->entry->name, item);
item->type = CMDQ_COMMAND;
- item->group = group;
+ item->group = cmd->group;
item->flags = flags;
item->shared = shared;
item->cmdlist = cmdlist;
item->cmd = cmd;
+ log_debug("%s: %s group %u", __func__, item->name, item->group);
+
shared->references++;
cmdlist->references++;
diff --git a/cmd-source-file.c b/cmd-source-file.c
index 5a3be6f9..a38ecb57 100644
--- a/cmd-source-file.c
+++ b/cmd-source-file.c
@@ -37,8 +37,8 @@ const struct cmd_entry cmd_source_file_entry = {
.name = "source-file",
.alias = "source",
- .args = { "q", 1, 1 },
- .usage = "[-q] path",
+ .args = { "nq", 1, 1 },
+ .usage = "[-nq] path",
.flags = 0,
.exec = cmd_source_file_exec
@@ -58,7 +58,9 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
u_int i;
if (args_has(args, 'q'))
- flags |= CFG_QUIET;
+ flags |= CMD_PARSE_QUIET;
+ if (args_has(args, 'n'))
+ flags |= CMD_PARSE_PARSEONLY;
if (*path == '/')
pattern = xstrdup(path);
@@ -71,7 +73,7 @@ cmd_source_file_exec(struct cmd *self, struct cmdq_item *item)
retval = CMD_RETURN_NORMAL;
if (glob(pattern, 0, NULL, &g) != 0) {
- if (errno != ENOENT || (~flags & CFG_QUIET)) {
+ if (errno != ENOENT || (~flags & CMD_PARSE_QUIET)) {
cmdq_error(item, "%s: %s", path, strerror(errno));
retval = CMD_RETURN_ERROR;
}
diff --git a/cmd-string.c b/cmd-string.c
deleted file mode 100644
index 058f997c..00000000
--- a/cmd-string.c
+++ /dev/null
@@ -1,393 +0,0 @@
-/* $OpenBSD$ */
-
-/*
- * Copyright (c) 2008 Nicholas Marriott <nicholas.marriott@gmail.com>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
- * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
- * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <sys/types.h>
-
-#include <errno.h>
-#include <pwd.h>
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include "tmux.h"
-
-/*
- * Parse a command from a string.
- */
-
-static int cmd_string_getc(const char *, size_t *);
-static void cmd_string_ungetc(size_t *);
-static void cmd_string_copy(char **, char *, size_t *);
-static char *cmd_string_string(const char *, size_t *, char, int);
-static char *cmd_string_variable(const char *, size_t *);
-static char *cmd_string_expand_tilde(const char *, size_t *);
-
-static int
-cmd_string_getc(const char *s, size_t *p)
-{
- const u_char *ucs = s;
-
- if (ucs[*p] == '\0')
- return (EOF);
- return (ucs[(*p)++]);
-}
-
-static void
-cmd_string_ungetc(size_t *p)
-{
- (*p)--;
-}
-
-static int
-cmd_string_unicode(wchar_t *wc, const char *s, size_t *p, char ch)
-{
- int size = (ch == 'u') ? 4 : 8;
- u_int tmp;
-
- if (size == 4 && sscanf(s + *p, "%4x", &tmp) != 1)
- return (-1);
- if (size == 8 && sscanf(s + *p, "%8x", &tmp) != 1)
- return (-1);
- *p += size;
-
- *wc = (wchar_t)tmp;
- return (0);
-}
-
-int
-cmd_string_split(const char *s, int *rargc, char ***rargv)
-{
- size_t p = 0;
- int ch, argc = 0, append = 0;
- char **argv = NULL, *buf = NULL, *t;
- const char *whitespace, *equals;
- size_t len = 0;
-
- for (;;) {
- ch = cmd_string_getc(s, &p);
- switch (ch) {
- case '\'':
- if ((t = cmd_string_string(s, &p, '\'', 0)) == NULL)
- goto error;
- cmd_string_copy(&buf, t, &len);
- break;
- case '"':
- if ((t = cmd_string_string(s, &p, '"', 1)) == NULL)
- goto error;
- cmd_string_copy(&buf, t, &len);
- break;
- case '$':
- if ((t = cmd_string_variable(s, &p)) == NULL)
- goto error;
- cmd_string_copy(&buf, t, &len);
- break;
- case '#':
- /* Comment: discard rest of line. */
- while ((ch = cmd_string_getc(s, &p)) != EOF)
- ;
- /* FALLTHROUGH */
- case EOF:
- case ' ':
- case '\t':
- if (buf != NULL) {
- buf = xrealloc(buf, len + 1);
- buf[len] = '\0';
-
- argv = xreallocarray(argv, argc + 1,
- sizeof *argv);
- argv[argc++] = buf;
-
- buf = NULL;
- len = 0;
- }
-
- if (ch != EOF)
- break;
-
- while (argc != 0) {
- equals = strchr(argv[0], '=');
- whitespace = argv[0] + strcspn(argv[0], " \t");
- if (equals == NULL || equals > whitespace)
- break;
- environ_put(global_environ, argv[0]);
- argc--;
- memmove(argv, argv + 1, argc * (sizeof *argv));
- }
- goto done;
- case '~':
- if (buf != NULL) {
- append = 1;
- break;
- }
- t = cmd_string_expand_tilde(s, &p);
- if (t == NULL)
- goto error;
- cmd_string_copy(&buf, t, &len);
- break;
- default:
- append = 1;
- break;
- }
- if (append) {
- if (len >= SIZE_MAX - 2)
- goto error;
- buf = xrealloc(buf, len + 1);
- buf[len++] = ch;
- }
- append = 0;
- }
-
-done:
- *rargc = argc;
- *rargv = argv;
-
- free(buf);
- return (0);
-
-error:
- if (argv != NULL)
- cmd_free_argv(argc, argv);
- free(buf);
- return (-1);
-}
-
-struct cmd_list *
-cmd_string_parse(const char *s, const char *file, u_int line, char **cause)
-{
- struct cmd_list *cmdlist = NULL;
- int argc;
- char **argv;
-
- if (cause != NULL)
- *cause = NULL;
- log_debug ("%s: %s", __func__, s);
-
- if (cmd_string_split(s, &argc, &argv) != 0) {
- xasprintf(cause, "invalid or unknown command: %s", s);
- return (NULL);
- }
- if (argc != 0) {
- cmdlist = cmd_list_parse(argc, argv, file, line, cause);
- if (cmdlist == NULL) {
- cmd_free_argv(argc, argv);
- return (NULL);
- }
- }
- cmd_free_argv(argc, argv);
- return (cmdlist);
-}
-
-static void
-cmd_string_copy(char **dst, char *src, size_t *len)
-{
- size_t srclen;
-
- srclen = strlen(src);
-
- *dst = xrealloc(*dst, *len + srclen + 1);
- strlcpy(*dst + *len, src, srclen + 1);
-
- *len += srclen;
- free(src);
-}
-
-static char *
-cmd_string_string(const char *s, size_t *p, char endch, int esc)
-{
- int ch;
- wchar_t wc;
- struct utf8_data ud;
- char *buf = NULL, *t;
- size_t len = 0;
-
- while ((ch = cmd_string_getc(s, p)) != endch) {
- switch (ch) {
- case EOF:
- goto error;
- case '\\':
- if (!esc)
- break;
- switch (ch = cmd_string_getc(s, p)) {
- case EOF:
- goto error;
- case 'e':
- ch = '\033';
- break;
- case 'r':
- ch = '\r';
- break;
- case 'n':
- ch = '\n';
- break;
- case 't':
- ch = '\t';
- break;
- case 'u':
- case 'U':
- if (cmd_string_unicode(&wc, s, p, ch) != 0)
- goto error;
- if (utf8_split(wc, &ud) != UTF8_DONE)
- goto error;
- if (len >= SIZE_MAX - ud.size - 1)
- goto error;
- buf = xrealloc(buf, len + ud.size);
- memcpy(buf + len, ud.data, ud.size);
- len += ud.size;
- continue;
- }
- break;
- case '$':
- if (!esc)
- break;
- if ((t = cmd_string_variable(s, p)) == NULL)
- goto error;
- cmd_string_copy(&buf, t, &len);
- continue;
- }
-
- if (len >= SIZE_MAX - 2)
- goto error;
- buf = xrealloc(buf, len + 1);
- buf[len++] = ch;
- }
-
- buf = xrealloc(buf, len + 1);
- buf[len] = '\0';
- return (buf);
-
-error:
- free(buf);
- return (NULL);
-}
-
-static char *
-cmd_string_variable(const char *s, size_t *p)
-{
- int ch, fch;
- char *buf, *t;
- size_t len;
- struct environ_entry *envent;
-
-#define cmd_string_first(ch) ((ch) == '_' || \
- ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z'))
-#define cmd_string_other(ch) ((ch) == '_' || \
- ((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || \
- ((ch) >= '0' && (ch) <= '9'))
-
- buf = NULL;
- len = 0;
-
- fch = EOF;
- switch (ch = cmd_string_getc(s, p)) {
- case EOF:
- goto error;
- case '{':
- fch = '{';
-
- ch = cmd_string_getc(s, p);
- if (!cmd_string_first(ch))
- goto error;
- /* FALLTHROUGH */
- default:
- if (!cmd_string_first(ch)) {
- xasprintf(&t, "$%c", ch);
- return (t);
- }
-
- buf = xrealloc(buf, len + 1);
- buf[len++] = ch;
-
- for (;;) {
- ch = cmd_string_getc(s, p);
- if (ch == EOF || !cmd_string_other(ch))
- break;
- else {
- if (len >= SIZE_MAX - 3)
- goto error;
- buf = xrealloc(buf, len + 1);
- buf[len++] = ch;
- }
- }
- }
-
- if (fch == '{' && ch != '}')
- goto error;
- if (ch != EOF && fch != '{')
- cmd_string_ungetc(p); /* ch */
-
- buf = xrealloc(buf, len + 1);
- buf[len] = '\0';
-
- envent = environ_find(global_environ, buf);
- free(buf);
- if (envent == NULL)
- return (xstrdup(""));
- return (xstrdup(envent->value));
-
-error:
- free(buf);
- return (NULL);
-}
-
-static char *
-cmd_string_expand_tilde(const char *s, size_t *p)
-{
- struct passwd *pw;
- struct environ_entry *envent;
- char *home, *path, *user, *cp;
- int last;
-
- home = NULL;
-
- last = cmd_string_getc(s, p);
- if (last == EOF || last == '/' || last == ' '|| last == '\t') {
- envent = environ_find(global_environ, "HOME");
- if (envent != NULL && *envent->value != '\0')
- home = envent->value;
- else if ((pw = getpwuid(getuid())) != NULL)
- home = pw->pw_dir;
- } else {
- cmd_string_ungetc(p);
-
- cp = user = xmalloc(strlen(s));
- for (;;) {
- last = cmd_string_getc(s, p);
- if (last == EOF ||
- last == '/' ||
- last == ' '||
- last == '\t')
- break;
- *cp++ = last;
- }
- *cp = '\0';
-
- if ((pw = getpwnam(user)) != NULL)
- home = pw->pw_dir;
- free(user);
- }
-
- if (home == NULL)
- return (NULL);
-
- if (last != EOF)
- xasprintf(&path, "%s%c", home, last);
- else
- xasprintf(&path, "%s", home);
- return (path);
-}
diff --git a/cmd.c b/cmd.c
index 7f7348a9..e3dd9853 100644
--- a/cmd.c
+++ b/cmd.c
@@ -213,6 +213,29 @@ cmd_log_argv(int argc, char **argv, const char *prefix)
log_debug("%s: argv[%d]=%s", prefix, i, argv[i]);
}
+void
+cmd_prepend_argv(int *argc, char ***argv, char *arg)
+{
+ char **new_argv;
+ int i;
+
+ new_argv = xreallocarray(NULL, (*argc) + 1, sizeof *new_argv);
+ new_argv[0] = xstrdup(arg);
+ for (i = 0; i < *argc; i++)
+ new_argv[1 + i] = (*argv)[i];
+
+ free(*argv);
+ *argv = new_argv;
+ (*argc)++;
+}
+
+void
+cmd_append_argv(int *argc, char ***argv, char *arg)
+{
+ *argv = xreallocarray(*argv, (*argc) + 1, sizeof **argv);
+ (*argv)[(*argc)++] = xstrdup(arg);
+}
+
int
cmd_pack_argv(int argc, char **argv, char *buf, size_t len)
{
@@ -317,105 +340,102 @@ cmd_stringify_argv(int argc, char **argv)
return (buf);
}
-static int
-cmd_try_alias(int *argc, char ***argv)
+char *
+cmd_get_alias(const char *name)
{
- struct options_entry *o;
- struct options_array_item *a;
- union options_value *ov;
- int old_argc = *argc, new_argc, i;
- char **old_argv = *argv, **new_argv;
- size_t wanted;
- const char *cp = NULL;
+ struct options_entry *o;
+ struct options_array_item *a;
+ union options_value *ov;
+ size_t wanted, n;
+ const char *equals;
o = options_get_only(global_options, "command-alias");
if (o == NULL)
- return (-1);
- wanted = strlen(old_argv[0]);
+ return (NULL);
+ wanted = strlen(name);
a = options_array_first(o);
while (a != NULL) {
ov = options_array_item_value(a);
- cp = strchr(ov->string, '=');
- if (cp != NULL &&
- (size_t)(cp - ov->string) == wanted &&
- strncmp(old_argv[0], ov->string, wanted) == 0)
- break;
+
+ equals = strchr(ov->string, '=');
+ if (equals != NULL) {
+ n = equals - ov->string;
+ if (n == wanted && strncmp(name, ov->string, n) == 0)
+ return (xstrdup(equals + 1));
+ }
+
a = options_array_next(a);
}
- if (a == NULL)
- return (-1);
+ return (NULL);
+}
- if (cmd_string_split(cp + 1, &new_argc, &new_argv) != 0)
- return (-1);
+static const struct cmd_entry *
+cmd_find(const char *name, char **cause)
+{
+ const struct cmd_entry **loop, *entry, *found = NULL;
+ int ambiguous;
+ char s[BUFSIZ];
- *argc = new_argc + old_argc - 1;
- *argv = xcalloc((*argc) + 1, sizeof **argv);
+ ambiguous = 0;
+ for (loop = cmd_table; *loop != NULL; loop++) {
+ entry = *loop;
+ if (entry->alias != NULL && strcmp(entry->alias, name) == 0) {
+ ambiguous = 0;
+ found = entry;
+ break;
+ }
- for (i = 0; i < new_argc; i++)
- (*argv)[i] = xstrdup(new_argv[i]);
- for (i = 1; i < old_argc; i++)
- (*argv)[new_argc + i - 1] = xstrdup(old_argv[i]);
+ if (strncmp(entry->name, name, strlen(name)) != 0)
+ continue;
+ if (found != NULL)
+ ambiguous = 1;
+ found = entry;
- log_debug("alias: %s=%s", old_argv[0], cp + 1);
- for (i = 0; i < *argc; i++)
- log_debug("alias: argv[%d] = %s", i, (*argv)[i]);
+ if (strcmp(entry->name, name) == 0)
+ break;
+ }
+ if (ambiguous)
+ goto ambiguous;
+ if (found == NULL) {
+ xasprintf(cause, "unknown command: %s", name);
+ return (NULL);
+ }
+ return (found);
- cmd_free_argv(new_argc, new_argv);
- return (0);
+ambiguous:
+ *s = '\0';
+ for (loop = cmd_table; *loop != NULL; loop++) {
+ entry = *loop;
+ if (strncmp(entry->name, name, strlen(name)) != 0)
+ continue;
+ if (strlcat(s, entry->name, sizeof s) >= sizeof s)
+ break;
+ if (strlcat(s, ", ", sizeof s) >= sizeof s)
+ break;
+ }
+ s[strlen(s) - 2] = '\0';
+ xasprintf(cause, "ambiguous command: %s, could be: %s", name, s);
+ return (NULL);
}
struct cmd *
cmd_parse(int argc, char **argv, const char *file, u_int line, char **cause)
{
+ const struct cmd_entry *entry;
const char *name;
- const struct cmd_entry **entryp, *entry;
struct cmd *cmd;
struct args *args;
- char s[BUFSIZ];
- int ambiguous, allocated = 0;
- *cause = NULL;
if (argc == 0) {
xasprintf(cause, "no command");
return (NULL);
}
name = argv[0];
-retry:
- ambiguous = 0;
- entry = NULL;
- for (entryp = cmd_table; *entryp != NULL; entryp++) {
- if ((*entryp)->alias != NULL &&
- strcmp((*entryp)->alias, argv[0]) == 0) {
- ambiguous = 0;
- entry = *entryp;
- break;
- }
-
- if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
- continue;
- if (entry != NULL)
- ambiguous = 1;
- entry = *entryp;
-
- /* Bail now if an exact match. */
- if (strcmp(entry->name, argv[0]) == 0)
- break;
- }
- if ((ambiguous || entry == NULL) &&
- server_proc != NULL &&
- !allocated &&
- cmd_try_alias(&argc, &argv) == 0) {
- allocated = 1;
- goto retry;
- }
- if (ambiguous)
- goto ambiguous;
- if (entry == NULL) {
- xasprintf(cause, "unknown command: %s", name);
+ entry = cmd_find(name, cause);
+ if (entry == NULL)
return (NULL);
- }
cmd_log_argv(argc, argv, entry->name);
args = args_parse(entry->args.template, argc, argv);
@@ -434,23 +454,11 @@ retry:
cmd->file = xstrdup(file);
cmd->line = line;
- if (allocated)
- cmd_free_argv(argc, argv);
- return (cmd);
+ cmd->alias = NULL;
+ cmd->argc = argc;
+ cmd->argv = cmd_copy_argv(argc, argv);
-ambiguous:
- *s = '\0';
- for (entryp = cmd_table; *entryp != NULL; entryp++) {
- if (strncmp((*entryp)->name, argv[0], strlen(argv[0])) != 0)
- continue;
- if (strlcat(s, (*entryp)->name, sizeof s) >= sizeof s)
- break;
- if (strlcat(s, ", ", sizeof s) >= sizeof s)
- break;
- }
- s[strlen(s) - 2] = '\0';
- xasprintf(cause, "ambiguous command: %s, could be: %s", name, s);
- return (NULL);
+ return (cmd);
usage:
if (args != NULL)
@@ -459,6 +467,18 @@ usage:
return (NULL);
}
+void
+cmd_free(struct cmd *cmd)
+{
+ free(cmd->alias);
+ cmd_free_argv(cmd->argc, cmd->argv);
+
+ free(cmd->file);
+
+ args_free(cmd->args);
+ free(cmd);
+}
+
char *
cmd_print(struct cmd *cmd)
{
diff --git a/control.c b/control.c
index 41c50df7..b7ac3f62 100644
--- a/control.c
+++ b/control.c
@@ -68,9 +68,9 @@ control_error(struct cmdq_item *item, void *data)
void
control_callback(struct client *c, int closed, __unused void *data)
{
- char *line, *cause;
- struct cmd_list *cmdlist;
+ char *line;
struct cmdq_item *item;
+ struct cmd_parse_result *pr;
if (closed)
c->flags |= CLIENT_EXIT;
@@ -84,15 +84,21 @@ control_callback(struct client *c, int closed, __unused void *data)
break;
}
- cmdlist = cmd_string_parse(line, NULL, 0, &cause);
- if (cmdlist == NULL) {
- item = cmdq_get_callback(control_error, cause);
+ pr = cmd_parse_from_string(line, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ break;
+ case CMD_PARSE_ERROR:
+ item = cmdq_get_callback(control_error, pr->error);
cmdq_append(c, item);
- } else {
- item = cmdq_get_command(cmdlist, NULL, NULL, 0);
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
item->shared->flags |= CMDQ_SHARED_CONTROL;
cmdq_append(c, item);
- cmd_list_free(cmdlist);
+ cmd_list_free(pr->cmdlist);
+ break;
}
free(line);
diff --git a/key-bindings.c b/key-bindings.c
index c4874b50..2bc659aa 100644
--- a/key-bindings.c
+++ b/key-bindings.c
@@ -433,16 +433,15 @@ 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_list *cmdlist;
- char *cause;
+ u_int i;
+ struct cmd_parse_result *pr;
for (i = 0; i < nitems(defaults); i++) {
- cmdlist = cmd_string_parse(defaults[i], "<default>", i, &cause);
- if (cmdlist == NULL)
+ pr = cmd_parse_from_string(defaults[i], NULL);
+ if (pr->status != CMD_PARSE_SUCCESS)
fatalx("bad default key: %s", defaults[i]);
- cmdq_append(NULL, cmdq_get_command(cmdlist, NULL, NULL, 0));
- cmd_list_free(cmdlist);
+ cmdq_append(NULL, cmdq_get_command(pr->cmdlist, NULL, NULL, 0));
+ cmd_list_free(pr->cmdlist);
}
}
diff --git a/menu.c b/menu.c
index 395e1455..9de6c5fc 100644
--- a/menu.c
+++ b/menu.c
@@ -200,9 +200,8 @@ menu_key_cb(struct client *c, struct key_event *event)
u_int i;
int count = menu->count, old = md->choice;
const struct menu_item *item;
- struct cmd_list *cmdlist;
struct cmdq_item *new_item;
- char *cause;
+ struct cmd_parse_result *pr;
if (KEYC_IS_MOUSE(event->key)) {
if (md->flags & MENU_NOMOUSE)
@@ -272,22 +271,22 @@ chosen:
md->cb = NULL;
return (1);
}
- cmdlist = cmd_string_parse(item->command, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL)
- new_item = cmdq_get_error(cause);
- else
- new_item = NULL;
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, NULL, NULL, 0);
- cmd_list_free(cmdlist);
- }
- if (new_item != NULL) {
- if (md->item != NULL)
- cmdq_insert_after(md->item, new_item);
- else
- cmdq_append(c, new_item);
+
+ pr = cmd_parse_from_string(item->command, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ new_item = NULL;
+ break;
+ case CMD_PARSE_ERROR:
+ new_item = cmdq_get_error(pr->error);
+ free(pr->error);
+ cmdq_append(c, new_item);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, NULL, NULL, 0);
+ cmd_list_free(pr->cmdlist);
+ cmdq_append(c, new_item);
+ break;
}
return (1);
}
diff --git a/mode-tree.c b/mode-tree.c
index fb186c1a..9dd96190 100644
--- a/mode-tree.c
+++ b/mode-tree.c
@@ -1045,8 +1045,8 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
const char *template, const char *name)
{
struct cmdq_item *new_item;
- struct cmd_list *cmdlist;
- char *command, *cause;
+ char *command;
+ struct cmd_parse_result *pr;
command = cmd_template_replace(template, name, 1);
if (command == NULL || *command == '\0') {
@@ -1054,17 +1054,22 @@ mode_tree_run_command(struct client *c, struct cmd_find_state *fs,
return;
}
- cmdlist = cmd_string_parse(command, NULL, 0, &cause);
- if (cmdlist == NULL) {
- if (cause != NULL && c != NULL) {
- *cause = toupper((u_char)*cause);
- status_message_set(c, "%s", cause);
+ pr = cmd_parse_from_string(command, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ break;
+ case CMD_PARSE_ERROR:
+ if (c != NULL) {
+ *pr->error = toupper((u_char)*pr->error);
+ status_message_set(c, "%s", pr->error);
}
- free(cause);
- } else {
- new_item = cmdq_get_command(cmdlist, fs, NULL, 0);
+ free(pr->error);
+ break;
+ case CMD_PARSE_SUCCESS:
+ new_item = cmdq_get_command(pr->cmdlist, fs, NULL, 0);
cmdq_append(c, new_item);
- cmd_list_free(cmdlist);
+ cmd_list_free(pr->cmdlist);
+ break;
}
free(command);
diff --git a/options.c b/options.c
index 488f6cf9..fa8752b0 100644
--- a/options.c
+++ b/options.c
@@ -353,8 +353,7 @@ options_array_set(struct options_entry *o, u_int idx, const char *value,
{
struct options_array_item *a;
char *new;
- struct cmd_list *cmdlist;
- char *error;
+ struct cmd_parse_result *pr;
if (!OPTIONS_IS_ARRAY(o)) {
if (cause != NULL)
@@ -363,13 +362,19 @@ options_array_set(struct options_entry *o, u_int idx, const char *value,
}
if (OPTIONS_IS_COMMAND(o)) {
- cmdlist = cmd_string_parse(value, NULL, 0, &error);
- if (cmdlist == NULL && error != NULL) {
+ pr = cmd_parse_from_string(value, NULL);
+ switch (pr->status) {
+ case CMD_PARSE_EMPTY:
+ *cause = xstrdup("empty command");
+ return (-1);
+ case CMD_PARSE_ERROR:
if (cause != NULL)
- *cause = error;
+ *cause = pr->error;
else
- free(error);
+ free(pr->error);
return (-1);
+ case CMD_PARSE_SUCCESS:
+ break;
}
}
@@ -397,7 +402,7 @@ options_array_set(struct options_entry *o, u_int idx, const char *value,
if (OPTIONS_IS_STRING(o))
a->value.string = new;
else if (OPTIONS_IS_COMMAND(o))
- a->value.cmdlist = cmdlist;
+ a->value.cmdlist = pr->cmdlist;
return (0);
}
diff --git a/tmux.1 b/tmux.1
index d5739bd1..3c86ab60 100644
--- a/tmux.1
+++ b/tmux.1
@@ -360,8 +360,217 @@ Key bindings may be changed with the
and
.Ic unbind-key
commands.
+.Sh COMMAND PARSING AND EXECUTION
+.Nm
+supports a large number of commands which can be used to control its
+behaviour.
+Each command is named and can accept zero or more flags and arguments.
+They may be bound to a key with the
+.Ic bind-key
+command or run from the shell prompt, a shell script, a configuration file or
+the command prompt.
+For example, the same
+.Ic set-option
+command run from the shell prompt, from
+.Pa ~/.tmux.conf
+and bound to a key may look like:
+.Bd -literal -offset indent
+$ tmux set-option -g status-style bg=cyan
+
+set-option -g status-style bg=cyan
+
+bind-key C set-option -g status-style bg=cyan
+.Ed
+.Pp
+Here, the command name is
+.Ql set-option ,
+.Ql Fl g
+is a flag and
+.Ql status-style
+and
+.Ql bg=cyan
+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
+.Nm
+or from a configuration file,
+.Nm
+does.
+Examples of when
+.Nm
+parses commands are:
+.Bl -dash -offset indent
+.It
+in a configuration file;
+.It
+typed at the command prompt (see
+.Ic command-prompt ) ;
+.It
+given to
+.Ic bind-key ;
+.It
+passed as arguments to
+.Ic if-shell
+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 ~/.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
+.Ic if-shell ,
+.Ic run-shell
+and
+.Ic display-panes
+stop execution of subsequent commands on the queue until something happens -
+.Ic if-shell
+and
+.Ic run-shell
+until a shell command finishes and
+.Ic display-panes
+until a key is pressed.
+For example, the following commands:
+.Bd -literal -offset indent
+new-session; new-window
+if-shell "true" "split-window"
+kill-session
+.Ed
+.Pp
+Will execute
+.Ic new-session ,
+.Ic new-window ,
+.Ic if-shell ,
+the shell command
+.Xr true 1 ,
+.Ic new-window
+and
+.Ic kill-session
+in that order.
+.Pp
+The
+.Sx COMMANDS
+section lists the
+.Nm
+commands and their arguments.
+.Sh PARSING SYNTAX
+This section describes the syntax of commands parsed by
+.Nm ,
+for example in a configuration file or at the command prompt.
+Note the when commands are entered into the shell, they are parsed by the shell
+- see for example
+.Xr ksh 1
+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.
+.Pp
+Comments are marked by the unquoted # character - any remaining text after a
+comment is ignored until the end of the line.
+.Pp
+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.
+.Pp
+Command arguments may be specified as strings surrounded by either single (')
+or double quotes (").
+.\" "
+This is required when the argument contains any special character.
+Strings cannot span multiple lines except with line continuation.
+.Pp
+Outside of quotes and inside double quotes, these replacements are performed:
+.Bl -dash -offset indent
+.It
+Environment variables preceded by $ are replaced with their value from the
+global environment (see the
+.Sx GLOBAL AND SESSION ENVIRONMENT
+section).
+.It
+A leading ~ or ~user is expanded to 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.
+.Pp
+Any other characters preceded by \e are replaced by themselves (that is, the \e
+is removed) and are not treated as having any special meaning - so for example
+\e; will not mark a command sequence and \e$ will not expand an environment
+variable.
+.El
+.Pp
+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.
+.Pp
+Commands may be parsed conditionally by surrounding them with
+.Ql %if ,
+.Ql %elif ,
+.Ql %else
+and
+.Ql %endif .
+The argument to
+.Ql %if
+and
+.Ql %elif
+is expanded as a format (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 .
+For example:
+.Bd -literal -offset indent
+%if #{==:#{host},myhost}
+set -g status-style bg=red
+%elif #{==:#{host},myotherhost}
+set -g status-style bg=green
+%else
+set -g status-style bg=blue
+%endif
+.Ed
+.Pp
+Will change the status line to red if running on
+.Ql myhost ,
+green if running on
+.Ql myotherhost ,
+or blue if running on another host.
+Conditionals may be given on one line, for example:
+.Bd -literal -offset indent
+%if #{==:#{host},myhost} set -g status-style bg=red %endif
+.Ed
.Sh COMMANDS
-This section contains a list of the commands supported by
+This section describes the commands supported by
.Nm .
Most commands accept the optional
.Fl t
@@ -627,16 +836,6 @@ Or if using
$ tmux bind-key F1 set-option status off
.Ed
.Pp
-Multiple commands may be specified together as part of a
-.Em command sequence .
-Each command should be separated by spaces and a semicolon;
-commands are executed sequentially from left to right and
-lines ending with a backslash continue on to the next line,
-except when escaped by another backslash.
-A literal semicolon may be included by escaping it with a backslash (for
-example, when specifying a command sequence to
-.Ic bind-key ) .
-.Pp
Example
.Nm
commands include:
@@ -1010,7 +1209,7 @@ and
.Fl T
show debugging information about jobs and terminals.
.It Xo Ic source-file
-.Op Fl q
+.Op Fl nq
.Ar path
.Xc
.D1 (alias: Ic source )
@@ -1024,44 +1223,9 @@ If
is given, no error will be returned if
.Ar path
does not exist.
-.Pp
-Within a configuration file, commands may be made conditional by surrounding
-them with
-.Em %if
-and
-.Em %endif
-lines.
-Additional
-.Em %elif
-and
-.Em %else
-lines may also be used.
-The argument to
-.Em %if
-and
-.Em %elif
-is expanded as a format and if it evaluates to false (zero or empty),
-subsequent lines are ignored until the next
-.Em %elif ,
-.Em %else
-or
-.Em %endif .
-For example:
-.Bd -literal -offset indent
-%if #{==:#{host},myhost}
-set -g status-style bg=red
-%elif #{==:#{host},myotherhost}
-set -g status-style bg=green
-%else
-set -g status-style bg=blue
-%endif
-.Ed
-.Pp
-Will change the status line to red if running on
-.Ql myhost ,
-green if running on
-.Ql myotherhost ,
-or blue if running on another host.
+With
+.Fl n ,
+the file is parsed but no commands are executed.
.It Ic start-server
.D1 (alias: Ic start )
Start the
@@ -4141,7 +4305,7 @@ right of the list if there is not enough space.
.Ic norange
.Xc
Mark a range in the
-. Ic status-format
+.Ic status-format
option.
.Ic range=left
and
@@ -4457,7 +4621,7 @@ This command works only from inside
.Op Fl x Ar position
.Op Fl y Ar position
.Xc
-.D1 (alias: Ic menu)
+.D1 (alias: Ic menu )
Display a menu on
.Ar target-client .
.Ar target-pane
diff --git a/tmux.h b/tmux.h
index f80409aa..58e313b1 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1281,18 +1281,25 @@ struct cmd_find_state {
/* Command and list of commands. */
struct cmd {
- const struct cmd_entry *entry;
- struct args *args;
+ const struct cmd_entry *entry;
+ struct args *args;
+ u_int group;
- char *file;
- u_int line;
+ char *file;
+ u_int line;
+
+ char *alias;
+ int argc;
+ char **argv;
- TAILQ_ENTRY(cmd) qentry;
+ TAILQ_ENTRY(cmd) qentry;
};
+TAILQ_HEAD(cmds, cmd);
struct cmd_list {
- int references;
- TAILQ_HEAD(, cmd) list;
+ int references;
+ u_int group;
+ struct cmds list;
};
/* Command return values. */
@@ -1303,6 +1310,31 @@ enum cmd_retval {
CMD_RETURN_STOP
};
+/* Command parse result. */
+enum cmd_parse_status {
+ CMD_PARSE_EMPTY,
+ 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
+
+ const char *file;
+ u_int line;
+
+ struct cmdq_item *item;
+ struct client *c;
+ struct cmd_find_state fs;
+};
+
/* Command queue item type. */
enum cmdq_type {
CMDQ_COMMAND,
@@ -1673,7 +1705,6 @@ void proc_toggle_log(struct tmuxproc *);
/* cfg.c */
extern int cfg_finished;
extern struct client *cfg_client;
-#define CFG_QUIET 0x1
void start_cfg(void);
int load_cfg(const char *, struct client *, struct cmdq_item *, int,
struct cmdq_item **);
@@ -1958,12 +1989,16 @@ int cmd_find_from_nothing(struct cmd_find_state *, int);
/* cmd.c */
void cmd_log_argv(int, char **, const char *);
+void cmd_prepend_argv(int *, char ***, char *);
+void cmd_append_argv(int *, char ***, char *);
int cmd_pack_argv(int, char **, char *, size_t);
int cmd_unpack_argv(char *, size_t, int, char ***);
char **cmd_copy_argv(int, char **);
void cmd_free_argv(int, char **);
char *cmd_stringify_argv(int, char **);
+char *cmd_get_alias(const char *);
struct cmd *cmd_parse(int, char **, const char *, u_int, char **);
+void cmd_free(struct cmd *);
char *cmd_print(struct cmd *);
int cmd_mouse_at(struct window_pane *, struct mouse_event *,
u_int *, u_int *, int);
@@ -1977,7 +2012,16 @@ extern const struct cmd_entry *cmd_table[];
enum cmd_retval cmd_attach_session(struct cmdq_item *, const char *, int, int,
const char *, int);
+/* cmd-parse.c */
+void cmd_parse_empty(struct cmd_parse_input *);
+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 *);
+
/* cmd-list.c */
+struct cmd_list *cmd_list_new(void);
+void cmd_list_append(struct cmd_list *, struct cmd *);
+void cmd_list_move(struct cmd_list *, struct cmd_list *);
struct cmd_list *cmd_list_parse(int, char **, const char *, u_int, char **);
void cmd_list_free(struct cmd_list *);
char *cmd_list_print(struct cmd_list *);
@@ -1999,10 +2043,6 @@ void cmdq_guard(struct cmdq_item *, const char *, int);
void printflike(2, 3) cmdq_print(struct cmdq_item *, const char *, ...);
void printflike(2, 3) cmdq_error(struct cmdq_item *, const char *, ...);
-/* cmd-string.c */
-int cmd_string_split(const char *, int *, char ***);
-struct cmd_list *cmd_string_parse(const char *, const char *, u_int, char **);
-
/* cmd-wait-for.c */
void cmd_wait_for_flush(void);