Skip to content

Commit

Permalink
fixup! simplify cmd and args
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis6991 committed Jun 7, 2023
1 parent a4dfcca commit 7509ddd
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 112 deletions.
17 changes: 6 additions & 11 deletions runtime/doc/lua.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1566,7 +1566,7 @@ schedule_wrap({cb}) *vim.schedule_wrap()*
|vim.schedule()|
|vim.in_fast_event()|

system({spec}, {on_exit}) *vim.system()*
system({cmd}, {opts}, {on_exit}) *vim.system()*
Run a system command

Examples: >lua
Expand All @@ -1579,23 +1579,18 @@ system({spec}, {on_exit}) *vim.system()*
end

-- Run asynchronously
vim.system({'echo', 'hello'}, on_exit)
vim.system({cmd = {'echo', 'hello'}}, on_exit)
vim.system({cmd = 'echo', args = {'hello'}}, on_exit)
vim.system({'echo', 'hello'}, { text = true }, on_exit)

-- Run synchronously
local obj = vim.system({'echo', 'hello'}):wait()
local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
-- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
<

See |uv.spawn()| for more details.

Parameters: ~
{spec} (string[]|SystemSpec) When passed as a string array, the
argument is interpreted as a command. Accepts the options:
• cmd: (string|string[]) command to execute
• args: (string[]) Command line arguments. Ignored when cmd
is a string[]
{cmd} (string[]) Command to execute
{opts} (SystemOpts|nil) Options:
• cwd: (string) Set the current working directory for the
sub-process.
• env: table<string,string> Set environment variables for
Expand Down Expand Up @@ -1623,7 +1618,7 @@ system({spec}, {on_exit}) *vim.system()*
process will still keep the parent's event loop alive
unless the parent process calls |uv.unref()| on the
child's process handle.
• {on_exit} function|nil Called when subprocess exits. When provided,
• {on_exit} (function|nil) Called when subprocess exits. When provided,
the command runs asynchronously. Receives SystemCompleted
object, see return of SystemObj:wait().

Expand Down
2 changes: 1 addition & 1 deletion runtime/lua/man.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ end
---@param env? table<string,string|number>
---@return string
local function system(cmd, silent, env)
local r = vim.system({ cmd = cmd, env = env, timeout = 10000 }):wait()
local r = vim.system(cmd, { env = env, timeout = 10000 }):wait()

if r.code ~= 0 and not silent then
local cmd_str = table.concat(cmd, ' ')
Expand Down
42 changes: 20 additions & 22 deletions runtime/lua/vim/_editor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ vim.log = {
},
}

-- TODO(lewis6991): document that the signature is system({cmd}, [{opts},] {on_exit})
--- Run a system command
---
--- Examples:
Expand All @@ -78,27 +79,23 @@ vim.log = {
--- end
---
--- -- Run asynchronously
--- vim.system({'echo', 'hello'}, on_exit)
--- vim.system({cmd = {'echo', 'hello'}}, on_exit)
--- vim.system({cmd = 'echo', args = {'hello'}}, on_exit)
--- vim.system({'echo', 'hello'}, { text = true }, on_exit)
---
--- -- Run synchronously
--- local obj = vim.system({'echo', 'hello'}):wait()
--- local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
--- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
---
--- </pre>
---
--- See |uv.spawn()| for more details.
---
--- @param spec (string[]|SystemSpec) When passed as a string array, the argument is interpreted
--- as a command. Accepts the options:
--- - cmd: (string|string[]) command to execute
--- - args: (string[]) Command line arguments. Ignored when cmd is a string[]
--- @param cmd (string[]) Command to execute
--- @param opts (SystemOpts|nil) Options:
--- - cwd: (string) Set the current working directory for the sub-process.
--- - env: table<string,string> Set environment variables for the new
--- process. Inherits the current environment with `NVIM` set to |v:servername|.
--- - clear_env: (boolean) `env` defines the job environment
--- exactly, instead of merging current environment.
--- - env: table<string,string> Set environment variables for the new process. Inherits the
--- current environment with `NVIM` set to |v:servername|.
--- - clear_env: (boolean) `env` defines the job environment exactly, instead of merging current
--- environment.
--- - stdin: (string|string[]|boolean) If `true`, then a pipe to stdin is opened and can be written
--- to via the `write()` method to SystemObj. If string or string[] then will be written to stdin
--- and closed. Defaults to `false`.
Expand All @@ -110,14 +107,12 @@ vim.log = {
--- Defaults to `true`.
--- - text: (boolean) Handle stdout and stderr as text. Replaces `\r\n` with `\n`.
--- - timeout: (integer)
--- - detach: (boolean) 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. Note that the child process will
--- still keep the parent's event loop alive unless the parent
--- process calls |uv.unref()| on the child's process handle.
--- - detach: (boolean) 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. Note that the child process will still keep the parent's event loop alive
--- unless the parent process calls |uv.unref()| on the child's process handle.
---
--- @param on_exit function|nil Called when subprocess exits. When provided, the command runs
--- @param on_exit (function|nil) Called when subprocess exits. When provided, the command runs
--- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait().
---
--- @return SystemObj Object with the fields:
Expand All @@ -131,9 +126,12 @@ vim.log = {
--- - kill (fun(signal: integer))
--- - write (fun(data: string|nil)) Requires `stdin=true`. Pass `nil` to close the stream.
--- - is_closing (fun(): boolean)
function vim.system(spec, on_exit)
---@diagnostic disable-next-line:return-type-mismatch,param-type-mismatch
return require('vim._system').run(spec, on_exit)
function vim.system(cmd, opts, on_exit)
if type(opts) == 'function' then
on_exit = opts
opts = nil
end
return require('vim._system').run(cmd, opts, on_exit)
end

-- Gets process info from the `ps` command.
Expand Down
89 changes: 28 additions & 61 deletions runtime/lua/vim/_system.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
local uv = vim.uv

--- @class SystemSpec
--- @field cmd string|string[]
--- @field args? string[]
--- @class SystemOpts
--- @field cmd string[]
--- @field stdin string|string[]|true
--- @field stdout fun(err:string, data: string)|false
--- @field stderr fun(err:string, data: string)|false
Expand All @@ -28,8 +27,7 @@ local uv = vim.uv
--- @field stdin uv_stream_t?
--- @field stdout uv_stream_t?
--- @field stderr uv_stream_t?
--- @field cmd string
--- @field args? string[]
--- @field cmd string[]
--- @field result? SystemCompleted

---@private
Expand All @@ -42,11 +40,10 @@ local function close_handles(state)
end
end

--- @param cmd string
--- @param args string[]?
--- @param cmd string[]
--- @return SystemCompleted
local function timeout_result(cmd, args)
local cmd_str = cmd .. ' ' .. table.concat(args or {}, ' ')
local function timeout_result(cmd)
local cmd_str = table.concat(cmd, ' ')
local err = string.format("Command timed out: '%s'", cmd_str)
return { code = 0, signal = 2, stdout = '', stderr = err }
end
Expand Down Expand Up @@ -89,7 +86,7 @@ function SystemObj:wait(timeout)

if not state.done then
self:kill(6) -- 'sigint'
state.result = timeout_result(state.cmd, state.args)
state.result = timeout_result(state.cmd)
end

return state.result
Expand Down Expand Up @@ -131,21 +128,6 @@ function SystemObj:is_closing()
return handle == nil or handle:is_closing()
end

--- @param spec string[]|SystemSpec
--- @return SystemSpec
local function process_spec(spec)
if spec[1] then
local cmd = {} --- @type string[]
for _, p in ipairs(spec) do
cmd[#cmd + 1] = p
end
spec = vim.deepcopy(spec)
spec.cmd = cmd
end

return spec
end

---@private
---@param output function|'false'
---@return uv_stream_t?
Expand Down Expand Up @@ -180,21 +162,6 @@ local function setup_input(input)
return assert(uv.new_pipe(false)), towrite
end

--- @param cmd string|string[]
--- @param args? string[]
--- @return string Command
--- @return string[]? Arguments
local function setup_cmd(cmd, args)
if type(cmd) == 'string' then
cmd = { cmd }
if args then
vim.list_extend(cmd, args)
end
end

return cmd[1], vim.list_slice(cmd, 2)
end

--- @return table<string,string>
local function base_env()
local env = vim.fn.environ()
Expand Down Expand Up @@ -265,45 +232,45 @@ end

--- Run a system command
---
--- @param user_spec string[]|SystemSpec
--- @param cmd string[]
--- @param opts? SystemOpts
--- @param on_exit? fun(out: SystemCompleted)
--- @return SystemObj
function M.run(user_spec, on_exit)
function M.run(cmd, opts, on_exit)
vim.validate({
user_spec = { user_spec, 'table' },
cmd = { opts, 'table' },
opts = { opts, 'table', true },
on_exit = { on_exit, 'function', true },
})

local spec = process_spec(user_spec)
opts = opts or {}

local stdout, stdout_handler = setup_output(spec.stdout)
local stderr, stderr_handler = setup_output(spec.stderr)
local stdin, towrite = setup_input(spec.stdin)
local stdout, stdout_handler = setup_output(opts.stdout)
local stderr, stderr_handler = setup_output(opts.stderr)
local stdin, towrite = setup_input(opts.stdin)

--- @type SystemState
local state = {
done = false,
timeout = spec.timeout,
-- Pipes which are being automatically managed.
-- Don't manage if pipe was passed directly in spec
timeout = opts.timeout,
stdin = stdin,
stdout = stdout,
stderr = stderr,
}

state.cmd, state.args = setup_cmd(spec.cmd, spec.args)
state.cmd = cmd

-- Define data buckets as tables and concatenate the elements at the end as
-- one operation.
--- @type string[], string[]
local stdout_data, stderr_data

state.handle, state.pid = spawn(state.cmd, {
args = state.args,
state.handle, state.pid = spawn(cmd[1], {
args = vim.list_slice(cmd, 2),
stdio = { stdin, stdout, stderr },
cwd = spec.cwd,
env = setup_env(spec.env, spec.clear_env),
detached = spec.detach,
cwd = opts.cwd,
env = setup_env(opts.env, opts.clear_env),
detached = opts.detach,
hide = true,
}, function(code, signal)
close_handles(state)
Expand Down Expand Up @@ -340,12 +307,12 @@ function M.run(user_spec, on_exit)

if stdout then
stdout_data = {}
stdout:read_start(stdout_handler or default_handler(stdout, spec.text, stdout_data))
stdout:read_start(stdout_handler or default_handler(stdout, opts.text, stdout_data))
end

if stderr then
stderr_data = {}
stderr:read_start(stderr_handler or default_handler(stderr, spec.text, stderr_data))
stderr:read_start(stderr_handler or default_handler(stderr, opts.text, stderr_data))
end

local obj = new_systemobj(state)
Expand All @@ -355,14 +322,14 @@ function M.run(user_spec, on_exit)
obj:write(nil) -- close the stream
end

if spec.timeout then
if opts.timeout then
state.timer = assert(uv.new_timer())
state.timer:start(spec.timeout, 0, function()
state.timer:start(opts.timeout, 0, function()
state.timer:stop()
state.timer:close()
if state.handle and state.handle:is_active() then
obj:kill(6) --- 'sigint'
state.result = timeout_result(state.cmd, state.args)
state.result = timeout_result(state.cmd)
if on_exit then
on_exit(state.result)
end
Expand Down
7 changes: 4 additions & 3 deletions runtime/lua/vim/lsp/rpc.lua
Original file line number Diff line number Diff line change
Expand Up @@ -683,9 +683,10 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
detached = extra_spawn_params.detached
end

local ok, sysobj_or_err = pcall(vim.system, {
cmd = cmd,
args = cmd_args,
local cmd1 = { cmd }
vim.list_extend(cmd1, cmd_args)

local ok, sysobj_or_err = pcall(vim.system, cmd1, {
stdin = true,
stdout = stdout_handler,
stderr = stderr_handler,
Expand Down
23 changes: 9 additions & 14 deletions test/functional/lua/system_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@ local clear = helpers.clear
local exec_lua = helpers.exec_lua
local eq = helpers.eq

local function system_sync(spec)
local function system_sync(cmd, opts)
return exec_lua([[
return vim.system(...):wait()
]], spec)
]], cmd, opts)
end

local function system_async(spec)
local function system_async(cmd, opts)
exec_lua([[
local spec = ...
local cmd, opts = ...
_G.done = false
vim.system(spec, function(obj)
vim.system(cmd, opts, function(obj)
_G.done = true
_G.ret = obj
end)
]], spec)
]], cmd, opts)

while true do
if exec_lua[[return _G.done]] then
Expand All @@ -36,14 +36,11 @@ describe('vim.system', function()
for name, system in pairs{ sync = system_sync, async = system_async, } do
describe('('..name..')', function()
it('can run simple commands', function()
eq('hello\n', system({'echo', 'hello', text = true}).stdout)
eq('hello\n', system({ cmd = {'echo', 'hello' }, text = true }).stdout)
eq('hello\n', system({ cmd = 'echo', args = {'hello'}, text = true }).stdout)
eq('hello\n', system({'echo', 'hello' }, { text = true }).stdout)
end)

it('handle input', function()
eq('hellocat', system({ cmd = 'cat', stdin = 'hellocat', text = true }).stdout)
eq('hello\ncat\n', system({ cmd = 'cat', stdin = {'hello', 'cat'}, text = true }).stdout)
eq('hellocat', system({ 'cat' }, { stdin = 'hellocat', text = true }).stdout)
end)

it ('supports timeout', function()
Expand All @@ -52,9 +49,7 @@ describe('vim.system', function()
signal = 2,
stdout = '',
stderr = "Command timed out: 'sleep 10'"
}, system {
cmd = {'sleep', '10'}, timeout = 1
})
}, system({ 'sleep', '10' }, { timeout = 1 }))
end)
end)
end
Expand Down

0 comments on commit 7509ddd

Please sign in to comment.