Discussion:
Regression with set-titles-string in 2.0?
Lander Brandt
2015-05-16 04:52:06 UTC
Permalink
When upgrading from tmux 1.9a to 2.0, shell commands are no longer executed in title strings. For example I have in my .tmux.conf:

set-option -g set-titles-string "#(whoami)@#H: $PWD \"#S\" (#W)#F [#I:#P]”

My title string ends up looking like: #(whoami)@myhostname.local: /Users/lander [..rest..]

A very nice Stack Overflow user (Barry Gackle) looked at the repo and found that the server_client_set_title method now uses format strings and does not allow “#()” anymore (as mentioned in commit cd9ccbc1e98b48fd8bfb64356664313a8eb1f7b0). I didn’t see this change noted anywhere.

This Stack Overflow question identifies the problem in more detail: https://stackoverflow.com/questions/30264679/tmux-titles-string-not-executing-shell-command
Thomas Adam
2015-05-16 18:01:54 UTC
Permalink
Post by Lander Brandt
When upgrading from tmux 1.9a to 2.0, shell commands are no longer
Yes, it will be replaced eventually.

-- Thomas Adam
--
"Deep in my heart I wish I was wrong. But deep in my heart I know I am
not." -- Morrissey ("Girl Least Likely To" -- off of Viva Hate.)
Nicholas Marriott
2015-05-21 10:55:42 UTC
Permalink
I think that all the status jobs stuff needs to be moved into formats,
but it needs a bit of thinking.

At the moment the jobs are run every status-interval with the
last two complete sets of output kept.

For formats it's a bit more fiddly because we could have many formats
coming from different places, some of them only used once.

I expect we probably want to do something vaguely similar but I'd say
have one cached set of outputs which is regularly cleaned of old entries
(every 5 minutes? 1 hour? 24 hours?). When a job is seen in a format, we
start it up and in the meantime use the value in the cache (or if it
isn't ready, something generic like '<job %d not ready>'. After it has
completed the first time, of course the old value will be ready (unless
it hasn't been run since the last clean).
Post by Thomas Adam
Post by Lander Brandt
When upgrading from tmux 1.9a to 2.0, shell commands are no longer
Yes, it will be replaced eventually.
-- Thomas Adam
--
"Deep in my heart I wish I was wrong. But deep in my heart I know I am
not." -- Morrissey ("Girl Least Likely To" -- off of Viva Hate.)
------------------------------------------------------------------------------
One dashboard for servers and applications across Physical-Virtual-Cloud
Widest out-of-the-box monitoring support with 50+ applications
Performance metrics, stats and reports that give you Actionable Insights
Deep dive visibility with transaction tracing using APM Insight.
http://ad.doubleclick.net/ddm/clk/290420510;117567292;y
_______________________________________________
tmux-users mailing list
https://lists.sourceforge.net/lists/listinfo/tmux-users
Nicholas Marriott
2015-05-21 11:30:54 UTC
Permalink
Post by Nicholas Marriott
I think that all the status jobs stuff needs to be moved into formats,
but it needs a bit of thinking.
Something like this:

Index: cmd-refresh-client.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/cmd-refresh-client.c,v
retrieving revision 1.13
diff -u -p -r1.13 cmd-refresh-client.c
--- cmd-refresh-client.c 20 Oct 2014 22:29:25 -0000 1.13
+++ cmd-refresh-client.c 21 May 2015 11:30:04 -0000
@@ -65,10 +65,9 @@ cmd_refresh_client_exec(struct cmd *self
}
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);
Index: format.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/format.c,v
retrieving revision 1.67
diff -u -p -r1.67 format.c
--- format.c 20 May 2015 06:39:02 -0000 1.67
+++ format.c 21 May 2015 11:30:04 -0000
@@ -35,6 +35,9 @@
* string.
*/

+void format_job_callback(struct job *);
+const char *format_job_get(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,31 @@ void format_defaults_client(struct form
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;
+
+ 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 +82,20 @@ 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;
+ 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,6 +160,94 @@ 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;
+
+ fj->job = NULL;
+ free(fj->out);
+
+ if (!WIFEXITED(job->status) && WEXITSTATUS(job->status) != 0) {
+ xasprintf(&fj->out, "<job '%s' exited with %d>", fj->cmd,
+ WEXITSTATUS(job->status));
+ return;
+ }
+ if (WIFSIGNALED(job->status)) {
+ xasprintf(&fj->out, "<job '%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;
+}
+
+/* Find a job. */
+const char *
+format_job_get(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->last = time(NULL);
+ xasprintf(&fj->out, "<job '%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, "<job '%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)
@@ -160,8 +274,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 +299,7 @@ format_add(struct format_tree *ft, const
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 +338,7 @@ format_find(struct format_tree *ft, cons
}

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 +472,9 @@ format_expand_time(struct format_tree *f
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 +497,34 @@ format_expand(struct format_tree *ft, co

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(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++) {
Index: server-client.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/server-client.c,v
retrieving revision 1.138
diff -u -p -r1.138 server-client.c
--- server-client.c 8 May 2015 15:56:49 -0000 1.138
+++ server-client.c 21 May 2015 11:30:05 -0000
@@ -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;
- }
}
}

Index: server.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/server.c,v
retrieving revision 1.122
diff -u -p -r1.122 server.c
--- server.c 24 Apr 2015 23:17:11 -0000 1.122
+++ server.c 21 May 2015 11:30:05 -0000
@@ -485,6 +485,8 @@ server_second_callback(unused int fd, un

server_client_status_timer();

+ format_clean();
+
evtimer_del(&server_ev_second);
memset(&tv, 0, sizeof tv);
tv.tv_sec = 1;
Index: status.c
===================================================================
RCS file: /cvs/src/usr.bin/tmux/status.c,v
retrieving revision 1.128
diff -u -p -r1.128 status.c
--- status.c 6 May 2015 23:56:46 -0000 1.128
+++ status.c 21 May 2015 11:30:05 -0000
@@ -33,13 +33,10 @@ char *status_redraw_get_left(struct cl
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 *);
@@ -370,8 +367,7 @@ out:

/* Replace a single special sequence (prefixed by #). */
void
-status_replace1(struct client *c, char **iptr, char **optr, char *out,
- size_t outsize)
+status_replace1(char **iptr, char **optr, char *out, size_t outsize)
{
char ch, tmp[256], *ptr, *endptr;
size_t ptrlen;
@@ -388,10 +384,6 @@ status_replace1(struct client *c, char *
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
@@ -462,7 +454,7 @@ status_replace(struct client *c, struct
*optr++ = ch;
continue;
}
- status_replace1(c, &iptr, &optr, out, sizeof out);
+ status_replace1(&iptr, &optr, out, sizeof out);
}
*optr = '\0';

@@ -471,141 +463,6 @@ status_replace(struct client *c, struct
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. */
Index: tmux.h
===================================================================
RCS file: /cvs/src/usr.bin/tmux/tmux.h,v
retrieving revision 1.514
diff -u -p -r1.514 tmux.h
--- tmux.h 12 May 2015 22:40:38 -0000 1.514
+++ tmux.h 21 May 2015 11:30:06 -0000
@@ -1475,6 +1475,7 @@ void cfg_show_causes(struct session *)

/* format.c */
struct format_tree;
+void format_clean(void);
struct format_tree *format_create(void);
void format_free(struct format_tree *);
void printflike(3, 4) format_add(struct format_tree *, const char *,
Loading...