diff options
author | Lewis Russell <lewis6991@gmail.com> | 2023-06-07 13:52:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-07 13:52:23 +0100 |
commit | c0952e62fd0ee16a3275bb69e0de04c836b39015 (patch) | |
tree | ebecfe9f07b4e5d5a306b83a886372da269d38f9 /runtime/lua/vim/lsp/rpc.lua | |
parent | 4ecc71f6fc7377403ed91ae5bc32992a5d08f678 (diff) | |
download | rneovim-c0952e62fd0ee16a3275bb69e0de04c836b39015.tar.gz rneovim-c0952e62fd0ee16a3275bb69e0de04c836b39015.tar.bz2 rneovim-c0952e62fd0ee16a3275bb69e0de04c836b39015.zip |
feat(lua): add `vim.system()`
feat(lua): add vim.system()
Problem:
Handling system commands in Lua is tedious and error-prone:
- vim.fn.jobstart() is vimscript and comes with all limitations attached to typval.
- vim.loop.spawn is too low level
Solution:
Add vim.system().
Partly inspired by Python's subprocess module
Does not expose any libuv objects.
Diffstat (limited to 'runtime/lua/vim/lsp/rpc.lua')
-rw-r--r-- | runtime/lua/vim/lsp/rpc.lua | 124 |
1 files changed, 47 insertions, 77 deletions
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 5f48effebf..64bc732bdf 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -15,32 +15,6 @@ local function is_dir(filename) end ---@private ---- Merges current process env with the given env and returns the result as ---- a list of "k=v" strings. ---- ---- <pre> ---- Example: ---- ---- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", } ---- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", } ---- </pre> ----@param env (table) table of environment variable assignments ----@returns (table) list of `"k=v"` strings -local function env_merge(env) - if env == nil then - return env - end - -- Merge. - env = vim.tbl_extend('force', vim.fn.environ(), env) - local final_env = {} - for k, v in pairs(env) do - assert(type(k) == 'string', 'env must be a dict') - table.insert(final_env, k .. '=' .. tostring(v)) - end - return final_env -end - ----@private --- Embeds the given string into a table and correctly computes `Content-Length`. --- ---@param encoded_message (string) @@ -658,89 +632,85 @@ end --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. local function start(cmd, cmd_args, dispatchers, extra_spawn_params) - local _ = log.info() - and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) + if log.info() then + log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) + end + validate({ cmd = { cmd, 's' }, cmd_args = { cmd_args, 't' }, dispatchers = { dispatchers, 't', true }, }) - if extra_spawn_params and extra_spawn_params.cwd then + extra_spawn_params = extra_spawn_params or {} + + if extra_spawn_params.cwd then assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory') end dispatchers = merge_dispatchers(dispatchers) - local stdin = uv.new_pipe(false) - local stdout = uv.new_pipe(false) - local stderr = uv.new_pipe(false) - local handle, pid + + local sysobj ---@type SystemObj local client = new_client(dispatchers, { write = function(msg) - stdin:write(msg) + sysobj:write(msg) end, is_closing = function() - return handle == nil or handle:is_closing() + return sysobj == nil or sysobj:is_closing() end, terminate = function() - if handle then - handle:kill(15) - end + sysobj:kill(15) end, }) - ---@private - --- Callback for |vim.uv.spawn()| Closes all streams and runs the `on_exit` dispatcher. - ---@param code (integer) Exit code - ---@param signal (integer) Signal that was used to terminate (if any) - local function onexit(code, signal) - stdin:close() - stdout:close() - stderr:close() - handle:close() - dispatchers.on_exit(code, signal) + local handle_body = function(body) + client:handle_body(body) end - local spawn_params = { - args = cmd_args, - stdio = { stdin, stdout, stderr }, - detached = not is_win, - } - if extra_spawn_params then - spawn_params.cwd = extra_spawn_params.cwd - spawn_params.env = env_merge(extra_spawn_params.env) - if extra_spawn_params.detached ~= nil then - spawn_params.detached = extra_spawn_params.detached + + local stdout_handler = create_read_loop(handle_body, nil, function(err) + client:on_error(client_errors.READ_ERROR, err) + end) + + local stderr_handler = function(_, chunk) + if chunk and log.error() then + log.error('rpc', cmd, 'stderr', chunk) end end - handle, pid = uv.spawn(cmd, spawn_params, onexit) - if handle == nil then - stdin:close() - stdout:close() - stderr:close() + + local detached = not is_win + if extra_spawn_params.detached ~= nil then + detached = extra_spawn_params.detached + end + + 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, + cwd = extra_spawn_params.cwd, + env = extra_spawn_params.env, + detach = detached, + }, function(obj) + dispatchers.on_exit(obj.code, obj.signal) + end) + + if not ok then + local err = sysobj_or_err --[[@as string]] local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) - if string.match(pid, 'ENOENT') then + if string.match(err, 'ENOENT') then msg = msg .. '. The language server is either not installed, missing from PATH, or not executable.' else - msg = msg .. string.format(' with error message: %s', pid) + msg = msg .. string.format(' with error message: %s', err) end vim.notify(msg, vim.log.levels.WARN) return end - stderr:read_start(function(_, chunk) - if chunk then - local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk) - end - end) - - local handle_body = function(body) - client:handle_body(body) - end - stdout:read_start(create_read_loop(handle_body, nil, function(err) - client:on_error(client_errors.READ_ERROR, err) - end)) + sysobj = sysobj_or_err --[[@as SystemObj]] return public_client(client) end |