diff options
author | Lewis Russell <lewis6991@gmail.com> | 2023-09-04 11:30:16 +0100 |
---|---|---|
committer | Lewis Russell <lewis6991@gmail.com> | 2023-09-05 17:10:01 +0100 |
commit | 6d5f12efd286c684de8608c07bb0f76a9d594b5b (patch) | |
tree | aaf460e24971f3b03d29e20b965cfebc18b17fad /runtime/lua/vim/_system.lua | |
parent | a44521f46e6f79171d034e5cce1a4dc266d23e49 (diff) | |
download | rneovim-6d5f12efd286c684de8608c07bb0f76a9d594b5b.tar.gz rneovim-6d5f12efd286c684de8608c07bb0f76a9d594b5b.tar.bz2 rneovim-6d5f12efd286c684de8608c07bb0f76a9d594b5b.zip |
fix(vim.system): make timeout work properly
Mimic the behaviour of timeout(1) from coreutils.
Diffstat (limited to 'runtime/lua/vim/_system.lua')
-rw-r--r-- | runtime/lua/vim/_system.lua | 102 |
1 files changed, 64 insertions, 38 deletions
diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua index ba3fc34e78..7bd7ff91f9 100644 --- a/runtime/lua/vim/_system.lua +++ b/runtime/lua/vim/_system.lua @@ -2,8 +2,8 @@ local uv = vim.uv --- @class SystemOpts --- @field stdin? string|string[]|true ---- @field stdout? fun(err:string, data: string)|false ---- @field stderr? fun(err:string, data: string)|false +--- @field stdout? fun(err:string?, data: string?)|false +--- @field stderr? fun(err:string?, data: string?)|false --- @field cwd? string --- @field env? table<string,string|number> --- @field clear_env? boolean @@ -18,39 +18,46 @@ local uv = vim.uv --- @field stderr? string --- @class SystemState ---- @field cmd string[] --- @field handle? uv.uv_process_t --- @field timer? uv.uv_timer_t --- @field pid? integer --- @field timeout? integer ---- @field done? boolean +--- @field done? boolean|'timeout' --- @field stdin? uv.uv_stream_t --- @field stdout? uv.uv_stream_t --- @field stderr? uv.uv_stream_t --- @field result? SystemCompleted ----@param state SystemState -local function close_handles(state) - for _, handle in pairs({ state.handle, state.stdin, state.stdout, state.stderr }) do - if not handle:is_closing() then - handle:close() - end +--- @enum vim.SystemSig +local SIG = { + HUP = 1, -- Hangup + INT = 2, -- Interrupt from keyboard + KILL = 9, -- Kill signal + TERM = 15, -- Termination signal + -- STOP = 17,19,23 -- Stop the process +} + +---@param handle uv.uv_handle_t? +local function close_handle(handle) + if handle and not handle:is_closing() then + handle:close() end end ---- @param cmd string[] ---- @return SystemCompleted -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 } +---@param state 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 SystemObj --- @field pid integer --- @field private _state SystemState --- @field wait fun(self: SystemObj, timeout?: integer): SystemCompleted ---- @field kill fun(self: SystemObj, signal: integer) +--- @field kill fun(self: SystemObj, signal: integer|string) --- @field write fun(self: SystemObj, data?: string|string[]) --- @field is_closing fun(self: SystemObj): boolean? local SystemObj = {} @@ -69,6 +76,13 @@ function SystemObj:kill(signal) self._state.handle:kill(signal) end +--- @package +--- @param signal? vim.SystemSig +function SystemObj:_timeout(signal) + self._state.done = 'timeout' + self:kill(signal or SIG.TERM) +end + local MAX_TIMEOUT = 2 ^ 31 --- @param timeout? integer @@ -76,13 +90,16 @@ local MAX_TIMEOUT = 2 ^ 31 function SystemObj:wait(timeout) local state = self._state - vim.wait(timeout or state.timeout or MAX_TIMEOUT, function() - return state.done + local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function() + return state.result ~= nil end) - if not state.done then - self:kill(6) -- 'sigint' - state.result = timeout_result(state.cmd) + if not done then + -- Send sigkill since this cannot be caught + self:_timeout(SIG.KILL) + vim.wait(timeout or state.timeout or MAX_TIMEOUT, function() + return state.result ~= nil + end) end return state.result @@ -124,9 +141,9 @@ function SystemObj:is_closing() return handle == nil or handle:is_closing() end ----@param output function|'false' +---@param output fun(err:string?, data: string?)|false ---@return uv.uv_stream_t? ----@return function? Handler +---@return fun(err:string?, data: string?)? Handler local function setup_output(output) if output == nil then return assert(uv.new_pipe(false)), nil @@ -224,6 +241,19 @@ 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 +local function timer_oneshot(timeout, cb) + local timer = assert(uv.new_timer()) + timer:start(timeout, 0, function() + timer:stop() + timer:close() + cb() + end) + return timer +end + --- Run a system command --- --- @param cmd string[] @@ -267,10 +297,6 @@ function M.run(cmd, opts, on_exit) hide = true, }, function(code, signal) close_handles(state) - if state.timer then - state.timer:stop() - state.timer:close() - end local check = assert(uv.new_check()) @@ -283,7 +309,14 @@ function M.run(cmd, opts, on_exit) check:stop() check:close() - state.done = true + if state.done == nil then + state.done = true + end + + if code == 0 and state.done == 'timeout' then + code = 124 + end + state.result = { code = code, signal = signal, @@ -317,16 +350,9 @@ function M.run(cmd, opts, on_exit) end if opts.timeout then - state.timer = assert(uv.new_timer()) - state.timer:start(opts.timeout, 0, function() - state.timer:stop() - state.timer:close() + state.timer = timer_oneshot(opts.timeout, function() if state.handle and state.handle:is_active() then - obj:kill(6) --- 'sigint' - state.result = timeout_result(state.cmd) - if on_exit then - on_exit(state.result) - end + obj:_timeout() end end) end |