Add invoke API.

This commit is contained in:
Your Name
2026-06-30 08:24:44 +01:00
parent efe2453eac
commit 22b6ad6fce
6 changed files with 552 additions and 4 deletions

View File

@@ -103,6 +103,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 \

488
cmd-invoke.c Normal file
View File

@@ -0,0 +1,488 @@
/* $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_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_invoke_frame *stack;
u_int nstack;
enum cmd_retval last;
int have_last;
int argc;
char **argv;
int parse_flags;
char *file;
};
static void cmd_invoke_push(struct cmd_invoke_state *,
struct cmd_parse_node *, struct cmd_parse_node *,
struct cmd_parse_node *);
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 *);
/* Push a node's child range onto the traversal stack. */
static void
cmd_invoke_push(struct cmd_invoke_state *is, 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].node = node;
is->stack[is->nstack].next = first;
is->stack[is->nstack].end = end;
is->nstack = n;
}
/* 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;
is->nstack--;
}
node = frame->next;
frame->next = cmd_parse_node_next(node);
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;
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);
}
/* Push the selected conditional branch onto the traversal stack. */
static void
cmd_invoke_push_branch(struct cmd_invoke_state *is,
struct cmd_parse_node *node, struct cmd_parse_node *first)
{
cmd_invoke_push(is, node, first, 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_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) {
cmd_invoke_push_branch(is, node, first);
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) {
cmd_invoke_push_branch(is, child, next);
return (0);
}
break;
case CMD_PARSE_ELSE:
next = cmd_parse_node_first_child(child);
cmd_invoke_push_branch(is, child, next);
return (0);
default:
break;
}
}
return (0);
}
/* 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_node *child;
struct args_value *values = NULL;
struct cmd *cmd;
char *cause = NULL;
u_int count = 0, i;
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].cmdparse = child;
break;
default:
fatalx("unexpected node type in command");
}
count++;
child = cmd_parse_node_next(child);
}
cmd = cmd_parse(values, count, is->file, cmd_parse_node_line(node),
is->parse_flags, &cause);
if (cmd == NULL) {
cmdq_error(item, "%s", cause);
free(cause);
goto fail;
}
for (i = 0; i < count; i++) {
if (values[i].type == ARGS_STRING)
free(values[i].string);
}
free(values);
return (cmd);
fail:
for (i = 0; i < count; i++) {
if (values[i].type == ARGS_STRING)
free(values[i].string);
}
free(values);
return (NULL);
}
/* 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, int argc,
char **argv)
{
struct cmd_invoke_state *is;
struct cmd_parse_node *root = cmd_parse_root(tree), *first;
struct cmdq_item *item;
int i;
is = xcalloc(1, sizeof *is);
is->references = 1;
is->tree = cmd_parse_add_ref(tree);
is->argc = argc;
if (argc != 0) {
is->argv = xreallocarray(NULL, argc, sizeof *is->argv);
for (i = 0; i < argc; i++)
is->argv[i] = xstrdup(argv[i]);
}
first = cmd_parse_node_first_child(root);
cmd_invoke_push(is, 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;
if (is == NULL)
return;
if (--is->references != 0)
return;
for (i = 0; i < is->argc; i++)
free(is->argv[i]);
free(is->argv);
cmd_parse_free(is->tree);
free(is->file);
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;
struct cmdq_item *new_item, *next;
struct cmdq_state *state;
struct cmd *cmd;
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);
switch (cmd_parse_node_type(node)) {
case CMD_PARSE_ROOT:
case CMD_PARSE_SEQUENCE:
cmd_invoke_push(is, node,
cmd_parse_node_first_child(node), NULL);
break;
case CMD_PARSE_ASSIGN:
case CMD_PARSE_HIDDEN_ASSIGN:
if (cmd_invoke_assignment(item, is, node) != 0)
return (CMD_RETURN_ERROR);
break;
case CMD_PARSE_IF:
if (cmd_invoke_if(item, is, node) != 0)
return (CMD_RETURN_ERROR);
break;
case CMD_PARSE_ELIF:
case CMD_PARSE_ELSE:
break;
case CMD_PARSE_COMMAND:
cmd = cmd_invoke_build_command(item, is, node);
if (cmd == NULL)
return (CMD_RETURN_ERROR);
/*
* 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");
}
}
}

View File

@@ -675,7 +675,7 @@ cmd_parse_from_string(const char *s, struct cmd_parse_input *pi, char **cause)
}
struct cmd_parse_tree *
cmd_parse_addref(struct cmd_parse_tree *tree)
cmd_parse_add_ref(struct cmd_parse_tree *tree)
{
tree->references++;
return (tree);

View File

@@ -35,6 +35,7 @@
enum cmdq_type {
CMDQ_COMMAND,
CMDQ_CALLBACK,
CMDQ_INVOKE,
};
/* Command queue item. */
@@ -60,6 +61,7 @@ struct cmdq_item {
struct cmd_list *cmdlist;
struct cmd *cmd;
struct cmd_invoke_state *invoke_state;
cmdq_cb cb;
void *data;
@@ -457,6 +459,8 @@ cmdq_remove(struct cmdq_item *item)
server_client_unref(item->client);
if (item->cmdlist != NULL)
cmd_list_free(item->cmdlist);
if (item->invoke_state != NULL)
cmd_invoke_state_free(item->invoke_state);
cmdq_free_state(item->state);
TAILQ_REMOVE(&item->queue->list, item, entry);
@@ -536,6 +540,41 @@ cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state)
return (first);
}
/* Get a single command for command queue with invoke state. */
struct cmdq_item *
cmdq_get_one_command(struct cmd *cmd, struct cmdq_state *state,
struct cmd_invoke_state *invoke_state)
{
struct cmd_list *cmdlist;
struct cmdq_item *item;
cmdlist = cmd_list_new();
cmd_list_append(cmdlist, cmd);
item = cmdq_get_command(cmdlist, state);
cmd_list_free(cmdlist);
item->invoke_state = cmd_invoke_state_add_ref(invoke_state);
return (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;
item = xcalloc(1, sizeof *item);
xasprintf(&item->name, "[invoke/%p]", item);
item->type = CMDQ_INVOKE;
item->group = 0;
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. */
static enum cmd_retval
cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs,
@@ -772,6 +811,8 @@ 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
@@ -783,6 +824,9 @@ cmdq_next(struct client *c)
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;

View File

@@ -63,10 +63,10 @@ enum cmd_parse_node_type {
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_input *, char **);
struct cmd_parse_tree *cmd_parse_from_string(const char *,
struct cmd_parse_input *, char **);
struct cmd_parse_tree *cmd_parse_addref(struct cmd_parse_tree *);
struct cmd_parse_input *, char **);
struct cmd_parse_tree *cmd_parse_add_ref(struct cmd_parse_tree *);
void cmd_parse_free(struct cmd_parse_tree *);
struct cmd_parse_node *cmd_parse_root(struct cmd_parse_tree *);
char *cmd_parse_print(const struct cmd_parse_tree *);

15
tmux.h
View File

@@ -43,6 +43,9 @@ 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;
@@ -3023,6 +3026,14 @@ struct cmd_parse_result *cmd_parse_from_buffer(const void *, size_t,
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 *,
int, char **);
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 *,
int);
@@ -3046,6 +3057,10 @@ 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 *);