mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	 Lewis Russell
					Lewis Russell
				
			
				
					committed by
					
						 Lewis Russell
						Lewis Russell
					
				
			
			
				
	
			
			
			 Lewis Russell
						Lewis Russell
					
				
			
						parent
						
							56d11b494b
						
					
				
				
					commit
					734dba04d1
				
			| @@ -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 | ||||
| @@ -132,9 +123,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 +135,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 +224,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 +249,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 +267,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 +310,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 +332,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 +343,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 +360,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) | ||||
|   | ||||
| @@ -18,8 +18,7 @@ local function system_sync(cmd, opts) | ||||
|     local res = obj:wait() | ||||
|  | ||||
|     -- Check the process is no longer running | ||||
|     local proc = vim.api.nvim_get_proc(obj.pid) | ||||
|     assert(not proc, 'process still exists') | ||||
|     assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') | ||||
|  | ||||
|     return res | ||||
|   end) | ||||
| @@ -27,23 +26,23 @@ end | ||||
|  | ||||
| local function system_async(cmd, opts) | ||||
|   return exec_lua(function() | ||||
|     _G.done = false | ||||
|     local done = false | ||||
|     local res --- @type vim.SystemCompleted? | ||||
|     local obj = vim.system(cmd, opts, function(obj) | ||||
|       _G.done = true | ||||
|       _G.ret = obj | ||||
|       done = true | ||||
|       res = obj | ||||
|     end) | ||||
|  | ||||
|     local ok = vim.wait(10000, function() | ||||
|       return _G.done | ||||
|       return done | ||||
|     end) | ||||
|  | ||||
|     assert(ok, 'process did not exit') | ||||
|  | ||||
|     -- Check the process is no longer running | ||||
|     local proc = vim.api.nvim_get_proc(obj.pid) | ||||
|     assert(not proc, 'process still exists') | ||||
|     assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') | ||||
|  | ||||
|     return _G.ret | ||||
|     return res | ||||
|   end) | ||||
| end | ||||
|  | ||||
| @@ -120,4 +119,32 @@ describe('vim.system', function() | ||||
|       system_sync({ './test' }) | ||||
|     end) | ||||
|   end | ||||
|  | ||||
|   it('always captures all content of stdout/stderr #30846', function() | ||||
|     t.skip(n.fn.executable('git') == 0, 'missing "git" command') | ||||
|     eq( | ||||
|       0, | ||||
|       exec_lua(function() | ||||
|         local done = 0 | ||||
|         local fail = 0 | ||||
|         for _ = 1, 200 do | ||||
|           vim.system( | ||||
|             { 'git', 'show', ':0:test/functional/plugin/lsp_spec.lua' }, | ||||
|             { text = true }, | ||||
|             function(o) | ||||
|               if o.code ~= 0 or #o.stdout == 0 then | ||||
|                 fail = fail + 1 | ||||
|               end | ||||
|               done = done + 1 | ||||
|             end | ||||
|           ) | ||||
|         end | ||||
|  | ||||
|         local ok = vim.wait(10000, function() | ||||
|           return done == 200 | ||||
|         end, 200) | ||||
|         return fail + (ok and 0 or 1) | ||||
|       end) | ||||
|     ) | ||||
|   end) | ||||
| end) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user