aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/eval.txt22
-rw-r--r--src/nvim/event/libuv_process.c22
-rw-r--r--test/functional/eval/system_spec.lua38
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>')