Skip to content

Commit

Permalink
feat(job): add parameter to close stdin
Browse files Browse the repository at this point in the history
Some programs behave differently when they detect that stdin is being
piped. This can be problematic when these programs are used with the job
control API where stdin is attached, but not typically used. It is
possible to run the job using a PTY which circumvents this problem, but
that includes a lot of overhead when simply closing the stdin pipe would
suffice.

To enable this behavior, add a new parameter to the jobstart options
dict called "stdin" with two valid values: "pipe" (the default)
implements the existing behavior of opening a channel for stdin and
"null" which disconnects stdin (or, if you prefer, connects it to
/dev/null). This is extensible so that other modes can be added in the
future.
  • Loading branch information
gpanders authored and bfredl committed Jul 12, 2021
1 parent eece073 commit eb7f24b
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 9 deletions.
3 changes: 3 additions & 0 deletions runtime/doc/eval.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5637,6 +5637,9 @@ jobstart({cmd}[, {opts}]) *jobstart()*
before invoking `on_stderr`. |channel-buffered|
stdout_buffered: (boolean) Collect data until EOF (stream
closed) before invoking `on_stdout`. |channel-buffered|
stdin: (string) Either "pipe" (default) to connect the
job's stdin to a channel or "null" to disconnect
stdin.
width: (number) Width of the `pty` terminal.

{opts} is passed as |self| dictionary to the callback; the
Expand Down
23 changes: 19 additions & 4 deletions src/nvim/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@ static void close_cb(Stream *stream, void *data)
/// `on_stdout` is ignored
/// @param[in] detach True if the job should not be killed when nvim exits,
/// ignored if `pty` is true
/// @param[in] stdin Stdin mode. Either kChannelStdinPipe to open a channel
/// for stdin or kChannelStdinNull to leave stdin
/// disconnected.
/// @param[in] cwd Initial working directory for the job. Nvim's working
/// directory if `cwd` is NULL
/// @param[in] pty_width Width of the pty, ignored if `pty` is false
Expand All @@ -302,7 +305,7 @@ static void close_cb(Stream *stream, void *data)
Channel *channel_job_start(char **argv, CallbackReader on_stdout,
CallbackReader on_stderr, Callback on_exit,
bool pty, bool rpc, bool overlapped, bool detach,
const char *cwd,
ChannelStdinMode stdin, const char *cwd,
uint16_t pty_width, uint16_t pty_height,
dict_T *env, varnumber_T *status_out)
{
Expand Down Expand Up @@ -345,15 +348,25 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
proc->overlapped = overlapped;

char *cmd = xstrdup(proc->argv[0]);
bool has_out, has_err;
bool has_in, has_out, has_err;
if (proc->type == kProcessTypePty) {
has_out = true;
has_err = false;
} else {
has_out = rpc || callback_reader_set(chan->on_data);
has_err = callback_reader_set(chan->on_stderr);
}
int status = process_spawn(proc, true, has_out, has_err);

switch (stdin) {
case kChannelStdinPipe:
has_in = true;
break;
case kChannelStdinNull:
has_in = false;
break;
}

int status = process_spawn(proc, has_in, has_out, has_err);
if (status) {
EMSG3(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
Expand All @@ -369,7 +382,9 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout,
tv_dict_free(proc->env);
}

wstream_init(&proc->in, 0);
if (has_in) {
wstream_init(&proc->in, 0);
}
if (has_out) {
rstream_init(&proc->out, 0);
}
Expand Down
4 changes: 4 additions & 0 deletions src/nvim/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ typedef enum {
kChannelPartAll
} ChannelPart;

typedef enum {
kChannelStdinPipe,
kChannelStdinNull,
} ChannelStdinMode;

typedef struct {
Stream in;
Expand Down
24 changes: 19 additions & 5 deletions src/nvim/eval/funcs.c
Original file line number Diff line number Diff line change
Expand Up @@ -5182,6 +5182,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
bool pty = false;
bool clear_env = false;
bool overlapped = false;
ChannelStdinMode stdin = kChannelStdinPipe;
CallbackReader on_stdout = CALLBACK_READER_INIT,
on_stderr = CALLBACK_READER_INIT;
Callback on_exit = CALLBACK_NONE;
Expand All @@ -5196,6 +5197,17 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
clear_env = tv_dict_get_number(job_opts, "clear_env") != 0;
overlapped = tv_dict_get_number(job_opts, "overlapped") != 0;

char *s = tv_dict_get_string(job_opts, "stdin", false);
if (s) {
if (!strncmp(s, "null", NUMBUFLEN)) {
stdin = kChannelStdinNull;
} else if (!strncmp(s, "pipe", NUMBUFLEN)) {
// Nothing to do, default value
} else {
EMSG3(_(e_invargNval), "stdin", s);
}
}

if (pty && rpc) {
EMSG2(_(e_invarg2), "job cannot have both 'pty' and 'rpc' options set");
shell_free_argv(argv);
Expand Down Expand Up @@ -5252,8 +5264,8 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
env = create_environment(job_env, clear_env, pty, term_name);

Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
rpc, overlapped, detach, cwd, width, height,
env, &rettv->vval.v_number);
rpc, overlapped, detach, stdin, cwd, width,
height, env, &rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
Expand Down Expand Up @@ -7733,8 +7745,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)

Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
false, true, false, false, NULL, 0, 0,
NULL, &rettv->vval.v_number);
false, true, false, false,
kChannelStdinPipe, NULL, 0, 0, NULL,
&rettv->vval.v_number);
if (chan) {
channel_create_event(chan, NULL);
}
Expand Down Expand Up @@ -10850,9 +10863,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const bool rpc = false;
const bool overlapped = false;
const bool detach = false;
ChannelStdinMode stdin = kChannelStdinPipe;
uint16_t term_width = MAX(0, curwin->w_width_inner - win_col_off(curwin));
Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
pty, rpc, overlapped, detach, cwd,
pty, rpc, overlapped, detach, stdin, cwd,
term_width, curwin->w_height_inner,
env, &rettv->vval.v_number);
if (rettv->vval.v_number <= 0) {
Expand Down

0 comments on commit eb7f24b

Please sign in to comment.