summaryrefslogtreecommitdiff
path: root/usr.bin/tmux/cmd-queue.c
diff options
context:
space:
mode:
authorNicholas Marriott <nicm@cvs.openbsd.org>2016-10-16 17:55:15 +0000
committerNicholas Marriott <nicm@cvs.openbsd.org>2016-10-16 17:55:15 +0000
commite642c0db3cd87ff0bbb46b1773a45d805b5a0f96 (patch)
tree6c5e61a462443375725c23c72bd64617a08d738b /usr.bin/tmux/cmd-queue.c
parent31dcef6aecdf49dc9f5f9931fecbcb6a386f26e0 (diff)
Rewrite command queue handling. Each client still has a command queue,
but there is also now a global command queue. Instead of command queues being dispatched on demand from wherever the command happens to be added, they are now all dispatched from the top level server loop. Command queues may now also include callbacks as well as commands, and items may be inserted after the current command as well as at the end. This all makes command queues significantly more predictable and easier to use, and avoids the complex multiple nested command queues used by source-file, if-shell and friends. A mass rename of struct cmdq to a better name (cmdq_item probably) is coming.
Diffstat (limited to 'usr.bin/tmux/cmd-queue.c')
-rw-r--r--usr.bin/tmux/cmd-queue.c491
1 files changed, 294 insertions, 197 deletions
diff --git a/usr.bin/tmux/cmd-queue.c b/usr.bin/tmux/cmd-queue.c
index 7fc277e84b4..23211d53ef7 100644
--- a/usr.bin/tmux/cmd-queue.c
+++ b/usr.bin/tmux/cmd-queue.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: cmd-queue.c,v 1.42 2016/10/15 00:01:01 nicm Exp $ */
+/* $OpenBSD: cmd-queue.c,v 1.43 2016/10/16 17:55:14 nicm Exp $ */
/*
* Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com>
@@ -25,47 +25,316 @@
#include "tmux.h"
-static enum cmd_retval cmdq_continue_one(struct cmd_q *);
-static void cmdq_flush(struct cmd_q *);
+/* Global command queue. */
+static struct cmd_q_list global_queue = TAILQ_HEAD_INITIALIZER(global_queue);
-/* Create new command queue. */
+/* Get command queue name. */
+static const char *
+cmdq_name(struct client *c)
+{
+ static char s[32];
+
+ if (c == NULL)
+ return ("<global>");
+ xsnprintf(s, sizeof s, "<%p>", c);
+ return (s);
+}
+
+/* Get command queue from client. */
+static struct cmd_q_list *
+cmdq_get(struct client *c)
+{
+ if (c == NULL)
+ return (&global_queue);
+ return (&c->queue);
+}
+
+/* Append an item. */
+void
+cmdq_append(struct client *c, struct cmd_q *cmdq)
+{
+ struct cmd_q_list *queue = cmdq_get(c);
+ struct cmd_q *next;
+
+ do {
+ next = cmdq->next;
+ cmdq->next = NULL;
+
+ if (c != NULL)
+ c->references++;
+ cmdq->client = c;
+
+ cmdq->queue = queue;
+ TAILQ_INSERT_TAIL(queue, cmdq, entry);
+
+ cmdq = next;
+ } while (cmdq != NULL);
+}
+
+/* Insert an item. */
+void
+cmdq_insert_after(struct cmd_q *after, struct cmd_q *cmdq)
+{
+ struct client *c = after->client;
+ struct cmd_q_list *queue = after->queue;
+ struct cmd_q *next;
+
+ do {
+ next = cmdq->next;
+ cmdq->next = NULL;
+
+ if (c != NULL)
+ c->references++;
+ cmdq->client = c;
+
+ cmdq->queue = queue;
+ if (after->next != NULL)
+ TAILQ_INSERT_AFTER(queue, after->next, cmdq, entry);
+ else
+ TAILQ_INSERT_AFTER(queue, after, cmdq, entry);
+ after->next = cmdq;
+
+ cmdq = next;
+ } while (cmdq != NULL);
+}
+
+/* Remove an item. */
+static void
+cmdq_remove(struct cmd_q *cmdq)
+{
+ free((void *)cmdq->hook);
+
+ if (cmdq->client != NULL)
+ server_client_unref(cmdq->client);
+
+ if (cmdq->type == CMD_Q_COMMAND)
+ cmd_list_free(cmdq->cmdlist);
+
+ TAILQ_REMOVE(cmdq->queue, cmdq, entry);
+ free(cmdq);
+}
+
+/* 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 cmd_q *cmdq)
+{
+ struct cmd_q *this, *next;
+
+ this = TAILQ_NEXT(cmdq, entry);
+ while (this != NULL) {
+ next = TAILQ_NEXT(this, entry);
+ if (this->group == cmdq->group)
+ cmdq_remove(this);
+ this = next;
+ }
+}
+
+/* Get a command for the command queue. */
struct cmd_q *
-cmdq_new(struct client *c)
+cmdq_get_command(struct cmd_list *cmdlist, struct cmd_find_state *current,
+ struct mouse_event *m, int flags)
{
- struct cmd_q *cmdq;
+ struct cmd_q *cmdq, *first = NULL, *last = NULL;
+ struct cmd *cmd;
+ u_int group = cmdq_next_group();
+
+ TAILQ_FOREACH(cmd, &cmdlist->list, qentry) {
+ cmdq = xcalloc(1, sizeof *cmdq);
+ cmdq->type = CMD_Q_COMMAND;
+ cmdq->group = group;
+ cmdq->flags = flags;
+
+ cmdq->cmdlist = cmdlist;
+ cmdq->cmd = cmd;
+
+ if (current != NULL)
+ cmd_find_copy_state(&cmdq->current, current);
+ if (m != NULL)
+ memcpy(&cmdq->mouse, m, sizeof cmdq->mouse);
+ cmdlist->references++;
+
+ if (first == NULL)
+ first = cmdq;
+ if (last != NULL)
+ last->next = cmdq;
+ last = cmdq;
+ }
+ return (first);
+}
- cmdq = xcalloc(1, sizeof *cmdq);
- cmdq->references = 1;
- cmdq->flags = 0;
+/* Fire command on command queue. */
+static enum cmd_retval
+cmdq_fire_command(struct cmd_q *cmdq)
+{
+ struct client *c = cmdq->client;
+ struct cmd *cmd = cmdq->cmd;
+ enum cmd_retval retval;
+ const char *name;
+ struct cmd_find_state *fsp, fs;
+ int flags;
+
+ flags = !!(cmd->flags & CMD_CONTROL);
+ cmdq_guard(cmdq, "begin", flags);
+
+ if (cmd_prepare_state(cmd, cmdq) != 0) {
+ retval = CMD_RETURN_ERROR;
+ goto out;
+ }
+ if (cmdq->client == NULL)
+ cmdq->client = cmd_find_client(cmdq, NULL, CMD_FIND_QUIET);
+ retval = cmd->entry->exec(cmd, cmdq);
+ if (retval == CMD_RETURN_ERROR)
+ goto out;
+
+ if (cmd->entry->flags & CMD_AFTERHOOK) {
+ name = cmd->entry->name;
+ if (cmd_find_valid_state(&cmdq->state.tflag))
+ fsp = &cmdq->state.tflag;
+ else {
+ if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
+ goto out;
+ fsp = &fs;
+ }
+ hooks_insert(fsp->s->hooks, cmdq, fsp, "after-%s", name);
+ }
+
+out:
cmdq->client = c;
- cmdq->client_exit = -1;
+ if (retval == CMD_RETURN_ERROR)
+ cmdq_guard(cmdq, "error", flags);
+ else
+ cmdq_guard(cmdq, "end", flags);
+ return (retval);
+}
- TAILQ_INIT(&cmdq->queue);
- cmdq->item = NULL;
- cmdq->cmd = NULL;
+/* Get a callback for the command queue. */
+struct cmd_q *
+cmdq_get_callback(cmd_q_cb cb, void *data)
+{
+ struct cmd_q *cmdq;
+
+ cmdq = xcalloc(1, sizeof *cmdq);
+ cmdq->type = CMD_Q_CALLBACK;
+ cmdq->group = 0;
+ cmdq->flags = 0;
- cmd_find_clear_state(&cmdq->current, NULL, 0);
- cmdq->parent = NULL;
+ cmdq->cb = cb;
+ cmdq->data = data;
return (cmdq);
}
-/* Free command queue */
-int
-cmdq_free(struct cmd_q *cmdq)
+/* Fire callback on callback queue. */
+static enum cmd_retval
+cmdq_fire_callback(struct cmd_q *cmdq)
{
- log_debug("cmdq %p free: %u references", cmdq, cmdq->references);
+ return (cmdq->cb(cmdq, cmdq->data));
+}
+
+/* Process next item on command queue. */
+u_int
+cmdq_next(struct client *c)
+{
+ struct cmd_q_list *queue = cmdq_get(c);
+ const char *name = cmdq_name(c);
+ struct cmd_q *cmdq;
+ enum cmd_retval retval;
+ u_int items = 0;
+ static u_int number;
- if (--cmdq->references != 0) {
- if (cmdq->flags & CMD_Q_DEAD)
- return (1);
+ if (TAILQ_EMPTY(queue)) {
+ log_debug("%s %s: empty", __func__, name);
+ return (0);
+ }
+ if (TAILQ_FIRST(queue)->flags & CMD_Q_WAITING) {
+ log_debug("%s %s: waiting", __func__, name);
return (0);
}
- cmdq_flush(cmdq);
- free(cmdq);
- return (1);
+ log_debug("%s %s: enter", __func__, name);
+ for (;;) {
+ cmdq = TAILQ_FIRST(queue);
+ if (cmdq == NULL)
+ break;
+ log_debug("%s %s: type %d, flags %x", __func__, name,
+ cmdq->type, cmdq->flags);
+
+ /*
+ * Any item with the waiting flag set waits until an external
+ * event clears the flag (for example, a job - look at
+ * run-shell).
+ */
+ if (cmdq->flags & CMD_Q_WAITING)
+ goto waiting;
+
+ /*
+ * Items are only fired once, once the fired flag is set, a
+ * waiting flag can only be cleared by an external event.
+ */
+ if (~cmdq->flags & CMD_Q_FIRED) {
+ cmdq->time = time(NULL);
+ cmdq->number = ++number;
+
+ switch (cmdq->type)
+ {
+ case CMD_Q_COMMAND:
+ retval = cmdq_fire_command(cmdq);
+
+ /*
+ * If a command returns an error, remove any
+ * subsequent commands in the same group.
+ */
+ if (retval == CMD_RETURN_ERROR)
+ cmdq_remove_group(cmdq);
+ break;
+ case CMD_Q_CALLBACK:
+ retval = cmdq_fire_callback(cmdq);
+ break;
+ default:
+ retval = CMD_RETURN_ERROR;
+ break;
+ }
+ cmdq->flags |= CMD_Q_FIRED;
+
+ if (retval == CMD_RETURN_WAIT) {
+ cmdq->flags |= CMD_Q_WAITING;
+ goto waiting;
+ }
+ items++;
+ }
+ cmdq_remove(cmdq);
+ }
+
+ log_debug("%s %s: exit (empty)", __func__, name);
+ return (items);
+
+waiting:
+ log_debug("%s %s: exit (wait)", __func__, name);
+ return (items);
+}
+
+/* Print a guard line. */
+void
+cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
+{
+ struct client *c = cmdq->client;
+
+ if (c == NULL || !(c->flags & CLIENT_CONTROL))
+ return;
+
+ evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
+ (long)cmdq->time, cmdq->number, flags);
+ server_client_push_stdout(c);
}
/* Show message from command. */
@@ -140,175 +409,3 @@ cmdq_error(struct cmd_q *cmdq, const char *fmt, ...)
free(msg);
}
-
-/* Print a guard line. */
-void
-cmdq_guard(struct cmd_q *cmdq, const char *guard, int flags)
-{
- struct client *c = cmdq->client;
-
- if (c == NULL || !(c->flags & CLIENT_CONTROL))
- return;
-
- evbuffer_add_printf(c->stdout_data, "%%%s %ld %u %d\n", guard,
- (long) cmdq->time, cmdq->number, flags);
- server_client_push_stdout(c);
-}
-
-/* Add command list to queue and begin processing if needed. */
-void
-cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist, struct mouse_event *m)
-{
- cmdq_append(cmdq, cmdlist, m);
-
- 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 mouse_event *m)
-{
- struct cmd_q_item *item;
-
- item = xcalloc(1, sizeof *item);
- item->cmdlist = cmdlist;
- TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry);
- cmdlist->references++;
-
- if (m != NULL)
- memcpy(&item->mouse, m, sizeof item->mouse);
- else
- item->mouse.valid = 0;
-}
-
-/* Process one command. */
-static enum cmd_retval
-cmdq_continue_one(struct cmd_q *cmdq)
-{
- struct cmd_list *cmdlist = cmdq->item->cmdlist;
- struct cmd *cmd = cmdq->cmd;
- enum cmd_retval retval;
- char *tmp;
- int flags = !!(cmd->flags & CMD_CONTROL);
- const char *name;
- struct cmd_find_state *fsp, fs;
-
- cmdlist->references++;
-
- tmp = cmd_print(cmd);
- log_debug("cmdq %p: %s", cmdq, tmp);
- free(tmp);
-
- cmdq->time = time(NULL);
- cmdq->number++;
-
- cmdq_guard(cmdq, "begin", flags);
-
- if (cmd_prepare_state(cmd, cmdq, cmdq->parent) != 0)
- goto error;
-
- retval = cmd->entry->exec(cmd, cmdq);
- if (retval == CMD_RETURN_ERROR)
- goto error;
-
- if (~cmd->entry->flags & CMD_AFTERHOOK)
- goto end;
-
- if (cmd_find_valid_state(&cmdq->state.tflag))
- fsp = &cmdq->state.tflag;
- else {
- if (cmd_find_current(&fs, cmdq, CMD_FIND_QUIET) != 0)
- goto end;
- fsp = &fs;
- }
- name = cmd->entry->name;
- if (hooks_wait(fsp->s->hooks, cmdq, fsp, "after-%s", name) == 0)
- retval = CMD_RETURN_WAIT;
-
-end:
- cmdq_guard(cmdq, "end", flags);
- cmd_list_free(cmdlist);
- return (retval);
-
-error:
- cmdq_guard(cmdq, "error", flags);
- cmd_list_free(cmdlist);
- return (CMD_RETURN_ERROR);
-}
-
-/* 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 empty;
-
- log_debug("continuing cmdq %p: flags %#x (%p)", cmdq, cmdq->flags, c);
- cmdq->references++;
-
- 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 {
- while (cmdq->cmd != NULL) {
- retval = cmdq_continue_one(cmdq);
- 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);
- }
- next = TAILQ_NEXT(cmdq->item, 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:
- log_debug("cmdq %p empty", cmdq);
- if (cmdq->client_exit > 0)
- cmdq->client->flags |= CLIENT_EXIT;
- if (cmdq->emptyfn != NULL)
- cmdq->emptyfn(cmdq);
- empty = 1;
-
-out:
- cmdq_free(cmdq);
- return (empty);
-}
-
-/* Flush command queue. */
-static 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;
-}
-