diff --git a/cfg.c b/cfg.c index 60735046..86b4c82f 100644 --- a/cfg.c +++ b/cfg.c @@ -301,9 +301,10 @@ load_cfg(const char *path, struct client *c, struct cmdq_item *item, int quiet) free(buf); new_item = cmdq_get_command(cmdlist, NULL, NULL, 0); - if (item != NULL) + if (item != NULL) { cmdq_insert_after(item, new_item); - else + item = new_item; + } else cmdq_append(c, new_item); cmd_list_free(cmdlist); diff --git a/cmd-display-message.c b/cmd-display-message.c index 2e935908..8c1ad5f6 100644 --- a/cmd-display-message.c +++ b/cmd-display-message.c @@ -39,8 +39,8 @@ const struct cmd_entry cmd_display_message_entry = { .name = "display-message", .alias = "display", - .args = { "ac:pt:F:v", 0, 1 }, - .usage = "[-apv] [-c target-client] [-F format] " + .args = { "ac:Ipt:F:v", 0, 1 }, + .usage = "[-aIpv] [-c target-client] [-F format] " CMD_TARGET_PANE_USAGE " [message]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -66,10 +66,19 @@ cmd_display_message_exec(struct cmd *self, struct cmdq_item *item) struct winlink *wl = item->target.wl; struct window_pane *wp = item->target.wp; const char *template; - char *msg; + char *msg, *cause; struct format_tree *ft; int flags; + if (args_has(args, 'I')) { + if (window_pane_start_input(wp, item, &cause) != 0) { + cmdq_error(item, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } + return (CMD_RETURN_WAIT); + } + if (args_has(args, 'F') && args->argc != 0) { cmdq_error(item, "only one of -F or argument must be given"); return (CMD_RETURN_ERROR); diff --git a/cmd-split-window.c b/cmd-split-window.c index b06c48f2..8d63473f 100644 --- a/cmd-split-window.c +++ b/cmd-split-window.c @@ -39,9 +39,10 @@ const struct cmd_entry cmd_split_window_entry = { .name = "split-window", .alias = "splitw", - .args = { "bc:de:fF:l:hp:Pt:v", 0, -1 }, - .usage = "[-bdefhvP] [-c start-directory] [-e environment] [-F format] " - "[-p percentage|-l size] " CMD_TARGET_PANE_USAGE " [command]", + .args = { "bc:de:fF:hIl:p:Pt:v", 0, -1 }, + .usage = "[-bdefhIPv] [-c start-directory] [-e environment] " + "[-F format] [-p percentage|-l size] " CMD_TARGET_PANE_USAGE + " [command]", .target = { 't', CMD_FIND_PANE, 0 }, @@ -62,7 +63,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) enum layout_type type; struct layout_cell *lc; struct cmd_find_state fs; - int size, percentage, flags; + int size, percentage, flags, input; const char *template, *add; char *cause, *cp; struct args_value *value; @@ -93,12 +94,15 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) size = -1; server_unzoom_window(wp->window); + input = (args_has(args, 'I') && args->argc == 0); flags = 0; if (args_has(args, 'b')) flags |= SPAWN_BEFORE; if (args_has(args, 'f')) flags |= SPAWN_FULLSIZE; + if (input || (args->argc == 1 && *args->argv[0] == '\0')) + flags |= SPAWN_EMPTY; lc = layout_split_pane(wp, type, size, flags); if (lc == NULL) { @@ -133,10 +137,18 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) sc.flags |= SPAWN_DETACHED; if ((new_wp = spawn_pane(&sc, &cause)) == NULL) { + layout_close_pane(new_wp); cmdq_error(item, "create pane failed: %s", cause); free(cause); return (CMD_RETURN_ERROR); } + if (input && window_pane_start_input(new_wp, item, &cause) != 0) { + layout_close_pane(new_wp); + window_remove_pane(wp->window, new_wp); + cmdq_error(item, "%s", cause); + free(cause); + return (CMD_RETURN_ERROR); + } if (!args_has(args, 'd')) cmd_find_from_winlink_pane(current, wl, new_wp, 0); server_redraw_window(wp->window); @@ -154,5 +166,7 @@ cmd_split_window_exec(struct cmd *self, struct cmdq_item *item) cmdq_insert_hook(s, item, &fs, "after-split-window"); environ_free(sc.environ); + if (input) + return (CMD_RETURN_WAIT); return (CMD_RETURN_NORMAL); } diff --git a/format.c b/format.c index db642f63..77ca6769 100644 --- a/format.c +++ b/format.c @@ -2047,7 +2047,10 @@ format_defaults_pane(struct format_tree *ft, struct window_pane *wp) if ((wp->flags & PANE_STATUSREADY) && WIFEXITED(status)) format_add(ft, "pane_dead_status", "%d", WEXITSTATUS(status)); - format_add(ft, "pane_dead", "%d", wp->fd == -1); + if (~wp->flags & PANE_EMPTY) + format_add(ft, "pane_dead", "%d", wp->fd == -1); + else + format_add(ft, "pane_dead", "0"); format_add(ft, "pane_left", "%u", wp->xoff); format_add(ft, "pane_top", "%u", wp->yoff); diff --git a/input.c b/input.c index 07341403..20272a62 100644 --- a/input.c +++ b/input.c @@ -1185,6 +1185,8 @@ input_c0_dispatch(struct input_ctx *ictx) case '\013': /* VT */ case '\014': /* FF */ screen_write_linefeed(sctx, 0, ictx->cell.cell.bg); + if (s->mode & MODE_CRLF) + screen_write_carriagereturn(sctx); break; case '\015': /* CR */ screen_write_carriagereturn(sctx); diff --git a/server-client.c b/server-client.c index c72278f1..b6d450a0 100644 --- a/server-client.c +++ b/server-client.c @@ -1681,8 +1681,7 @@ server_client_dispatch(struct imsg *imsg, void *arg) evbuffer_add(c->stdin_data, stdindata.data, stdindata.size); } - c->stdin_callback(c, c->stdin_closed, - c->stdin_callback_data); + c->stdin_callback(c, c->stdin_closed, c->stdin_callback_data); break; case MSG_RESIZE: if (datalen != 0) diff --git a/server-fn.c b/server-fn.c index 8051fe4f..213d72ea 100644 --- a/server-fn.c +++ b/server-fn.c @@ -440,7 +440,6 @@ server_check_unattached(void) } } -/* Set stdin callback. */ int server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, void *), void *cb_data, char **cause) @@ -454,7 +453,7 @@ server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, return (-1); } if (c->stdin_callback != NULL) { - *cause = xstrdup("stdin in use"); + *cause = xstrdup("stdin is in use"); return (-1); } diff --git a/spawn.c b/spawn.c index 6892e578..e7ccfe0f 100644 --- a/spawn.c +++ b/spawn.c @@ -330,6 +330,14 @@ spawn_pane(struct spawn_context *sc, char **cause) cmd_log_argv(new_wp->argc, new_wp->argv, __func__); environ_log(child, "%s: environment ", __func__); + /* If the command is empty, don't fork a child process. */ + if (sc->flags & SPAWN_EMPTY) { + new_wp->flags |= PANE_EMPTY; + new_wp->base.mode &= ~MODE_CURSOR; + new_wp->base.mode |= MODE_CRLF; + goto complete; + } + /* Initialize the window size. */ memset(&ws, 0, sizeof ws); ws.ws_col = screen_size_x(&new_wp->base); @@ -353,25 +361,8 @@ spawn_pane(struct spawn_context *sc, char **cause) } /* In the parent process, everything is done now. */ - if (new_wp->pid != 0) { - new_wp->pipe_off = 0; - new_wp->flags &= ~PANE_EXITED; - - sigprocmask(SIG_SETMASK, &oldset, NULL); - window_pane_set_event(new_wp); - - if (sc->flags & SPAWN_RESPAWN) - return (new_wp); - if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { - if (sc->flags & SPAWN_NONOTIFY) - window_set_active_pane(w, new_wp, 0); - else - window_set_active_pane(w, new_wp, 1); - } - if (~sc->flags & SPAWN_NONOTIFY) - notify_window("window-layout-changed", w); - return (new_wp); - } + if (new_wp->pid != 0) + goto complete; /* * Child process. Change to the working directory or home if that @@ -431,4 +422,23 @@ spawn_pane(struct spawn_context *sc, char **cause) xasprintf(&argv0, "-%s", new_wp->shell); execl(new_wp->shell, argv0, (char *)NULL); _exit(1); + +complete: + new_wp->pipe_off = 0; + new_wp->flags &= ~PANE_EXITED; + + sigprocmask(SIG_SETMASK, &oldset, NULL); + window_pane_set_event(new_wp); + + if (sc->flags & SPAWN_RESPAWN) + return (new_wp); + if ((~sc->flags & SPAWN_DETACHED) || w->active == NULL) { + if (sc->flags & SPAWN_NONOTIFY) + window_set_active_pane(w, new_wp, 0); + else + window_set_active_pane(w, new_wp, 1); + } + if (~sc->flags & SPAWN_NONOTIFY) + notify_window("window-layout-changed", w); + return (new_wp); } diff --git a/tmux.1 b/tmux.1 index 0ea1fdd5..8e795df8 100644 --- a/tmux.1 +++ b/tmux.1 @@ -2209,7 +2209,7 @@ is given and the selected window is already the current window, the command behaves like .Ic last-window . .It Xo Ic split-window -.Op Fl bdfhvP +.Op Fl bdfhIvP .Op Fl c Ar start-directory .Op Fl e Ar environment .Oo Fl l @@ -2245,6 +2245,24 @@ option creates a new pane spanning the full window height (with or full window width (with .Fl v ) , instead of splitting the active pane. +.Pp +An empty +.Ar shell-command +('') will create a pane with no command running in it. +Output can be sent to such a pane with the +.Ic display-message +command. +The +.Fl I +flag (if +.Ar shell-command +is not specified or empty) +will create an empty pane and forward any output from stdin to it. +For example: +.Bd -literal -offset indent +$ make 2>&1|tmux splitw -dI & +.Ed +.Pp All other options have the same meaning as for the .Ic new-window command. @@ -4435,7 +4453,7 @@ option. This command works only from inside .Nm . .It Xo Ic display-message -.Op Fl apv +.Op Fl aIpv .Op Fl c Ar target-client .Op Fl t Ar target-pane .Op Ar message @@ -4462,6 +4480,10 @@ is given, otherwise the active pane for the session attached to prints verbose logging as the format is parsed and .Fl a lists the format variables and their values. +.Pp +.Fl I +forwards any input read from stdin to the empty pane given by +.Ar target-pane . .El .Sh BUFFERS .Nm diff --git a/tmux.h b/tmux.h index 3fce877f..e8305d93 100644 --- a/tmux.h +++ b/tmux.h @@ -524,6 +524,7 @@ struct msg_stderr_data { #define MODE_FOCUSON 0x800 #define MODE_MOUSE_ALL 0x1000 #define MODE_ORIGIN 0x2000 +#define MODE_CRLF 0x4000 #define ALL_MODES 0xffffff #define ALL_MOUSE_MODES (MODE_MOUSE_STANDARD|MODE_MOUSE_BUTTON|MODE_MOUSE_ALL) @@ -804,6 +805,7 @@ struct window_pane { #define PANE_EXITED 0x100 #define PANE_STATUSREADY 0x200 #define PANE_STATUSDRAWN 0x400 +#define PANE_EMPTY 0x800 int argc; char **argv; @@ -1600,6 +1602,7 @@ struct spawn_context { #define SPAWN_BEFORE 0x8 #define SPAWN_NONOTIFY 0x10 #define SPAWN_FULLSIZE 0x20 +#define SPAWN_EMPTY 0x40 }; /* tmux.c */ @@ -2319,6 +2322,8 @@ void window_add_ref(struct window *, const char *); void window_remove_ref(struct window *, const char *); void winlink_clear_flags(struct winlink *); int winlink_shuffle_up(struct session *, struct winlink *); +int window_pane_start_input(struct window_pane *, + struct cmdq_item *, char **); /* layout.c */ u_int layout_count_cells(struct layout_cell *); diff --git a/window.c b/window.c index 795bc26c..e4cc51d3 100644 --- a/window.c +++ b/window.c @@ -70,6 +70,11 @@ const struct window_mode *all_window_modes[] = { NULL }; +struct window_pane_input_data { + struct cmdq_item *item; + u_int wp; +}; + static struct window_pane *window_pane_create(struct window *, u_int, u_int, u_int); static void window_pane_destroy(struct window_pane *); @@ -1467,3 +1472,45 @@ winlink_shuffle_up(struct session *s, struct winlink *wl) return (idx); } + +static void +window_pane_input_callback(struct client *c, int closed, void *data) +{ + struct window_pane_input_data *cdata = data; + struct window_pane *wp; + + wp = window_pane_find_by_id(cdata->wp); + if (wp == NULL || closed || c->flags & CLIENT_DEAD) { + c->stdin_callback = NULL; + server_client_unref(c); + + cdata->item->flags &= ~CMDQ_WAITING; + free(cdata); + + return; + } + + if (evbuffer_add_buffer(wp->event->input, c->stdin_data) != 0) + evbuffer_drain(c->stdin_data, EVBUFFER_LENGTH(c->stdin_data)); + input_parse(wp); +} + +int +window_pane_start_input(struct window_pane *wp, struct cmdq_item *item, + char **cause) +{ + struct client *c = item->client; + struct window_pane_input_data *cdata; + + if (~wp->flags & PANE_EMPTY) { + *cause = xstrdup("pane is not empty"); + return (-1); + } + + cdata = xmalloc(sizeof *cdata); + cdata->item = item; + cdata->wp = wp->id; + + return (server_set_stdin_callback(c, window_pane_input_callback, cdata, + cause)); +}