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...
pull/865/head
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;
memcpy(&cdata->mouse, &item->mouse, sizeof cdata->mouse);
job_run(shellcmd, s, cwd, cmd_if_shell_callback, cmd_if_shell_free,
cdata);
job_run(shellcmd, s, cwd, NULL, cmd_if_shell_callback,
cmd_if_shell_free, cdata);
free(shellcmd);
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'))
cdata->item = item;
job_run(cdata->cmd, s, cwd, cmd_run_shell_callback, cmd_run_shell_free,
cdata);
job_run(cdata->cmd, s, cwd, NULL, cmd_run_shell_callback,
cmd_run_shell_free, cdata);
if (args_has(args, 'b'))
return (CMD_RETURN_NORMAL);

View File

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

View File

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

49
job.c
View File

@ -33,8 +33,9 @@
* 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_error_callback(struct bufferevent *, short, void *);
/* All jobs list. */
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. */
struct job *
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 environ *env;
@ -104,17 +106,18 @@ job_run(const char *cmd, struct session *s, const char *cwd,
job->pid = pid;
job->status = 0;
LIST_INSERT_HEAD(&all_jobs, job, lentry);
LIST_INSERT_HEAD(&all_jobs, job, entry);
job->callbackfn = callbackfn;
job->freefn = freefn;
job->updatecb = updatecb;
job->completecb = completecb;
job->freecb = freecb;
job->data = data;
job->fd = out[0];
setblocking(job->fd, 0);
job->event = bufferevent_new(job->fd, NULL, job_write_callback,
job_callback, job);
job->event = bufferevent_new(job->fd, job_read_callback,
job_write_callback, job_error_callback, job);
bufferevent_enable(job->event, EV_READ|EV_WRITE);
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);
LIST_REMOVE(job, lentry);
LIST_REMOVE(job, entry);
free(job->cmd);
if (job->freefn != NULL && job->data != NULL)
job->freefn(job->data);
if (job->freecb != NULL && job->data != NULL)
job->freecb(job->data);
if (job->pid != -1)
kill(job->pid, SIGTERM);
@ -143,7 +146,21 @@ job_free(struct job *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
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. */
static void
job_callback(__unused struct bufferevent *bufev, __unused short events,
job_error_callback(__unused struct bufferevent *bufev, __unused short events,
void *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);
if (job->state == JOB_DEAD) {
if (job->callbackfn != NULL)
job->callbackfn(job);
if (job->completecb != NULL)
job->completecb(job);
job_free(job);
} else {
bufferevent_disable(job->event, EV_READ);
@ -187,8 +204,8 @@ job_died(struct job *job, int status)
job->status = status;
if (job->state == JOB_CLOSED) {
if (job->callbackfn != NULL)
job->callbackfn(job);
if (job->completecb != NULL)
job->completecb(job);
job_free(job);
} else {
job->pid = -1;

View File

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

2
tmux.1
View File

@ -3490,6 +3490,8 @@ 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.
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
.Nm
global environment set (see the

31
tmux.h
View File

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

View File

@ -1641,7 +1641,7 @@ window_copy_copy_pipe(struct window_pane *wp, struct session *s,
return;
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);
free(expanded);