From 0b44ad99b51606a8cab662e04cf043a8c4a3ca92 Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 20 Apr 2017 09:20:22 +0000 Subject: [PATCH 1/3] If a #() command doesn't exit, use its most recent line of output (it must be a full line). Don't let it redraw the status line more than once a second. Requested by someone about 10 years ago... --- cmd-if-shell.c | 4 ++-- cmd-run-shell.c | 4 ++-- cmd-show-messages.c | 2 +- format.c | 47 ++++++++++++++++++++++++++++++++++--------- job.c | 49 ++++++++++++++++++++++++++++++--------------- server.c | 11 ++++++++-- tmux.1 | 2 ++ tmux.h | 31 ++++++++++++++++------------ window-copy.c | 2 +- 9 files changed, 106 insertions(+), 46 deletions(-) diff --git a/cmd-if-shell.c b/cmd-if-shell.c index f7d95d53..0b1fe7f5 100644 --- a/cmd-if-shell.c +++ b/cmd-if-shell.c @@ -127,8 +127,8 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item) cdata->item = NULL; memcpy(&cdata->mouse, &item->mouse, sizeof cdata->mouse); - job_run(shellcmd, s, cwd, cmd_if_shell_callback, cmd_if_shell_free, - cdata); + job_run(shellcmd, s, cwd, NULL, cmd_if_shell_callback, + cmd_if_shell_free, cdata); free(shellcmd); if (args_has(args, 'b')) diff --git a/cmd-run-shell.c b/cmd-run-shell.c index d06d7f99..48d88049 100644 --- a/cmd-run-shell.c +++ b/cmd-run-shell.c @@ -110,8 +110,8 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item) if (!args_has(args, 'b')) cdata->item = item; - job_run(cdata->cmd, s, cwd, cmd_run_shell_callback, cmd_run_shell_free, - cdata); + job_run(cdata->cmd, s, cwd, NULL, cmd_run_shell_callback, + cmd_run_shell_free, cdata); if (args_has(args, 'b')) return (CMD_RETURN_NORMAL); diff --git a/cmd-show-messages.c b/cmd-show-messages.c index e0b72b9a..559c36df 100644 --- a/cmd-show-messages.c +++ b/cmd-show-messages.c @@ -76,7 +76,7 @@ cmd_show_messages_jobs(struct cmdq_item *item, int blank) u_int n; n = 0; - LIST_FOREACH(job, &all_jobs, lentry) { + LIST_FOREACH(job, &all_jobs, entry) { if (blank) { cmdq_print(item, "%s", ""); blank = 0; diff --git a/format.c b/format.c index 269d506a..5c85f1ff 100644 --- a/format.c +++ b/format.c @@ -39,7 +39,6 @@ struct format_entry; typedef void (*format_cb)(struct format_tree *, struct format_entry *); -static void format_job_callback(struct job *); static char *format_job_get(struct format_tree *, const char *); static void format_job_timer(int, short, void *); @@ -83,6 +82,7 @@ struct format_job { time_t last; char *out; + int updated; struct job *job; int status; @@ -203,9 +203,35 @@ static const char *format_lower[] = { NULL /* z */ }; -/* Format job callback. */ +/* Format job update callback. */ static void -format_job_callback(struct job *job) +format_job_update(struct job *job) +{ + struct format_job *fj = job->data; + char *line; + time_t t; + struct client *c; + + if ((line = evbuffer_readline(job->event->input)) == NULL) + return; + fj->updated = 1; + + free(fj->out); + fj->out = line; + + log_debug("%s: %s: %s", __func__, fj->cmd, fj->out); + + t = time (NULL); + if (fj->status && fj->last != t) { + TAILQ_FOREACH(c, &clients, entry) + server_status_client(c); + fj->last = t; + } +} + +/* Format job complete callback. */ +static void +format_job_complete(struct job *job) { struct format_job *fj = job->data; char *line, *buf; @@ -213,7 +239,6 @@ format_job_callback(struct job *job) struct client *c; fj->job = NULL; - free(fj->out); buf = NULL; if ((line = evbuffer_readline(job->event->input)) == NULL) { @@ -224,15 +249,19 @@ format_job_callback(struct job *job) buf[len] = '\0'; } else buf = line; - fj->out = buf; + + if (*buf != '\0' || !fj->updated) { + free(fj->out); + fj->out = buf; + log_debug("%s: %s: %s", __func__, fj->cmd, fj->out); + } else + free(buf); if (fj->status) { TAILQ_FOREACH(c, &clients, entry) server_status_client(c); fj->status = 0; } - - log_debug("%s: %s: %s", __func__, fj->cmd, fj->out); } /* Find a job. */ @@ -267,8 +296,8 @@ format_job_get(struct format_tree *ft, const char *cmd) t = time(NULL); if (fj->job == NULL && (force || fj->last != t)) { - fj->job = job_run(expanded, NULL, NULL, format_job_callback, - NULL, fj); + fj->job = job_run(expanded, NULL, NULL, format_job_update, + format_job_complete, NULL, fj); if (fj->job == NULL) { free(fj->out); xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); diff --git a/job.c b/job.c index d5b8713f..caf5db52 100644 --- a/job.c +++ b/job.c @@ -33,8 +33,9 @@ * output. */ -static void job_callback(struct bufferevent *, short, void *); +static void job_read_callback(struct bufferevent *, void *); static void job_write_callback(struct bufferevent *, void *); +static void job_error_callback(struct bufferevent *, short, void *); /* All jobs list. */ struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); @@ -42,7 +43,8 @@ struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); /* Start a job running, if it isn't already. */ struct job * job_run(const char *cmd, struct session *s, const char *cwd, - void (*callbackfn)(struct job *), void (*freefn)(void *), void *data) + job_update_cb updatecb, job_complete_cb completecb, job_free_cb freecb, + void *data) { struct job *job; struct environ *env; @@ -104,17 +106,18 @@ job_run(const char *cmd, struct session *s, const char *cwd, job->pid = pid; job->status = 0; - LIST_INSERT_HEAD(&all_jobs, job, lentry); + LIST_INSERT_HEAD(&all_jobs, job, entry); - job->callbackfn = callbackfn; - job->freefn = freefn; + job->updatecb = updatecb; + job->completecb = completecb; + job->freecb = freecb; job->data = data; job->fd = out[0]; setblocking(job->fd, 0); - job->event = bufferevent_new(job->fd, NULL, job_write_callback, - job_callback, job); + job->event = bufferevent_new(job->fd, job_read_callback, + job_write_callback, job_error_callback, job); bufferevent_enable(job->event, EV_READ|EV_WRITE); log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); @@ -127,11 +130,11 @@ job_free(struct job *job) { log_debug("free job %p: %s", job, job->cmd); - LIST_REMOVE(job, lentry); + LIST_REMOVE(job, entry); free(job->cmd); - if (job->freefn != NULL && job->data != NULL) - job->freefn(job->data); + if (job->freecb != NULL && job->data != NULL) + job->freecb(job->data); if (job->pid != -1) kill(job->pid, SIGTERM); @@ -143,7 +146,21 @@ job_free(struct job *job) free(job); } -/* Called when output buffer falls below low watermark (default is 0). */ +/* Job buffer read callback. */ +static void +job_read_callback(__unused struct bufferevent *bufev, void *data) +{ + struct job *job = data; + + if (job->updatecb != NULL) + job->updatecb (job); +} + +/* + * Job buffer write callback. Fired when the buffer falls below watermark + * (default is empty). If all the data has been written, disable the write + * event. + */ static void job_write_callback(__unused struct bufferevent *bufev, void *data) { @@ -161,7 +178,7 @@ job_write_callback(__unused struct bufferevent *bufev, void *data) /* Job buffer error callback. */ static void -job_callback(__unused struct bufferevent *bufev, __unused short events, +job_error_callback(__unused struct bufferevent *bufev, __unused short events, void *data) { struct job *job = data; @@ -169,8 +186,8 @@ job_callback(__unused struct bufferevent *bufev, __unused short events, log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid); if (job->state == JOB_DEAD) { - if (job->callbackfn != NULL) - job->callbackfn(job); + if (job->completecb != NULL) + job->completecb(job); job_free(job); } else { bufferevent_disable(job->event, EV_READ); @@ -187,8 +204,8 @@ job_died(struct job *job, int status) job->status = status; if (job->state == JOB_CLOSED) { - if (job->callbackfn != NULL) - job->callbackfn(job); + if (job->completecb != NULL) + job->completecb(job); job_free(job); } else { job->pid = -1; diff --git a/server.c b/server.c index 0ce90db6..70f1a76f 100644 --- a/server.c +++ b/server.c @@ -134,7 +134,8 @@ server_create_socket(void) int server_start(struct event_base *base, int lockfd, char *lockfile) { - int pair[2]; + int pair[2]; + struct job *job; if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) fatal("socketpair failed"); @@ -180,6 +181,12 @@ server_start(struct event_base *base, int lockfd, char *lockfile) server_add_accept(0); proc_loop(server_proc, server_loop); + + LIST_FOREACH(job, &all_jobs, entry) { + if (job->pid != -1) + kill(job->pid, SIGTERM); + } + status_prompt_save_history(); exit(0); } @@ -400,7 +407,7 @@ server_child_exited(pid_t pid, int status) } } - LIST_FOREACH(job, &all_jobs, lentry) { + LIST_FOREACH(job, &all_jobs, entry) { if (pid == job->pid) { job_died(job, status); /* might free job */ break; diff --git a/tmux.1 b/tmux.1 index 681c2381..0dc02ef2 100644 --- a/tmux.1 +++ b/tmux.1 @@ -3490,6 +3490,8 @@ does not wait for .Ql #() commands to finish; instead, the previous result from running the same command is used, or a placeholder if the command has not been run before. +If the command hasn't exited, the most recent line of output will be used, but the status +line will not be updated more than once a second. Commands are executed with the .Nm global environment set (see the diff --git a/tmux.h b/tmux.h index e000081d..adad2edc 100644 --- a/tmux.h +++ b/tmux.h @@ -588,6 +588,10 @@ struct hook { }; /* Scheduled job. */ +struct job; +typedef void (*job_update_cb) (struct job *); +typedef void (*job_complete_cb) (struct job *); +typedef void (*job_free_cb) (void *); struct job { enum { JOB_RUNNING, @@ -595,18 +599,19 @@ struct job { JOB_CLOSED } state; - char *cmd; - pid_t pid; - int status; + char *cmd; + pid_t pid; + int status; - int fd; - struct bufferevent *event; + int fd; + struct bufferevent *event; - void (*callbackfn)(struct job *); - void (*freefn)(void *); - void *data; + job_update_cb updatecb; + job_complete_cb completecb; + job_free_cb freecb; + void *data; - LIST_ENTRY(job) lentry; + LIST_ENTRY(job) entry; }; LIST_HEAD(joblist, job); @@ -1601,10 +1606,10 @@ extern const struct options_table_entry options_table[]; /* job.c */ extern struct joblist all_jobs; -struct job *job_run(const char *, struct session *, const char *, - void (*)(struct job *), void (*)(void *), void *); -void job_free(struct job *); -void job_died(struct job *, int); +struct job *job_run(const char *, struct session *, const char *, + job_update_cb, job_complete_cb, job_free_cb, void *); +void job_free(struct job *); +void job_died(struct job *, int); /* environ.c */ struct environ *environ_create(void); diff --git a/window-copy.c b/window-copy.c index 9b13926e..0896479a 100644 --- a/window-copy.c +++ b/window-copy.c @@ -1641,7 +1641,7 @@ window_copy_copy_pipe(struct window_pane *wp, struct session *s, return; expanded = format_single(NULL, arg, NULL, s, NULL, wp); - job = job_run(expanded, s, NULL, NULL, NULL, NULL); + job = job_run(expanded, s, NULL, NULL, NULL, NULL, NULL); bufferevent_write(job->event, buf, len); free(expanded); From 0f25ad3ca32d329500ac830f3c13e60ead4cb3db Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 20 Apr 2017 09:39:07 +0000 Subject: [PATCH 2/3] There is no real need for window_printable_flags to allocate, make it return a buffer from the stack. --- format.c | 7 +------ tmux.h | 2 +- window.c | 8 ++++---- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/format.c b/format.c index 5c85f1ff..151c3c48 100644 --- a/format.c +++ b/format.c @@ -1237,17 +1237,14 @@ format_defaults_winlink(struct format_tree *ft, struct session *s, struct winlink *wl) { struct window *w = wl->window; - char *flags; if (ft->w == NULL) ft->w = wl->window; - flags = window_printable_flags(s, wl); - format_defaults_window(ft, w); format_add(ft, "window_index", "%d", wl->idx); - format_add(ft, "window_flags", "%s", flags); + format_add(ft, "window_flags", "%s", window_printable_flags(s, wl)); format_add(ft, "window_active", "%d", wl == s->curw); format_add(ft, "window_bell_flag", "%d", @@ -1259,8 +1256,6 @@ format_defaults_winlink(struct format_tree *ft, struct session *s, format_add(ft, "window_last_flag", "%d", !!(wl == TAILQ_FIRST(&s->lastw))); format_add(ft, "window_linked", "%d", session_is_linked(s, wl->window)); - - free(flags); } /* Set default format keys for a window pane. */ diff --git a/tmux.h b/tmux.h index adad2edc..b8a40df0 100644 --- a/tmux.h +++ b/tmux.h @@ -2120,7 +2120,7 @@ int window_pane_outside(struct window_pane *); int window_pane_visible(struct window_pane *); char *window_pane_search(struct window_pane *, const char *, u_int *); -char *window_printable_flags(struct session *, struct winlink *); +const char *window_printable_flags(struct session *, struct winlink *); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); diff --git a/window.c b/window.c index 0dea2b4a..b0be6220 100644 --- a/window.c +++ b/window.c @@ -710,11 +710,11 @@ window_destroy_panes(struct window *w) } /* Retuns the printable flags on a window, empty string if no flags set. */ -char * +const char * window_printable_flags(struct session *s, struct winlink *wl) { - char flags[32]; - int pos; + static char flags[32]; + int pos; pos = 0; if (wl->flags & WINLINK_ACTIVITY) @@ -732,7 +732,7 @@ window_printable_flags(struct session *s, struct winlink *wl) if (wl->window->flags & WINDOW_ZOOMED) flags[pos++] = 'Z'; flags[pos] = '\0'; - return (xstrdup(flags)); + return (flags); } struct window_pane * From 21993105e53da0f5aae583b494c83f1cbbf48b1b Mon Sep 17 00:00:00 2001 From: nicm Date: Thu, 20 Apr 2017 09:43:45 +0000 Subject: [PATCH 3/3] Now that struct winlink has a session pointer, can remove some arguments. --- format.c | 12 ++++++------ tmux.h | 2 +- window.c | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/format.c b/format.c index 151c3c48..b173df4c 100644 --- a/format.c +++ b/format.c @@ -71,7 +71,7 @@ static int format_replace(struct format_tree *, const char *, size_t, static void format_defaults_session(struct format_tree *, struct session *); static void format_defaults_client(struct format_tree *, struct client *); -static void format_defaults_winlink(struct format_tree *, struct session *, +static void format_defaults_winlink(struct format_tree *, struct winlink *); /* Entry in format job tree. */ @@ -1121,8 +1121,8 @@ format_defaults(struct format_tree *ft, struct client *c, struct session *s, format_defaults_client(ft, c); if (s != NULL) format_defaults_session(ft, s); - if (s != NULL && wl != NULL) - format_defaults_winlink(ft, s, wl); + if (wl != NULL) + format_defaults_winlink(ft, wl); if (wp != NULL) format_defaults_pane(ft, wp); } @@ -1233,9 +1233,9 @@ format_defaults_window(struct format_tree *ft, struct window *w) /* Set default format keys for a winlink. */ static void -format_defaults_winlink(struct format_tree *ft, struct session *s, - struct winlink *wl) +format_defaults_winlink(struct format_tree *ft, struct winlink *wl) { + struct session *s = wl->session; struct window *w = wl->window; if (ft->w == NULL) @@ -1244,7 +1244,7 @@ format_defaults_winlink(struct format_tree *ft, struct session *s, format_defaults_window(ft, w); format_add(ft, "window_index", "%d", wl->idx); - format_add(ft, "window_flags", "%s", window_printable_flags(s, wl)); + format_add(ft, "window_flags", "%s", window_printable_flags(wl)); format_add(ft, "window_active", "%d", wl == s->curw); format_add(ft, "window_bell_flag", "%d", diff --git a/tmux.h b/tmux.h index b8a40df0..22344fb8 100644 --- a/tmux.h +++ b/tmux.h @@ -2120,7 +2120,7 @@ int window_pane_outside(struct window_pane *); int window_pane_visible(struct window_pane *); char *window_pane_search(struct window_pane *, const char *, u_int *); -const char *window_printable_flags(struct session *, struct winlink *); +const char *window_printable_flags(struct winlink *); struct window_pane *window_pane_find_up(struct window_pane *); struct window_pane *window_pane_find_down(struct window_pane *); struct window_pane *window_pane_find_left(struct window_pane *); diff --git a/window.c b/window.c index b0be6220..5af2c273 100644 --- a/window.c +++ b/window.c @@ -709,12 +709,12 @@ window_destroy_panes(struct window *w) } } -/* Retuns the printable flags on a window, empty string if no flags set. */ const char * -window_printable_flags(struct session *s, struct winlink *wl) +window_printable_flags(struct winlink *wl) { - static char flags[32]; - int pos; + struct session *s = wl->session; + static char flags[32]; + int pos; pos = 0; if (wl->flags & WINLINK_ACTIVITY)