Using External Processes in Neovim¶
Table of Contents¶
- Spawning External Processes with
vim.system()
- Spawning External Processes with
vim.uv
uv.spawn()
Parameters- Full Example Script
Spawning External Processes with vim.system()
¶
Runs a system command or throws an error if {cmd}
cannot be run.
vim.system({cmd}, {opts}, {on_exit})
vim.system()
Examples:¶
local on_exit = function(obj)
print(obj.code)
print(obj.signal)
print(obj.stdout)
print(obj.stderr)
end
-- Runs asynchronously:
vim.system({'echo', 'hello'}, { text = true }, on_exit)
-- Runs synchronously:
local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
-- Output:
-- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
Note: unlike uv.spawn()
, vim.system
throws an error if {cmd}
cannot be run.
vim.system({'ls'}, {
on_stdout = function(chan_id, data, name)
vim.notify(vim.inspect(data) .. '\n' .. vim.inspect(name) .. '\n' .. vim.inspect(chan_id))
end
Spawning External Processes with vim.uv
¶
Neovim's vim.uv
API is a Lua interface to libuv, an asynchronous
I/O library used by Neovim for non-blocking operations.
Creating Pipes¶
local stdin = vim.uv.new_pipe()
local stdout = vim.uv.new_pipe()
local stderr = vim.uv.new_pipe()
vim.uv.new_pipe()
creates new pipe objects for standard input, output, and error.* These pipes are used to communicate with the spawned process.
Debugging Prints¶
print('stdin', stdin)
print('stdout', stdout)
print('stderr', stderr)
Spawning the External Process¶
local handle, pid = vim.uv.spawn('cat', {
stdio = { stdin, stdout, stderr },
}, function(code, signal) -- on exit
print('exit code', code)
print('exit signal', signal)
end)
vim.uv.spawn
is used to start an external process (cat
in this instance), with the stdio
option set to the previously created pipes, allowing the process to interact with Neovim.* The anonymous function provided as the last argument to
spawn
is a callback that will be called when the process exits, printing the exit code and signal.
Monitoring Process Output¶
print('process opened', handle, pid)
-- One read for stdout and one for stderr
vim.uv.read_start(stdout, function(err, data)
assert(not err, err)
if data then
print('stdout chunk', stdout, data)
else
print('stdout end', stdout)
end
end)
vim.uv.read_start(stderr, function(err, data)
assert(not err, err)
if data then
print('stderr chunk', stderr, data)
else
print('stderr end', stderr)
end
end)
stdout
and stderr
pipes asynchronously.* When data is received, it prints the data chunk to Neovim's command line.
* If an error occurs, it's asserted to ensure it's handled properly.
* When there's no more data (indicating the end of the stream), it prints
a message indicating the end.
Writing to the Process's Standard Input¶
vim.uv.write(stdin, 'Hello World')
'Hello World'
to the process's standard input through the stdin
pipe.
Closing the Standard Input¶
vim.uv.shutdown(stdin, function()
print('stdin shutdown', stdin)
if handle then
vim.uv.close(handle, function()
print('process closed', handle, pid)
end)
end
end)
stdin
pipe.* The callback function is called once the shutdown is complete, printing a message to indicate this.
* It's important to shutdown the
stdin
to signal to the spawned
process (in this case, cat
) so that no more data will be sent.
Closing the Process¶
if handle then
vim.uv.close(handle, function()
print('process closed', handle, pid)
end)
end
stdin
, this code checks if the process handle is still active.* If the process handle is still active, it closes the handle using
vim.uv.close
.* The callback function prints a message indicating that the process
has been closed, along with the process handle and PID for confirmation.
uv.spawn()
Parameters:¶
uv.spawn()
accepts the parameters:
* path
: string
* options
: table
* on_exit
: callable
* code
: integer
* signal
: integer
Returns:
* handle
(uv_process_t userdata
)
* PID
(integer
)
local stdin = vim.uv.new_pipe()
local stdout = vim.uv.new_pipe()
local stderr = vim.uv.new_pipe()
uv.spawn("cat", {
stdio = {stdin, stdout, stderr} -- These must be made with new pipes (`vim.uv.new_pipe()`)
}, function(code, signal) -- on exit
print("exit code", code)
print("exit signal", signal)
end)
uv.spawn()
's options
parameter¶
The options
table accepts the fields:
-
options.args
- CLI arguments as a list of strings.- The first string should not be the path to the program, since that is already
provided viapath
. - On Windows, this uses
CreateProcess
which concatenates the arguments into a string. - This can cause some strange errors (see
options.verbatim
below for Windows).
- The first string should not be the path to the program, since that is already
-
options.stdio
- Set the file descriptors that will be made available to the child process.
- The convention is that the first entries are stdin, stdout, and stderr.
-
options.env
- Set environment variables for the new process.
options.cwd
- Set the current working directory for the sub-process.
options.uid
- Set the child process' user id.
options.gid
- Set the child process' group id.
-
options.verbatim
- If true, do not wrap any arguments in quotes (or any other escaping) when
converting the argument list into a command line string. - This option is only meaningful on Windows systems.
- On Unix it is silently ignored.
- If true, do not wrap any arguments in quotes (or any other escaping) when
-
options.detached
- If true, spawn the child process in a detached state
- This will make it a process group leader, and will effectively enable
the child to keep running after the parent exits. - The child process will still keep the parent's event loop alive unless the parent
process callsuv.unref()
on the child's process handle.
-
options.hide
- If true, hide the subprocess console window that would normally be created.
- This option is only meaningful on Windows systems.
- On Unix it is silently ignored.
options.stdio
¶
The options.stdio
entries can take many shapes.
- If they are numbers, then the child process inherits that
same zero-indexedfd
from the parent process. - If
uv_stream_t
handles are passed in, those are used as a read-write pipe or
inherited stream depending if the stream has a validfd
. - Including
nil
placeholders means to ignore thatfd
in the child process.
When the child process exits, on_exit
is called with an exit code and signal.
uv.spawn()
returns: uv_process_t userdata
(handle), integer
(PID)
Full Example Script¶
:h uv.spawn¶
local stdin = vim.uv.new_pipe()
local stdout = vim.uv.new_pipe()
local stderr = vim.uv.new_pipe()
print('stdin', stdin)
print('stdout', stdout)
print('stderr', stderr)
local handle, pid = vim.uv.spawn('cat', {
stdio = { stdin, stdout, stderr },
}, function(code, signal) -- on exit
print('exit code', code)
print('exit signal', signal)
end)
print('process opened', handle, pid)
vim.uv.read_start(stdout, function(err, data)
assert(not err, err)
if data then
print('stdout chunk', stdout, data)
else
print('stdout end', stdout)
end
end)
vim.uv.read_start(stderr, function(err, data)
assert(not err, err)
if data then
print('stderr chunk', stderr, data)
else
print('stderr end', stderr)
end
end)
vim.uv.write(stdin, 'Hello World')
vim.uv.shutdown(stdin, function()
print('stdin shutdown', stdin)
if handle then
vim.uv.close(handle, function()
print('process closed', handle, pid)
end)
end
end)