aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt4
-rw-r--r--src/nvim/eval.c49
-rw-r--r--src/nvim/os/job.c12
-rw-r--r--test/functional/job/job_spec.lua14
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)"))