summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornicm <nicm>2015-05-27 13:28:04 +0000
committernicm <nicm>2015-05-27 13:28:04 +0000
commit379400cfa69f8df9ac13b070c60d5f8a282ddf6e (patch)
treed67394e5c975bae54fc5da0c95b2447ea1b48d09
parent7140cce7f31eb2135491fdc62645b4753d941520 (diff)
Move the jobs output cache into the formats code so that #() work more
generally (for example, again working in set-titles-string).
-rw-r--r--cmd-refresh-client.c5
-rw-r--r--format.c192
-rw-r--r--server-client.c6
-rw-r--r--server.c2
-rw-r--r--status.c234
-rw-r--r--tmux.132
-rw-r--r--tmux.h2
7 files changed, 205 insertions, 268 deletions
diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c
index f693872c..b6d5d624 100644
--- a/cmd-refresh-client.c
+++ b/cmd-refresh-client.c
@@ -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/format.c b/format.c
index eff1668d..4fe1e924 100644
--- a/format.c
+++ b/format.c
@@ -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/server-client.c b/server-client.c
index dd6eda1d..36408e91 100644
--- a/server-client.c
+++ b/server-client.c
@@ -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/server.c b/server.c
index ba161c4b..a5cd50a7 100644
--- a/server.c
+++ b/server.c
@@ -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/status.c b/status.c
index 9bd5111c..0ffc0427 100644
--- a/status.c
+++ b/status.c
@@ -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/tmux.1 b/tmux.1
index f5a0aaa3..1233d1be 100644
--- a/tmux.1
+++ b/tmux.1
@@ -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/tmux.h b/tmux.h
index bc47a268..78e64170 100644
--- a/tmux.h
+++ b/tmux.h
@@ -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 *, ...);