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...
This commit is contained in:
nicm 2017-04-20 09:20:22 +00:00
parent f184c6f06c
commit 0b44ad99b5
9 changed files with 106 additions and 46 deletions

View File

@ -127,8 +127,8 @@ cmd_if_shell_exec(struct cmd *self, struct cmdq_item *item)
cdata->item = NULL; cdata->item = NULL;
memcpy(&cdata->mouse, &item->mouse, sizeof cdata->mouse); memcpy(&cdata->mouse, &item->mouse, sizeof cdata->mouse);
job_run(shellcmd, s, cwd, cmd_if_shell_callback, cmd_if_shell_free, job_run(shellcmd, s, cwd, NULL, cmd_if_shell_callback,
cdata); cmd_if_shell_free, cdata);
free(shellcmd); free(shellcmd);
if (args_has(args, 'b')) if (args_has(args, 'b'))

View File

@ -110,8 +110,8 @@ cmd_run_shell_exec(struct cmd *self, struct cmdq_item *item)
if (!args_has(args, 'b')) if (!args_has(args, 'b'))
cdata->item = item; cdata->item = item;
job_run(cdata->cmd, s, cwd, cmd_run_shell_callback, cmd_run_shell_free, job_run(cdata->cmd, s, cwd, NULL, cmd_run_shell_callback,
cdata); cmd_run_shell_free, cdata);
if (args_has(args, 'b')) if (args_has(args, 'b'))
return (CMD_RETURN_NORMAL); return (CMD_RETURN_NORMAL);

View File

@ -76,7 +76,7 @@ cmd_show_messages_jobs(struct cmdq_item *item, int blank)
u_int n; u_int n;
n = 0; n = 0;
LIST_FOREACH(job, &all_jobs, lentry) { LIST_FOREACH(job, &all_jobs, entry) {
if (blank) { if (blank) {
cmdq_print(item, "%s", ""); cmdq_print(item, "%s", "");
blank = 0; blank = 0;

View File

@ -39,7 +39,6 @@
struct format_entry; struct format_entry;
typedef void (*format_cb)(struct format_tree *, 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 char *format_job_get(struct format_tree *, const char *);
static void format_job_timer(int, short, void *); static void format_job_timer(int, short, void *);
@ -83,6 +82,7 @@ struct format_job {
time_t last; time_t last;
char *out; char *out;
int updated;
struct job *job; struct job *job;
int status; int status;
@ -203,9 +203,35 @@ static const char *format_lower[] = {
NULL /* z */ NULL /* z */
}; };
/* Format job callback. */ /* Format job update callback. */
static void 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; struct format_job *fj = job->data;
char *line, *buf; char *line, *buf;
@ -213,7 +239,6 @@ format_job_callback(struct job *job)
struct client *c; struct client *c;
fj->job = NULL; fj->job = NULL;
free(fj->out);
buf = NULL; buf = NULL;
if ((line = evbuffer_readline(job->event->input)) == NULL) { if ((line = evbuffer_readline(job->event->input)) == NULL) {
@ -224,15 +249,19 @@ format_job_callback(struct job *job)
buf[len] = '\0'; buf[len] = '\0';
} else } else
buf = line; 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) { if (fj->status) {
TAILQ_FOREACH(c, &clients, entry) TAILQ_FOREACH(c, &clients, entry)
server_status_client(c); server_status_client(c);
fj->status = 0; fj->status = 0;
} }
log_debug("%s: %s: %s", __func__, fj->cmd, fj->out);
} }
/* Find a job. */ /* Find a job. */
@ -267,8 +296,8 @@ format_job_get(struct format_tree *ft, const char *cmd)
t = time(NULL); t = time(NULL);
if (fj->job == NULL && (force || fj->last != t)) { if (fj->job == NULL && (force || fj->last != t)) {
fj->job = job_run(expanded, NULL, NULL, format_job_callback, fj->job = job_run(expanded, NULL, NULL, format_job_update,
NULL, fj); format_job_complete, NULL, fj);
if (fj->job == NULL) { if (fj->job == NULL) {
free(fj->out); free(fj->out);
xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd); xasprintf(&fj->out, "<'%s' didn't start>", fj->cmd);

49
job.c
View File

@ -33,8 +33,9 @@
* output. * 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_write_callback(struct bufferevent *, void *);
static void job_error_callback(struct bufferevent *, short, void *);
/* All jobs list. */ /* All jobs list. */
struct joblist all_jobs = LIST_HEAD_INITIALIZER(all_jobs); 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. */ /* Start a job running, if it isn't already. */
struct job * struct job *
job_run(const char *cmd, struct session *s, const char *cwd, 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 job *job;
struct environ *env; struct environ *env;
@ -104,17 +106,18 @@ job_run(const char *cmd, struct session *s, const char *cwd,
job->pid = pid; job->pid = pid;
job->status = 0; job->status = 0;
LIST_INSERT_HEAD(&all_jobs, job, lentry); LIST_INSERT_HEAD(&all_jobs, job, entry);
job->callbackfn = callbackfn; job->updatecb = updatecb;
job->freefn = freefn; job->completecb = completecb;
job->freecb = freecb;
job->data = data; job->data = data;
job->fd = out[0]; job->fd = out[0];
setblocking(job->fd, 0); setblocking(job->fd, 0);
job->event = bufferevent_new(job->fd, NULL, job_write_callback, job->event = bufferevent_new(job->fd, job_read_callback,
job_callback, job); job_write_callback, job_error_callback, job);
bufferevent_enable(job->event, EV_READ|EV_WRITE); bufferevent_enable(job->event, EV_READ|EV_WRITE);
log_debug("run job %p: %s, pid %ld", job, job->cmd, (long) job->pid); 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); log_debug("free job %p: %s", job, job->cmd);
LIST_REMOVE(job, lentry); LIST_REMOVE(job, entry);
free(job->cmd); free(job->cmd);
if (job->freefn != NULL && job->data != NULL) if (job->freecb != NULL && job->data != NULL)
job->freefn(job->data); job->freecb(job->data);
if (job->pid != -1) if (job->pid != -1)
kill(job->pid, SIGTERM); kill(job->pid, SIGTERM);
@ -143,7 +146,21 @@ job_free(struct job *job)
free(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 static void
job_write_callback(__unused struct bufferevent *bufev, void *data) 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. */ /* Job buffer error callback. */
static void static void
job_callback(__unused struct bufferevent *bufev, __unused short events, job_error_callback(__unused struct bufferevent *bufev, __unused short events,
void *data) void *data)
{ {
struct job *job = 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); log_debug("job error %p: %s, pid %ld", job, job->cmd, (long) job->pid);
if (job->state == JOB_DEAD) { if (job->state == JOB_DEAD) {
if (job->callbackfn != NULL) if (job->completecb != NULL)
job->callbackfn(job); job->completecb(job);
job_free(job); job_free(job);
} else { } else {
bufferevent_disable(job->event, EV_READ); bufferevent_disable(job->event, EV_READ);
@ -187,8 +204,8 @@ job_died(struct job *job, int status)
job->status = status; job->status = status;
if (job->state == JOB_CLOSED) { if (job->state == JOB_CLOSED) {
if (job->callbackfn != NULL) if (job->completecb != NULL)
job->callbackfn(job); job->completecb(job);
job_free(job); job_free(job);
} else { } else {
job->pid = -1; job->pid = -1;

View File

@ -134,7 +134,8 @@ server_create_socket(void)
int int
server_start(struct event_base *base, int lockfd, char *lockfile) 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) if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0)
fatal("socketpair failed"); fatal("socketpair failed");
@ -180,6 +181,12 @@ server_start(struct event_base *base, int lockfd, char *lockfile)
server_add_accept(0); server_add_accept(0);
proc_loop(server_proc, server_loop); proc_loop(server_proc, server_loop);
LIST_FOREACH(job, &all_jobs, entry) {
if (job->pid != -1)
kill(job->pid, SIGTERM);
}
status_prompt_save_history(); status_prompt_save_history();
exit(0); 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) { if (pid == job->pid) {
job_died(job, status); /* might free job */ job_died(job, status); /* might free job */
break; break;

2
tmux.1
View File

@ -3490,6 +3490,8 @@ does not wait for
.Ql #() .Ql #()
commands to finish; instead, the previous result from running the same command is used, 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. 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 Commands are executed with the
.Nm .Nm
global environment set (see the global environment set (see the

31
tmux.h
View File

@ -588,6 +588,10 @@ struct hook {
}; };
/* Scheduled job. */ /* 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 { struct job {
enum { enum {
JOB_RUNNING, JOB_RUNNING,
@ -595,18 +599,19 @@ struct job {
JOB_CLOSED JOB_CLOSED
} state; } state;
char *cmd; char *cmd;
pid_t pid; pid_t pid;
int status; int status;
int fd; int fd;
struct bufferevent *event; struct bufferevent *event;
void (*callbackfn)(struct job *); job_update_cb updatecb;
void (*freefn)(void *); job_complete_cb completecb;
void *data; job_free_cb freecb;
void *data;
LIST_ENTRY(job) lentry; LIST_ENTRY(job) entry;
}; };
LIST_HEAD(joblist, job); LIST_HEAD(joblist, job);
@ -1601,10 +1606,10 @@ extern const struct options_table_entry options_table[];
/* job.c */ /* job.c */
extern struct joblist all_jobs; extern struct joblist all_jobs;
struct job *job_run(const char *, struct session *, const char *, struct job *job_run(const char *, struct session *, const char *,
void (*)(struct job *), void (*)(void *), void *); job_update_cb, job_complete_cb, job_free_cb, void *);
void job_free(struct job *); void job_free(struct job *);
void job_died(struct job *, int); void job_died(struct job *, int);
/* environ.c */ /* environ.c */
struct environ *environ_create(void); struct environ *environ_create(void);

View File

@ -1641,7 +1641,7 @@ window_copy_copy_pipe(struct window_pane *wp, struct session *s,
return; return;
expanded = format_single(NULL, arg, NULL, s, NULL, wp); 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); bufferevent_write(job->event, buf, len);
free(expanded); free(expanded);