aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/autoload/provider/clipboard.vim1
-rw-r--r--runtime/doc/eval.txt33
-rw-r--r--src/nvim/eval.c39
-rw-r--r--src/nvim/event/libuv_process.c2
-rw-r--r--src/nvim/event/process.h2
-rw-r--r--src/nvim/os/pty_process_unix.c7
-rw-r--r--test/functional/job/job_spec.lua30
7 files changed, 88 insertions, 26 deletions
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index c7cb14ded7..2272519dfd 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -94,6 +94,7 @@ function! s:clipboard.set(lines, regtype, reg)
let selection.data = [a:lines, a:regtype]
let argv = split(s:copy[a:reg], " ")
let selection.detach = s:cache_enabled
+ let selection.cwd = "/"
let jobid = jobstart(argv, selection)
if jobid <= 0
echohl WarningMsg
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 219327fd77..869fcf0078 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -4309,21 +4309,24 @@ jobstart({cmd}[, {opts}]) {Nvim} *jobstart()*
with 'shell' or 'shellcmdflag' options. The above call is
only written to show the idea, one needs to perform unquoting
and do split taking quotes into account.
- If passed, {opts} must be a dictionary with any of the
- following keys:
- - on_stdout: stdout event handler
- - on_stderr: stderr event handler
- - on_exit: exit event handler
- - pty: If set, the job will be connected to a new pseudo
- terminal, and the job streams are connected to the master
- file descriptor.
- - width: Width of the terminal screen(only if pty is set)
- - height: Height of the terminal screen(only if pty is set)
- - TERM: $TERM environment variable(only if pty is set)
- - detach: Detach the job process from the nvim process. The
- process won't get killed when nvim exists. If the process
- dies before nvim exits, on_exit will still be invoked.
- This option is only allowed for non-pty jobs.
+
+ {opts} is a dictionary with these keys:
+ on_stdout: stdout event handler
+ on_stderr: stderr event handler
+ on_exit : exit event handler
+ cwd : Working directory of the job; defaults to
+ |current-directory|.
+ pty : If set, the job will be connected to a new pseudo
+ terminal, and the job streams are connected to
+ the master file descriptor.
+ width : (pty only) Width of the terminal screen
+ height : (pty only) Height of the terminal screen
+ TERM : (pty only) $TERM environment variable
+ detach : (non-pty only) Detach the job process from the
+ nvim process. The process will not get killed
+ when nvim exits. If the process dies before
+ nvim exits, on_exit will still be invoked.
+
Either funcrefs or function names can be passed as event
handlers. The {opts} object is also used as the "self"
argument for the callback, so the caller may pass arbitrary
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index d8f1ce8494..92d9dc8ecf 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -11742,8 +11742,21 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
dict_T *job_opts = NULL;
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL;
+ char *cwd = NULL;
if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict;
+
+ char *new_cwd = (char *)get_dict_string(job_opts, (char_u *)"cwd", false);
+ if (new_cwd && strlen(new_cwd) > 0) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir((char_u *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return;
@@ -11753,7 +11766,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
bool pty = job_opts && get_dict_number(job_opts, (uint8_t *)"pty") != 0;
bool detach = job_opts && get_dict_number(job_opts, (uint8_t *)"detach") != 0;
TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit,
- job_opts, pty, detach);
+ job_opts, pty, detach, cwd);
Process *proc = (Process *)&data->proc;
if (pty) {
@@ -16468,8 +16481,21 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
ufunc_T *on_stdout = NULL, *on_stderr = NULL, *on_exit = NULL;
dict_T *job_opts = NULL;
+ char *cwd = ".";
if (argvars[1].v_type == VAR_DICT) {
job_opts = argvars[1].vval.v_dict;
+
+ char *new_cwd = (char *)get_dict_string(job_opts, (char_u *)"cwd", false);
+ if (new_cwd && strlen(new_cwd) > 0) {
+ cwd = new_cwd;
+ // The new cwd must be a directory.
+ if (!os_isdir((char_u *)cwd)) {
+ EMSG2(_(e_invarg2), "expected valid directory");
+ shell_free_argv(argv);
+ return;
+ }
+ }
+
if (!common_job_callbacks(job_opts, &on_stdout, &on_stderr, &on_exit)) {
shell_free_argv(argv);
return;
@@ -16477,7 +16503,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
}
TerminalJobData *data = common_job_init(argv, on_stdout, on_stderr, on_exit,
- job_opts, true, false);
+ job_opts, true, false, cwd);
data->proc.pty.width = curwin->w_width;
data->proc.pty.height = curwin->w_height;
data->proc.pty.term_name = xstrdup("xterm-256color");
@@ -16492,11 +16518,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv)
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- char *cwd = ".";
- if (argvars[1].v_type == VAR_STRING
- && os_isdir(argvars[1].vval.v_string)) {
- cwd = (char *)argvars[1].vval.v_string;
- }
int pid = data->proc.pty.process.pid;
char buf[1024];
@@ -21764,7 +21785,8 @@ static inline TerminalJobData *common_job_init(char **argv,
ufunc_T *on_exit,
dict_T *self,
bool pty,
- bool detach)
+ bool detach,
+ char *cwd)
{
TerminalJobData *data = xcalloc(1, sizeof(TerminalJobData));
data->stopped = false;
@@ -21788,6 +21810,7 @@ static inline TerminalJobData *common_job_init(char **argv,
proc->cb = on_process_exit;
proc->events = data->events;
proc->detach = detach;
+ proc->cwd = cwd;
return data;
}
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index 9ef3468284..a68badcc8f 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -25,7 +25,7 @@ bool libuv_process_spawn(LibuvProcess *uvproc)
uvproc->uvopts.flags |= UV_PROCESS_DETACHED;
}
uvproc->uvopts.exit_cb = exit_cb;
- uvproc->uvopts.cwd = NULL;
+ uvproc->uvopts.cwd = proc->cwd;
uvproc->uvopts.env = NULL;
uvproc->uvopts.stdio = uvproc->uvstdio;
uvproc->uvopts.stdio_count = 3;
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index e23c8ea60f..a4c6e7eeb2 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -21,6 +21,7 @@ struct process {
int pid, status, refcount;
// set to the hrtime of when process_stop was called for the process.
uint64_t stopped_time;
+ char *cwd;
char **argv;
Stream *in, *out, *err;
process_exit_cb cb;
@@ -40,6 +41,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
.status = 0,
.refcount = 0,
.stopped_time = 0,
+ .cwd = NULL,
.argv = NULL,
.in = NULL,
.out = NULL,
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index d0a38e663b..436de030ba 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -28,6 +28,7 @@
#include "nvim/event/process.h"
#include "nvim/os/pty_process_unix.h"
#include "nvim/log.h"
+#include "nvim/os/os.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_unix.c.generated.h"
@@ -131,6 +132,12 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
+ Process *proc = (Process *)ptyproc;
+ if (proc->cwd && os_chdir(proc->cwd) != 0) {
+ fprintf(stderr, "chdir failed: %s\n", strerror(errno));
+ return;
+ }
+
setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1);
execvp(ptyproc->process.argv[0], ptyproc->process.argv);
fprintf(stderr, "execvp failed: %s\n", strerror(errno));
diff --git a/test/functional/job/job_spec.lua b/test/functional/job/job_spec.lua
index d21b9051e2..722c5d95ae 100644
--- a/test/functional/job/job_spec.lua
+++ b/test/functional/job/job_spec.lua
@@ -1,11 +1,11 @@
local helpers = require('test.functional.helpers')
local clear, eq, eval, execute, feed, insert, neq, next_msg, nvim,
- nvim_dir, ok, source, write_file = helpers.clear,
+ nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear,
helpers.eq, helpers.eval, helpers.execute, helpers.feed,
helpers.insert, helpers.neq, helpers.next_message, helpers.nvim,
helpers.nvim_dir, helpers.ok, helpers.source,
- helpers.write_file
+ helpers.write_file, helpers.mkdir, helpers.rmdir
local Screen = require('test.functional.ui.screen')
@@ -37,6 +37,32 @@ describe('jobs', function()
eq({'notification', 'exit', {0, 0}}, next_msg())
end)
+ it('changes to given / directory', function()
+ nvim('command', "let g:job_opts.cwd = '/'")
+ nvim('command', "let j = jobstart('pwd', g:job_opts)")
+ eq({'notification', 'stdout', {0, {'/', ''}}}, next_msg())
+ eq({'notification', 'exit', {0, 0}}, next_msg())
+ end)
+
+ it('changes to given `cwd` directory', function()
+ local dir = eval('resolve(tempname())')
+ mkdir(dir)
+ nvim('command', "let g:job_opts.cwd = '" .. dir .. "'")
+ nvim('command', "let j = jobstart('pwd', g:job_opts)")
+ eq({'notification', 'stdout', {0, {dir, ''}}}, next_msg())
+ eq({'notification', 'exit', {0, 0}}, next_msg())
+ rmdir(dir)
+ end)
+
+ it('fails to change to invalid `cwd`', function()
+ local dir = eval('resolve(tempname())."-bogus"')
+ local _, err = pcall(function()
+ nvim('command', "let g:job_opts.cwd = '" .. dir .. "'")
+ nvim('command', "let j = jobstart('pwd', g:job_opts)")
+ end)
+ ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil)
+ end)
+
it('returns 0 when it fails to start', function()
local status, rv = pcall(eval, "jobstart([])")
eq(false, status)