diff options
-rw-r--r-- | runtime/doc/eval.txt | 22 | ||||
-rw-r--r-- | src/nvim/event/libuv_process.c | 22 | ||||
-rw-r--r-- | test/functional/eval/system_spec.lua | 38 |
3 files changed, 65 insertions, 17 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 1ed919a241..aa979ae42c 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4852,16 +4852,18 @@ jobstart({cmd}[, {opts}]) {Nvim} *jobstart()* Spawns {cmd} as a job. If {cmd} is a |List| it is run directly. If {cmd} is a |String| it is processed like this: > :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) -< NOTE: This only shows the idea; see |shell-unquoting| before - constructing lists with 'shell' or 'shellcmdflag'. - - NOTE: On Windows if {cmd} is a List, cmd[0] must be a valid - executable (.exe, .com). If the executable is in $PATH it can - be called by name, with or without an extension: > - :call jobstart(['ping', 'neovim.io']) -< If it is a path (not a name), it must include the extension: > - :call jobstart(['System32\ping.exe', 'neovim.io']) -< +< (Only shows the idea; see |shell-unquoting| for full details.) + + NOTE: on Windows if {cmd} is a List: + - cmd[0] must be executable. If it is in $PATH it can be + called by name, with or without an extension: > + :call jobstart(['ping', 'neovim.io']) +< If it is a path (not a name), extension is required: > + :call jobstart(['System32\ping.exe', 'neovim.io']) +< - {cmd} is quoted per the convention expected by + CommandLineToArgvW https://msdn.microsoft.com/bb776391 + unless the first argument is some form of "cmd.exe". + {opts} is a dictionary with these keys: on_stdout: stdout event handler (function name or |Funcref|) on_stderr: stderr event handler (function name or |Funcref|) diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 3da0c386b4..ab69bea04c 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -8,6 +8,8 @@ #include "nvim/event/process.h" #include "nvim/event/libuv_process.h" #include "nvim/log.h" +#include "nvim/path.h" +#include "nvim/os/os.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/libuv_process.c.generated.h" @@ -24,6 +26,26 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (proc->detach) { uvproc->uvopts.flags |= UV_PROCESS_DETACHED; } +#ifdef WIN32 + // libuv assumes spawned processes follow the convention from + // CommandLineToArgvW(), cmd.exe does not. Disable quoting since it will + // result in unexpected behaviour, the caller is left with the responsibility + // to quote arguments accordingly. system('') has shell* options for this. + // + // Disable quoting for cmd, cmd.exe and $COMSPEC with a cmd.exe filename + bool is_cmd = STRICMP(proc->argv[0], "cmd.exe") == 0 + || STRICMP(proc->argv[0], "cmd") == 0; + if (!is_cmd) { + const char_u *comspec = (char_u *)os_getenv("COMSPEC"); + const char_u *comspecshell = path_tail((char_u *)proc->argv[0]); + is_cmd = comspec != NULL && STRICMP(proc->argv[0], comspec) == 0 + && STRICMP("cmd.exe", (char *)comspecshell) == 0; + } + + if (is_cmd) { + uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; + } +#endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; uvproc->uvopts.env = NULL; diff --git a/test/functional/eval/system_spec.lua b/test/functional/eval/system_spec.lua index 83d8028b56..45ca69707d 100644 --- a/test/functional/eval/system_spec.lua +++ b/test/functional/eval/system_spec.lua @@ -129,14 +129,38 @@ describe('system()', function() screen:detach() end) - it('escapes inner double quotes #6329', function() - if helpers.os_name() == 'windows' then - -- In Windows cmd.exe's echo prints the quotes + if helpers.os_name() == 'windows' then + it('with the default cmd.exe shell', function() eq('""\n', eval([[system('echo ""')]])) - else - eq('\n', eval([[system('echo ""')]])) - end - end) + eq('"a b"\n', eval([[system('echo "a b"')]])) + -- TODO: consistent with Vim, but it should be fixed + eq('a & echo b\n', eval([[system('echo a & echo b')]])) + eval([[system('cd "C:\Program Files"')]]) + eq(0, eval('v:shell_error')) + end) + + it('with set shell="cmd"', function() + helpers.source('let &shell="cmd"') + eq('"a b"\n', eval([[system('echo "a b"')]])) + end) + + it('works with cmd.exe from $COMSPEC', function() + local comspecshell = eval("fnamemodify($COMSPEC, ':t')") + if comspecshell == 'cmd.exe' then + helpers.source('let &shell=$COMSPEC') + eq('"a b"\n', eval([[system('echo "a b"')]])) + else + pending('$COMSPEC is not cmd.exe ' .. comspecshell) + end + end) + + it('works with powershell', function() + helpers.set_shell_powershell() + eq('a\nb\n', eval([[system('echo a b')]])) + eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]])) + eq('a b\n', eval([[system('echo "a b"')]])) + end) + end it('`echo` and waits for its return', function() feed(':call system("echo")<cr>') |