From 20636d956dd36c1f14152569a4d44a50eea9083d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 Mar 2013 09:54:10 +0000 Subject: Add a command queue to standardize and simplify commands that call other commands and allow a command to block execution of subsequent commands. This allows run-shell and if-shell to be synchronous which has been much requested. Each client has a default command queue and commands are consumed one at a time from it. A command may suspend execution from the queue by returning CMD_RETURN_WAIT and then resume it by calling cmd_continue() - for example run-shell does this from the callback that is fired after the job is freed. When the command queue becomes empty, command clients are automatically exited (unless attaching). A callback is also fired - this is used for nested commands in, for example, if-shell which can block execution of the client's cmdq until a new cmdq becomes empty. Also merge all the old error/info/print functions together and lose the old curclient/cmdclient distinction - a cmdq is bound to one client (or none if in the configuration file), this is a command client if c->session is NULL otherwise an attached client. --- cmd-queue.c | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 cmd-queue.c (limited to 'cmd-queue.c') diff --git a/cmd-queue.c b/cmd-queue.c new file mode 100644 index 00000000..07884df2 --- /dev/null +++ b/cmd-queue.c @@ -0,0 +1,258 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013 Nicholas Marriott + * + * 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 + +#include +#include + +#include "tmux.h" + +/* Create new command queue. */ +struct cmd_q * +cmdq_new(struct client *c) +{ + struct cmd_q *cmdq; + + cmdq = xcalloc(1, sizeof *cmdq); + cmdq->references = 1; + cmdq->dead = 0; + + cmdq->client = c; + cmdq->client_exit = 0; + + TAILQ_INIT(&cmdq->queue); + cmdq->item = NULL; + cmdq->cmd = NULL; + + return (cmdq); +} + +/* Free command queue */ +int +cmdq_free(struct cmd_q *cmdq) +{ + if (--cmdq->references != 0) + return (cmdq->dead); + + cmdq_flush(cmdq); + free(cmdq); + return (1); +} + +/* Show message from command. */ +void printflike2 +cmdq_print(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + struct window *w; + va_list ap; + + va_start(ap, fmt); + + if (c == NULL) + /* nothing */; + else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + va_start(ap, fmt); + evbuffer_add_vprintf(c->stdout_data, fmt, ap); + va_end(ap); + + evbuffer_add(c->stdout_data, "\n", 1); + server_push_stdout(c); + } else { + w = c->session->curw->window; + if (w->active->mode != &window_copy_mode) { + window_pane_reset_mode(w->active); + window_pane_set_mode(w->active, &window_copy_mode); + window_copy_init_for_output(w->active); + } + window_copy_vadd(w->active, fmt, ap); + } + + va_end(ap); +} + +/* Show info from command. */ +void printflike2 +cmdq_info(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + va_list ap; + char *msg; + + if (options_get_number(&global_options, "quiet")) + return; + + va_start(ap, fmt); + + if (c == NULL) + /* nothing */; + else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + va_start(ap, fmt); + evbuffer_add_vprintf(c->stdout_data, fmt, ap); + va_end(ap); + + evbuffer_add(c->stdout_data, "\n", 1); + server_push_stdout(c); + } else { + xvasprintf(&msg, fmt, ap); + *msg = toupper((u_char) *msg); + status_message_set(c, "%s", msg); + free(msg); + } + + va_end(ap); + +} + +/* Show error from command. */ +void printflike2 +cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + struct cmd *cmd = cmdq->cmd; + va_list ap; + char *msg, *cause; + size_t msglen; + + va_start(ap, fmt); + msglen = xvasprintf(&msg, fmt, ap); + va_end(ap); + + if (c == NULL) { + xasprintf(&cause, "%s:%u: %s", cmd->file, cmd->line, msg); + ARRAY_ADD(&cfg_causes, cause); + } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + evbuffer_add(c->stderr_data, msg, msglen); + evbuffer_add(c->stderr_data, "\n", 1); + + server_push_stderr(c); + c->retcode = 1; + } else { + *msg = toupper((u_char) *msg); + status_message_set(c, "%s", msg); + } + + free(msg); +} + +/* Add command list to queue and begin processing if needed. */ +void +cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist) +{ + cmdq_append(cmdq, cmdlist); + + if (cmdq->item == NULL) { + cmdq->cmd = NULL; + cmdq_continue(cmdq); + } +} + +/* Add command list to queue. */ +void +cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist) +{ + struct cmd_q_item *item; + + item = xcalloc(1, sizeof *item); + item->cmdlist = cmdlist; + TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry); + cmdlist->references++; +} + +/* Continue processing command queue. Returns 1 if finishes empty. */ +int +cmdq_continue(struct cmd_q *cmdq) +{ + struct client *c = cmdq->client; + struct cmd_q_item *next; + enum cmd_retval retval; + int guards, empty; + + guards = 0; + if (c != NULL && c->session != NULL) + guards = c->flags & CLIENT_CONTROL; + + notify_disable(); + + empty = TAILQ_EMPTY(&cmdq->queue); + if (empty) + goto empty; + + if (cmdq->item == NULL) { + cmdq->item = TAILQ_FIRST(&cmdq->queue); + cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); + } else + cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); + + do { + next = TAILQ_NEXT(cmdq->item, qentry); + + while (cmdq->cmd != NULL) { + if (guards) + cmdq_print(cmdq, "%%begin"); + retval = cmdq->cmd->entry->exec(cmdq->cmd, cmdq); + if (guards) + cmdq_print(cmdq, "%%end"); + + if (retval == CMD_RETURN_ERROR) + break; + if (retval == CMD_RETURN_WAIT) + goto out; + if (retval == CMD_RETURN_STOP) { + cmdq_flush(cmdq); + goto empty; + } + + cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); + } + + TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry); + cmd_list_free(cmdq->item->cmdlist); + free(cmdq->item); + + cmdq->item = next; + if (cmdq->item != NULL) + cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); + } while (cmdq->item != NULL); + +empty: + if (cmdq->client_exit) + cmdq->client->flags |= CLIENT_EXIT; + if (cmdq->emptyfn != NULL) + cmdq->emptyfn(cmdq); /* may free cmdq */ + empty = 1; + +out: + notify_enable(); + return (empty); +} + +/* Flush command queue. */ +void +cmdq_flush(struct cmd_q *cmdq) +{ + struct cmd_q_item *item, *item1; + + TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) { + TAILQ_REMOVE(&cmdq->queue, item, qentry); + cmd_list_free(item->cmdlist); + free(item); + } + cmdq->item = NULL; +} -- cgit v1.2.3