From afe199e1f5f87d4840cbc37a990f93ac3a500fb1 Mon Sep 17 00:00:00 2001 From: nicm Date: Wed, 27 May 2015 13:28:04 +0000 Subject: [PATCH] Move the jobs output cache into the formats code so that #() work more generally (for example, again working in set-titles-string). --- usr.bin/tmux/cmd-refresh-client.c | 7 +- usr.bin/tmux/format.c | 194 +++++++++++++++++++++--- usr.bin/tmux/server-client.c | 8 +- usr.bin/tmux/server.c | 4 +- usr.bin/tmux/status.c | 236 +----------------------------- usr.bin/tmux/tmux.1 | 36 ++--- usr.bin/tmux/tmux.h | 4 +- 7 files changed, 213 insertions(+), 276 deletions(-) diff --git a/usr.bin/tmux/cmd-refresh-client.c b/usr.bin/tmux/cmd-refresh-client.c index f4e85ca9f5b..f1b9504430b 100644 --- a/usr.bin/tmux/cmd-refresh-client.c +++ b/usr.bin/tmux/cmd-refresh-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: cmd-refresh-client.c,v 1.13 2014/10/20 22:29:25 nicm Exp $ */ +/* $OpenBSD: cmd-refresh-client.c,v 1.14 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -65,10 +65,9 @@ cmd_refresh_client_exec(struct cmd *self, struct cmd_q *cmdq) } if (tty_set_size(&c->tty, w, h)) recalculate_sizes(); - } else if (args_has(args, 'S')) { - status_update_jobs(c); + } else if (args_has(args, 'S')) server_status_client(c); - } else + else server_redraw_client(c); return (CMD_RETURN_NORMAL); diff --git a/usr.bin/tmux/format.c b/usr.bin/tmux/format.c index 9bf9a1cd927..f7bbd8971e1 100644 --- a/usr.bin/tmux/format.c +++ b/usr.bin/tmux/format.c @@ -1,4 +1,4 @@ -/* $OpenBSD: format.c,v 1.67 2015/05/20 06:39:02 nicm Exp $ */ +/* $OpenBSD: format.c,v 1.68 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2011 Nicholas Marriott @@ -35,6 +35,9 @@ * string. */ +void format_job_callback(struct job *); +const char *format_job_get(struct format_tree *, const char *); + int format_replace(struct format_tree *, const char *, size_t, char **, size_t *, size_t *); char *format_time_string(time_t); @@ -46,6 +49,32 @@ void format_defaults_client(struct format_tree *, struct client *); void format_defaults_winlink(struct format_tree *, struct session *, struct winlink *); +/* Entry in format job tree. */ +struct format_job { + const char *cmd; + + time_t last; + char *out; + + struct job *job; + int status; + + RB_ENTRY(format_job) entry; +}; + +/* Format job tree. */ +int format_job_cmp(struct format_job *, struct format_job *); +RB_HEAD(format_job_tree, format_job) format_jobs = RB_INITIALIZER(); +RB_PROTOTYPE(format_job_tree, format_job, entry, format_job_cmp); +RB_GENERATE(format_job_tree, format_job, entry, format_job_cmp); + +/* Format job tree comparison function. */ +int +format_job_cmp(struct format_job *fj1, struct format_job *fj2) +{ + return (strcmp(fj1->cmd, fj2->cmd)); +} + /* Entry in format tree. */ struct format_entry { char *key; @@ -54,22 +83,22 @@ struct format_entry { RB_ENTRY(format_entry) entry; }; -/* Tree of format entries. */ +/* Format entry tree. */ struct format_tree { struct window *w; struct session *s; - RB_HEAD(format_rb_tree, format_entry) tree; -}; + int status; -/* Format key-value replacement entry. */ -int format_cmp(struct format_entry *, struct format_entry *); -RB_PROTOTYPE(format_rb_tree, format_entry, entry, format_cmp); -RB_GENERATE(format_rb_tree, format_entry, entry, format_cmp); + RB_HEAD(format_entry_tree, format_entry) tree; +}; +int format_entry_cmp(struct format_entry *, struct format_entry *); +RB_PROTOTYPE(format_entry_tree, format_entry, entry, format_entry_cmp); +RB_GENERATE(format_entry_tree, format_entry, entry, format_entry_cmp); -/* Format tree comparison function. */ +/* Format entry tree comparison function. */ int -format_cmp(struct format_entry *fe1, struct format_entry *fe2) +format_entry_cmp(struct format_entry *fe1, struct format_entry *fe2) { return (strcmp(fe1->key, fe2->key)); } @@ -134,15 +163,118 @@ const char *format_lower[] = { NULL /* z */ }; +/* Format job callback. */ +void +format_job_callback(struct job *job) +{ + struct format_job *fj = job->data; + char *line, *buf; + size_t len; + struct client *c; + + fj->job = NULL; + free(fj->out); + + if (WIFEXITED(job->status) && WEXITSTATUS(job->status) != 0) { + xasprintf(&fj->out, "<'%s' exited with %d>", fj->cmd, + WEXITSTATUS(job->status)); + return; + } + if (WIFSIGNALED(job->status)) { + xasprintf(&fj->out, "<'%s' got signal %d>", fj->cmd, + WTERMSIG(job->status)); + return; + } + + buf = NULL; + if ((line = evbuffer_readline(job->event->input)) == NULL) { + len = EVBUFFER_LENGTH(job->event->input); + buf = xmalloc(len + 1); + if (len != 0) + memcpy(buf, EVBUFFER_DATA(job->event->input), len); + buf[len] = '\0'; + } else + buf = line; + fj->out = buf; + + if (fj->status) { + TAILQ_FOREACH(c, &clients, entry) + server_status_client(c); + fj->status = 0; + } +} + +/* Find a job. */ +const char * +format_job_get(struct format_tree *ft, const char *cmd) +{ + struct format_job fj0, *fj; + + fj0.cmd = cmd; + if ((fj = RB_FIND(format_job_tree, &format_jobs, &fj0)) == NULL) + { + fj = xcalloc(1, sizeof *fj); + fj->cmd = xstrdup(cmd); + fj->status = ft->status; + + xasprintf(&fj->out, "<'%s' not ready>", fj->cmd); + + RB_INSERT(format_job_tree, &format_jobs, fj); + } + + if (fj->job == NULL && fj->last != time(NULL)) { + fj->job = job_run(fj->cmd, NULL, -1, format_job_callback, + NULL, fj); + if (fj->job == NULL) { + free(fj->out); + xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); + } + } + fj->last = time(NULL); + + return (fj->out); +} + +/* Remove old jobs. */ +void +format_clean(void) +{ + struct format_job *fj, *fj1; + time_t now; + + now = time(NULL); + RB_FOREACH_SAFE(fj, format_job_tree, &format_jobs, fj1) { + if (fj->last > now || now - fj->last < 3600) + continue; + RB_REMOVE(format_job_tree, &format_jobs, fj); + + if (fj->job != NULL) + job_free(fj->job); + + free((void*)fj->cmd); + free(fj->out); + + free(fj); + } +} + /* Create a new tree. */ struct format_tree * format_create(void) +{ + return (format_create_status(0)); +} + +/* Create a new tree for the status line. */ +struct format_tree * +format_create_status(int status) { struct format_tree *ft; - char host[HOST_NAME_MAX+1], *ptr; + char host[HOST_NAME_MAX + 1], *ptr; ft = xcalloc(1, sizeof *ft); RB_INIT(&ft->tree); + ft->status = status; if (gethostname(host, sizeof host) == 0) { format_add(ft, "host", "%s", host); @@ -160,8 +292,8 @@ format_free(struct format_tree *ft) { struct format_entry *fe, *fe1; - RB_FOREACH_SAFE(fe, format_rb_tree, &ft->tree, fe1) { - RB_REMOVE(format_rb_tree, &ft->tree, fe); + RB_FOREACH_SAFE(fe, format_entry_tree, &ft->tree, fe1) { + RB_REMOVE(format_entry_tree, &ft->tree, fe); free(fe->value); free(fe->key); free(fe); @@ -185,7 +317,7 @@ format_add(struct format_tree *ft, const char *key, const char *fmt, ...) xvasprintf(&fe->value, fmt, ap); va_end(ap); - fe_now = RB_INSERT(format_rb_tree, &ft->tree, fe); + fe_now = RB_INSERT(format_entry_tree, &ft->tree, fe); if (fe_now != NULL) { free(fe_now->value); fe_now->value = fe->value; @@ -224,7 +356,7 @@ format_find(struct format_tree *ft, const char *key) } fe_find.key = (char *) key; - fe = RB_FIND(format_rb_tree, &ft->tree, &fe_find); + fe = RB_FIND(format_entry_tree, &ft->tree, &fe_find); if (fe == NULL) return (NULL); return (fe->value); @@ -358,9 +490,9 @@ format_expand_time(struct format_tree *ft, const char *fmt, time_t t) char * format_expand(struct format_tree *ft, const char *fmt) { - char *buf; + char *buf, *tmp; const char *ptr, *s; - size_t off, len, n; + size_t off, len, n, slen; int ch, brackets; if (fmt == NULL) @@ -383,6 +515,34 @@ format_expand(struct format_tree *ft, const char *fmt) ch = (u_char) *fmt++; switch (ch) { + case '(': + brackets = 1; + for (ptr = fmt; *ptr != '\0'; ptr++) { + if (*ptr == '(') + brackets++; + if (*ptr == ')' && --brackets == 0) + break; + } + if (*ptr != ')' || brackets != 0) + break; + n = ptr - fmt; + + tmp = xmalloc(n + 1); + memcpy(tmp, fmt, n); + tmp[n] = '\0'; + + s = format_job_get(ft, tmp); + slen = strlen(s); + + while (len - off < slen + 1) { + buf = xreallocarray(buf, 2, len); + len *= 2; + } + memcpy(buf + off, s, slen); + off += slen; + + fmt += n + 1; + continue; case '{': brackets = 1; for (ptr = fmt; *ptr != '\0'; ptr++) { diff --git a/usr.bin/tmux/server-client.c b/usr.bin/tmux/server-client.c index 7e873627922..731b9ea1cf2 100644 --- a/usr.bin/tmux/server-client.c +++ b/usr.bin/tmux/server-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server-client.c,v 1.138 2015/05/08 15:56:49 nicm Exp $ */ +/* $OpenBSD: server-client.c,v 1.139 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2009 Nicholas Marriott @@ -157,8 +157,6 @@ server_client_lost(struct client *c) if (c->stderr_data != c->stdout_data) evbuffer_free(c->stderr_data); - status_free_jobs(&c->status_new); - status_free_jobs(&c->status_old); screen_free(&c->status); free(c->title); @@ -269,10 +267,8 @@ server_client_status_timer(void) interval = options_get_number(&s->options, "status-interval"); difference = tv.tv_sec - c->status_timer.tv_sec; - if (interval != 0 && difference >= interval) { - status_update_jobs(c); + if (interval != 0 && difference >= interval) c->flags |= CLIENT_STATUS; - } } } diff --git a/usr.bin/tmux/server.c b/usr.bin/tmux/server.c index 0a652a7d2f9..34ec540658a 100644 --- a/usr.bin/tmux/server.c +++ b/usr.bin/tmux/server.c @@ -1,4 +1,4 @@ -/* $OpenBSD: server.c,v 1.122 2015/04/24 23:17:11 nicm Exp $ */ +/* $OpenBSD: server.c,v 1.123 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -485,6 +485,8 @@ server_second_callback(unused int fd, unused short events, unused void *arg) server_client_status_timer(); + format_clean(); + evtimer_del(&server_ev_second); memset(&tv, 0, sizeof tv); tv.tv_sec = 1; diff --git a/usr.bin/tmux/status.c b/usr.bin/tmux/status.c index 7b8885e03e3..ae0ea06976e 100644 --- a/usr.bin/tmux/status.c +++ b/usr.bin/tmux/status.c @@ -1,4 +1,4 @@ -/* $OpenBSD: status.c,v 1.128 2015/05/06 23:56:46 nicm Exp $ */ +/* $OpenBSD: status.c,v 1.129 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -33,13 +33,10 @@ char *status_redraw_get_left(struct client *, time_t, int, struct grid_cell *, size_t *); char *status_redraw_get_right(struct client *, time_t, int, struct grid_cell *, size_t *); -char *status_find_job(struct client *, char **); -void status_job_free(void *); -void status_job_callback(struct job *); char *status_print(struct client *, struct winlink *, time_t, struct grid_cell *); char *status_replace(struct client *, struct winlink *, const char *, time_t); -void status_replace1(struct client *, char **, char **, char *, size_t); +void status_replace1(char **, char **, char *, size_t); void status_message_callback(int, short, void *); const char *status_prompt_up_history(u_int *); @@ -368,246 +365,25 @@ out: return (1); } -/* Replace a single special sequence (prefixed by #). */ -void -status_replace1(struct client *c, char **iptr, char **optr, char *out, - size_t outsize) -{ - char ch, tmp[256], *ptr, *endptr; - size_t ptrlen; - long limit; - - errno = 0; - limit = strtol(*iptr, &endptr, 10); - if ((limit == 0 && errno != EINVAL) || - (limit == LONG_MIN && errno != ERANGE) || - (limit == LONG_MAX && errno != ERANGE) || - limit != 0) - *iptr = endptr; - if (limit <= 0) - limit = LONG_MAX; - - switch (*(*iptr)++) { - case '(': - if ((ptr = status_find_job(c, iptr)) == NULL) - return; - goto do_replace; - case '[': - /* - * Embedded style, handled at display time. Leave present and - * skip input until ]. - */ - ch = ']'; - goto skip_to; - case '{': - ptr = (char *) "#{"; - goto do_replace; - default: - xsnprintf(tmp, sizeof tmp, "#%c", *(*iptr - 1)); - ptr = tmp; - goto do_replace; - } - - return; - -do_replace: - ptrlen = strlen(ptr); - if ((size_t) limit < ptrlen) - ptrlen = limit; - - if (*optr + ptrlen >= out + outsize - 1) - return; - while (ptrlen > 0 && *ptr != '\0') { - *(*optr)++ = *ptr++; - ptrlen--; - } - - return; - -skip_to: - *(*optr)++ = '#'; - - (*iptr)--; /* include ch */ - while (**iptr != ch && **iptr != '\0') { - if (*optr >= out + outsize - 1) - break; - *(*optr)++ = *(*iptr)++; - } -} - /* Replace special sequences in fmt. */ char * status_replace(struct client *c, struct winlink *wl, const char *fmt, time_t t) { - static char out[BUFSIZ]; - char in[BUFSIZ], ch, *iptr, *optr, *expanded; - size_t len; struct format_tree *ft; + char *expanded; if (fmt == NULL) return (xstrdup("")); - len = strftime(in, sizeof in, fmt, localtime(&t)); - in[len] = '\0'; - - iptr = in; - optr = out; - - while (*iptr != '\0') { - if (optr >= out + (sizeof out) - 1) - break; - ch = *iptr++; + ft = format_create_status(1); + format_defaults(ft, c, NULL, wl, NULL); - if (ch != '#' || *iptr == '\0') { - *optr++ = ch; - continue; - } - status_replace1(c, &iptr, &optr, out, sizeof out); - } - *optr = '\0'; + expanded = format_expand_time(ft, fmt, t); - ft = format_create(); - format_defaults(ft, c, NULL, wl, NULL); - expanded = format_expand(ft, out); format_free(ft); return (expanded); } -/* Figure out job name and get its result, starting it off if necessary. */ -char * -status_find_job(struct client *c, char **iptr) -{ - struct status_out *so, so_find; - char *cmd; - int lastesc; - size_t len; - - if (**iptr == '\0') - return (NULL); - if (**iptr == ')') { /* no command given */ - (*iptr)++; - return (NULL); - } - - cmd = xmalloc(strlen(*iptr) + 1); - len = 0; - - lastesc = 0; - for (; **iptr != '\0'; (*iptr)++) { - if (!lastesc && **iptr == ')') - break; /* unescaped ) is the end */ - if (!lastesc && **iptr == '\\') { - lastesc = 1; - continue; /* skip \ if not escaped */ - } - lastesc = 0; - cmd[len++] = **iptr; - } - if (**iptr == '\0') /* no terminating ) */ { - free(cmd); - return (NULL); - } - (*iptr)++; /* skip final ) */ - cmd[len] = '\0'; - - /* First try in the new tree. */ - so_find.cmd = cmd; - so = RB_FIND(status_out_tree, &c->status_new, &so_find); - if (so != NULL && so->out != NULL) { - free(cmd); - return (so->out); - } - - /* If not found at all, start the job and add to the tree. */ - if (so == NULL) { - job_run(cmd, NULL, -1, status_job_callback, status_job_free, c); - c->references++; - - so = xmalloc(sizeof *so); - so->cmd = xstrdup(cmd); - so->out = NULL; - RB_INSERT(status_out_tree, &c->status_new, so); - } - - /* Lookup in the old tree. */ - so_find.cmd = cmd; - so = RB_FIND(status_out_tree, &c->status_old, &so_find); - free(cmd); - if (so != NULL) - return (so->out); - return (NULL); -} - -/* Free job tree. */ -void -status_free_jobs(struct status_out_tree *sotree) -{ - struct status_out *so, *so_next; - - so_next = RB_MIN(status_out_tree, sotree); - while (so_next != NULL) { - so = so_next; - so_next = RB_NEXT(status_out_tree, sotree, so); - - RB_REMOVE(status_out_tree, sotree, so); - free(so->out); - free(so->cmd); - free(so); - } -} - -/* Update jobs on status interval. */ -void -status_update_jobs(struct client *c) -{ - /* Free the old tree. */ - status_free_jobs(&c->status_old); - - /* Move the new to old. */ - memcpy(&c->status_old, &c->status_new, sizeof c->status_old); - RB_INIT(&c->status_new); -} - -/* Free status job. */ -void -status_job_free(void *data) -{ - struct client *c = data; - - c->references--; -} - -/* Job has finished: save its result. */ -void -status_job_callback(struct job *job) -{ - struct client *c = job->data; - struct status_out *so, so_find; - char *line, *buf; - size_t len; - - if (c->flags & CLIENT_DEAD) - return; - - so_find.cmd = job->cmd; - so = RB_FIND(status_out_tree, &c->status_new, &so_find); - if (so == NULL || so->out != NULL) - return; - - buf = NULL; - if ((line = evbuffer_readline(job->event->input)) == NULL) { - len = EVBUFFER_LENGTH(job->event->input); - buf = xmalloc(len + 1); - if (len != 0) - memcpy(buf, EVBUFFER_DATA(job->event->input), len); - buf[len] = '\0'; - } else - buf = line; - - so->out = buf; - server_status_client(c); -} - /* Return winlink status line entry and adjust gc as necessary. */ char * status_print(struct client *c, struct winlink *wl, time_t t, diff --git a/usr.bin/tmux/tmux.1 b/usr.bin/tmux/tmux.1 index 7ca49f01b67..6d0fd14deca 100644 --- a/usr.bin/tmux/tmux.1 +++ b/usr.bin/tmux/tmux.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: tmux.1,v 1.430 2015/05/12 15:29:29 nicm Exp $ +.\" $OpenBSD: tmux.1,v 1.431 2015/05/27 13:28:04 nicm Exp $ .\" .\" Copyright (c) 2007 Nicholas Marriott .\" @@ -14,7 +14,7 @@ .\" IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING .\" OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: May 12 2015 $ +.Dd $Mdocdate: May 27 2015 $ .Dt TMUX 1 .Os .Sh NAME @@ -2662,25 +2662,10 @@ will be expanded. It may also contain any of the following special character sequences: .Bl -column "Character pair" "Replaced with" -offset indent .It Sy "Character pair" Ta Sy "Replaced with" -.It Li "#(shell-command)" Ta "First line of the command's output" .It Li "#[attributes]" Ta "Colour or attribute change" .It Li "##" Ta "A literal" Ql # .El .Pp -The #(shell-command) form executes -.Ql shell-command -and inserts the first line of its output. -Note that shell commands are only executed once at the interval specified by -the -.Ic status-interval -option: if the status line is redrawn in the meantime, the previous result is -used. -Shell commands are executed with the -.Nm -global environment set (see the -.Sx ENVIRONMENT -section). -.Pp For details on how the names and titles can be set see the .Sx "NAMES AND TITLES" section. @@ -3245,6 +3230,23 @@ a number and a colon, so .Ql #{=10:pane_title} will include at most the first 10 characters of the pane title. .Pp +In addition, the first line of a shell command's output may be inserted using +.Ql #() . +For example, +.Ql #(uptime) +will insert the system's uptime. +When constructing formats, +.Nm +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. +Commands are executed with the +.Nm +global environment set (see the +.Sx ENVIRONMENT +section). +.Pp The following variables are available, where appropriate: .Bl -column "XXXXXXXXXXXXXXXXXXX" "XXXXX" .It Sy "Variable name" Ta Sy "Alias" Ta Sy "Replaced with" diff --git a/usr.bin/tmux/tmux.h b/usr.bin/tmux/tmux.h index c627af1913d..a79ac4a887d 100644 --- a/usr.bin/tmux/tmux.h +++ b/usr.bin/tmux/tmux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: tmux.h,v 1.514 2015/05/12 22:40:38 nicm Exp $ */ +/* $OpenBSD: tmux.h,v 1.515 2015/05/27 13:28:04 nicm Exp $ */ /* * Copyright (c) 2007 Nicholas Marriott @@ -1475,7 +1475,9 @@ void cfg_show_causes(struct session *); /* format.c */ struct format_tree; +void format_clean(void); struct format_tree *format_create(void); +struct format_tree *format_create_status(int); void format_free(struct format_tree *); void printflike(3, 4) format_add(struct format_tree *, const char *, const char *, ...); -- 2.20.1