diff options
-rw-r--r-- | runtime/doc/eval.txt | 4 | ||||
-rw-r--r-- | src/nvim/eval.c | 49 | ||||
-rw-r--r-- | src/nvim/os/job.c | 12 | ||||
-rw-r--r-- | test/functional/job/job_spec.lua | 14 |
4 files changed, 79 insertions, 0 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 9224fd25e0..1b1d93a065 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4012,6 +4012,10 @@ items({dict}) *items()* entry and the value of this entry. The |List| is in arbitrary order. +jobclose({job}[, {stream}]) {Nvim} *jobclose()* + Close {job}'s {stream}, which can be one "stdin", "stdout" or + "stderr". If {stream} is omitted, all streams are closed. + jobresize({job}, {width}, {height}) {Nvim} *jobresize()* Resize {job}'s pseudo terminal window to {width} and {height}. This function will fail if used on jobs started without the diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e4acaa0c53..4baca21116 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -445,6 +445,7 @@ typedef struct { Job *job; Terminal *term; bool exited; + bool stdin_closed; int refcount; ufunc_T *on_stdout, *on_stderr, *on_exit; dict_T *self; @@ -6535,6 +6536,7 @@ static struct fst { {"isdirectory", 1, 1, f_isdirectory}, {"islocked", 1, 1, f_islocked}, {"items", 1, 1, f_items}, + {"jobclose", 1, 2, f_jobclose}, {"jobresize", 3, 3, f_jobresize}, {"jobsend", 2, 2, f_jobsend}, {"jobstart", 1, 2, f_jobstart}, @@ -10658,6 +10660,48 @@ static void f_items(typval_T *argvars, typval_T *rettv) dict_list(argvars, rettv, 2); } +// "jobclose(id[, stream])" function +static void f_jobclose(typval_T *argvars, typval_T *rettv) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) { + return; + } + + if (argvars[0].v_type != VAR_NUMBER || (argvars[1].v_type != VAR_STRING + && argvars[1].v_type != VAR_UNKNOWN)) { + EMSG(_(e_invarg)); + return; + } + + Job *job = job_find(argvars[0].vval.v_number); + + if (!is_user_job(job)) { + // Invalid job id + EMSG(_(e_invjob)); + return; + } + + if (argvars[1].v_type == VAR_STRING) { + char *stream = (char *)argvars[1].vval.v_string; + if (!strcmp(stream, "stdin")) { + job_close_in(job); + ((TerminalJobData *)job_data(job))->stdin_closed = true; + } else if (!strcmp(stream, "stdout")) { + job_close_out(job); + } else if (!strcmp(stream, "stderr")) { + job_close_err(job); + } else { + EMSG2(_("Invalid job stream \"%s\""), stream); + } + } else { + ((TerminalJobData *)job_data(job))->stdin_closed = true; + job_close_streams(job); + } +} + // "jobsend()" function static void f_jobsend(typval_T *argvars, typval_T *rettv) { @@ -10683,6 +10727,11 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv) return; } + if (((TerminalJobData *)job_data(job))->stdin_closed) { + EMSG(_("Can't send data to the job: stdin is closed")); + return; + } + ssize_t input_len; char *input = (char *) save_tv_as_string(&argvars[1], &input_len, false); if (!input) { diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c index 7d9a91a1b2..8fe44c7a53 100644 --- a/src/nvim/os/job.c +++ b/src/nvim/os/job.c @@ -290,6 +290,18 @@ void job_close_in(Job *job) FUNC_ATTR_NONNULL_ALL close_job_in(job); } +// Close the job stdout stream. +void job_close_out(Job *job) FUNC_ATTR_NONNULL_ALL +{ + close_job_out(job); +} + +// Close the job stderr stream. +void job_close_err(Job *job) FUNC_ATTR_NONNULL_ALL +{ + close_job_out(job); +} + /// All writes that complete after calling this function will be reported /// to `cb`. /// diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua index 76632c0fec..c1c559eb34 100644 --- a/test/functional/job/job_spec.lua +++ b/test/functional/job/job_spec.lua @@ -107,6 +107,20 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) + it('can close the job streams with jobclose', function() + nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', 'call jobclose(j, "stdin")') + eq({'notification', 'exit', {0, 0}}, next_msg()) + end) + + it('wont allow jobsend with a job that closed stdin', function() + nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") + nvim('command', 'call jobclose(j, "stdin")') + eq(false, pcall(function() + nvim('command', 'call jobsend(j, ["some data"])') + end)) + end) + it('will not allow jobsend/stop on a non-existent job', function() eq(false, pcall(eval, "jobsend(-1, 'lol')")) eq(false, pcall(eval, "jobstop(-1)")) |