aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/_system.lua
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
committerJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
commitd5f194ce780c95821a855aca3c19426576d28ae0 (patch)
treed45f461b19f9118ad2bb1f440a7a08973ad18832 /runtime/lua/vim/_system.lua
parentc5d770d311841ea5230426cc4c868e8db27300a8 (diff)
parent44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff)
downloadrneovim-rahm.tar.gz
rneovim-rahm.tar.bz2
rneovim-rahm.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309HEADrahm
Diffstat (limited to 'runtime/lua/vim/_system.lua')
-rw-r--r--runtime/lua/vim/_system.lua129
1 files changed, 69 insertions, 60 deletions
diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
index ce5dbffeaa..157172447a 100644
--- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -47,15 +47,6 @@ local function close_handle(handle)
end
end
----@param state vim.SystemState
-local function close_handles(state)
- close_handle(state.handle)
- close_handle(state.stdin)
- close_handle(state.stdout)
- close_handle(state.stderr)
- close_handle(state.timer)
-end
-
--- @class vim.SystemObj
--- @field cmd string[]
--- @field pid integer
@@ -88,7 +79,8 @@ function SystemObj:_timeout(signal)
self:kill(signal or SIG.TERM)
end
-local MAX_TIMEOUT = 2 ^ 31
+-- Use max 32-bit signed int value to avoid overflow on 32-bit systems. #31633
+local MAX_TIMEOUT = 2 ^ 31 - 1
--- @param timeout? integer
--- @return vim.SystemCompleted
@@ -132,9 +124,7 @@ function SystemObj:write(data)
-- (https://github.com/neovim/neovim/pull/17620#discussion_r820775616)
stdin:write('', function()
stdin:shutdown(function()
- if stdin then
- stdin:close()
- end
+ close_handle(stdin)
end)
end)
end
@@ -146,25 +136,52 @@ function SystemObj:is_closing()
return handle == nil or handle:is_closing() or false
end
----@param output fun(err:string?, data: string?)|false
----@return uv.uv_stream_t?
----@return fun(err:string?, data: string?)? Handler
-local function setup_output(output)
- if output == nil then
- return assert(uv.new_pipe(false)), nil
+--- @param output? uv.read_start.callback|false
+--- @param text? boolean
+--- @return uv.uv_stream_t? pipe
+--- @return uv.read_start.callback? handler
+--- @return string[]? data
+local function setup_output(output, text)
+ if output == false then
+ return
end
+ local bucket --- @type string[]?
+ local handler --- @type uv.read_start.callback
+
if type(output) == 'function' then
- return assert(uv.new_pipe(false)), output
+ handler = output
+ else
+ bucket = {}
+ handler = function(err, data)
+ if err then
+ error(err)
+ end
+ if text and data then
+ bucket[#bucket + 1] = data:gsub('\r\n', '\n')
+ else
+ bucket[#bucket + 1] = data
+ end
+ end
end
- assert(output == false)
- return nil, nil
+ local pipe = assert(uv.new_pipe(false))
+
+ --- @type uv.read_start.callback
+ local function handler_with_close(err, data)
+ handler(err, data)
+ if data == nil then
+ pipe:read_stop()
+ pipe:close()
+ end
+ end
+
+ return pipe, handler_with_close, bucket
end
----@param input string|string[]|true|nil
----@return uv.uv_stream_t?
----@return string|string[]?
+--- @param input? string|string[]|boolean
+--- @return uv.uv_stream_t?
+--- @return string|string[]?
local function setup_input(input)
if not input then
return
@@ -208,28 +225,6 @@ local function setup_env(env, clear_env)
return renv
end
---- @param stream uv.uv_stream_t
---- @param text? boolean
---- @param bucket string[]
---- @return fun(err: string?, data: string?)
-local function default_handler(stream, text, bucket)
- return function(err, data)
- if err then
- error(err)
- end
- if data ~= nil then
- if text then
- bucket[#bucket + 1] = data:gsub('\r\n', '\n')
- else
- bucket[#bucket + 1] = data
- end
- else
- stream:read_stop()
- stream:close()
- end
- end
-end
-
local is_win = vim.fn.has('win32') == 1
local M = {}
@@ -255,9 +250,9 @@ local function spawn(cmd, opts, on_exit, on_error)
return handle, pid_or_err --[[@as integer]]
end
----@param timeout integer
----@param cb fun()
----@return uv.uv_timer_t
+--- @param timeout integer
+--- @param cb fun()
+--- @return uv.uv_timer_t
local function timer_oneshot(timeout, cb)
local timer = assert(uv.new_timer())
timer:start(timeout, 0, function()
@@ -273,7 +268,12 @@ end
--- @param signal integer
--- @param on_exit fun(result: vim.SystemCompleted)?
local function _on_exit(state, code, signal, on_exit)
- close_handles(state)
+ close_handle(state.handle)
+ close_handle(state.stdin)
+ close_handle(state.timer)
+
+ -- #30846: Do not close stdout/stderr here, as they may still have data to
+ -- read. They will be closed in uv.read_start on EOF.
local check = assert(uv.new_check())
check:start(function()
@@ -311,6 +311,15 @@ local function _on_exit(state, code, signal, on_exit)
end)
end
+--- @param state vim.SystemState
+local function _on_error(state)
+ close_handle(state.handle)
+ close_handle(state.stdin)
+ close_handle(state.stdout)
+ close_handle(state.stderr)
+ close_handle(state.timer)
+end
+
--- Run a system command
---
--- @param cmd string[]
@@ -324,8 +333,8 @@ function M.run(cmd, opts, on_exit)
opts = opts or {}
- local stdout, stdout_handler = setup_output(opts.stdout)
- local stderr, stderr_handler = setup_output(opts.stderr)
+ local stdout, stdout_handler, stdout_data = setup_output(opts.stdout, opts.text)
+ local stderr, stderr_handler, stderr_data = setup_output(opts.stderr, opts.text)
local stdin, towrite = setup_input(opts.stdin)
--- @type vim.SystemState
@@ -335,7 +344,9 @@ function M.run(cmd, opts, on_exit)
timeout = opts.timeout,
stdin = stdin,
stdout = stdout,
+ stdout_data = stdout_data,
stderr = stderr,
+ stderr_data = stderr_data,
}
--- @diagnostic disable-next-line:missing-fields
@@ -350,17 +361,15 @@ function M.run(cmd, opts, on_exit)
}, function(code, signal)
_on_exit(state, code, signal, on_exit)
end, function()
- close_handles(state)
+ _on_error(state)
end)
- if stdout then
- state.stdout_data = {}
- stdout:read_start(stdout_handler or default_handler(stdout, opts.text, state.stdout_data))
+ if stdout and stdout_handler then
+ stdout:read_start(stdout_handler)
end
- if stderr then
- state.stderr_data = {}
- stderr:read_start(stderr_handler or default_handler(stderr, opts.text, state.stderr_data))
+ if stderr and stderr_handler then
+ stderr:read_start(stderr_handler)
end
local obj = new_systemobj(state)