Move the jobs output cache into the formats code so that #() work more

generally (for example, again working in set-titles-string).
pull/1/head
nicm 2015-05-27 13:28:04 +00:00
parent 7140cce7f3
commit 379400cfa6
7 changed files with 206 additions and 269 deletions

View File

@ -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);

192
format.c
View File

@ -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;
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 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);
/* 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++) {

View File

@ -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;
}
}
}

View File

@ -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;

236
status.c
View File

@ -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++;
if (ch != '#' || *iptr == '\0') {
*optr++ = ch;
continue;
}
status_replace1(c, &iptr, &optr, out, sizeof out);
}
*optr = '\0';
ft = format_create();
ft = format_create_status(1);
format_defaults(ft, c, NULL, wl, NULL);
expanded = format_expand(ft, out);
expanded = format_expand_time(ft, fmt, t);
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,

32
tmux.1
View File

@ -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"

2
tmux.h
View File

@ -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 *, ...);