diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ffbe8379..d5bfdb214c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,7 +277,7 @@ add_glob_target( TOUCH_STRATEGY PER_DIR) add_custom_target(lintcommit - COMMAND $ -u NONE -l ${PROJECT_SOURCE_DIR}/scripts/lintcommit.lua main) + COMMAND $ --clean -l ${PROJECT_SOURCE_DIR}/scripts/lintcommit.lua main) add_dependencies(lintcommit nvim_bin) add_custom_target(lint) diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index aa76f55662..a390d74e09 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -3895,7 +3895,7 @@ conforming to the https://semver.org spec. Plugins, and plugin managers, can use this to check available tools and dependencies on the current system. Example: >lua - local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) + local v = vim.version.parse(vim.system({'tmux', '-V'}):wait().stdout, {strict=false}) if vim.version.gt(v, {3, 2, 0}) then -- ... end diff --git a/runtime/example_init.lua b/runtime/example_init.lua index f598ae84bd..45e17f9b6b 100644 --- a/runtime/example_init.lua +++ b/runtime/example_init.lua @@ -76,7 +76,7 @@ vim.api.nvim_create_autocmd('TextYankPost', { vim.api.nvim_create_user_command('GitBlameLine', function() local line_number = vim.fn.line('.') -- Get the current line number. See `:h line()` local filename = vim.api.nvim_buf_get_name(0) - print(vim.fn.system({ 'git', 'blame', '-L', line_number .. ',+1', filename })) + print(vim.system({ 'git', 'blame', '-L', line_number .. ',+1', filename }):wait().stdout) end, { desc = 'Print the git blame for the current line' }) -- [[ Add optional packages ]] diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index a640a6a548..5eaa1911e4 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -1,8 +1,13 @@ local M = {} local health = require('vim.health') -local shell_error = function() - return vim.v.shell_error ~= 0 +---Run a system command and return ok and its stdout and stderr combined. +---@param cmd string[] +---@return boolean +---@return string +local function system(cmd) + local result = vim.system(cmd, { text = true }):wait() + return result.code == 0, vim.trim(('%s\n%s'):format(result.stdout, result.stderr)) end local suggest_faq = 'https://github.com/neovim/neovim/blob/master/BUILD.md#building' @@ -168,10 +173,10 @@ local function check_performance() end -- check for slow shell invocation - local slow_cmd_time = 1.5 - local start_time = vim.fn.reltime() - vim.fn.system('echo') - local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time)) + local slow_cmd_time = 1.5e9 + local start_time = vim.uv.hrtime() + system({ 'echo' }) + local elapsed_time = vim.uv.hrtime() - start_time if elapsed_time > slow_cmd_time then health.warn( 'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).' @@ -252,17 +257,17 @@ local function check_tmux() ---@param option string local get_tmux_option = function(option) - local cmd = 'tmux show-option -qvg ' .. option -- try global scope - local out = vim.fn.system(vim.fn.split(cmd)) + local cmd = { 'tmux', 'show-option', '-qvg', option } -- try global scope + local ok, out = system(cmd) local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') - if shell_error() then + if not ok then health.error('command failed: ' .. cmd .. '\n' .. out) return 'error' elseif val == '' then - cmd = 'tmux show-option -qvgs ' .. option -- try session scope - out = vim.fn.system(vim.fn.split(cmd)) + cmd = { 'tmux', 'show-option', '-qvgs', option } -- try session scope + ok, out = system(cmd) val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') - if shell_error() then + if not ok then health.error('command failed: ' .. cmd .. '\n' .. out) return 'error' end @@ -301,16 +306,16 @@ local function check_tmux() -- check default-terminal and $TERM health.info('$TERM: ' .. vim.env.TERM) - local cmd = 'tmux show-option -qvg default-terminal' - local out = vim.fn.system(vim.fn.split(cmd)) + local cmd = { 'tmux', 'show-option', '-qvg', 'default-terminal' } + local ok, out = system(cmd) local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') if tmux_default_term == '' then - cmd = 'tmux show-option -qvgs default-terminal' - out = vim.fn.system(vim.fn.split(cmd)) + cmd = { 'tmux', 'show-option', '-qvgs', 'default-terminal' } + ok, out = system(cmd) tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g') end - if shell_error() then + if not ok then health.error('command failed: ' .. cmd .. '\n' .. out) elseif tmux_default_term ~= vim.env.TERM then health.info('default-terminal: ' .. tmux_default_term) @@ -329,7 +334,7 @@ local function check_tmux() end -- check for RGB capabilities - local info = vim.fn.system({ 'tmux', 'show-messages', '-T' }) + local _, info = system({ 'tmux', 'show-messages', '-T' }) local has_setrgbb = vim.fn.stridx(info, ' setrgbb: (string)') ~= -1 local has_setrgbf = vim.fn.stridx(info, ' setrgbf: (string)') ~= -1 if not has_setrgbb or not has_setrgbf then @@ -349,13 +354,13 @@ local function check_terminal() end health.start('terminal') - local cmd = 'infocmp -L' - local out = vim.fn.system(vim.fn.split(cmd)) + local cmd = { 'infocmp', '-L' } + local ok, out = system(cmd) local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*') local kdch1_entry = vim.fn.matchstr(out, 'key_dc=[^,[:space:]]*') if - shell_error() + not ok and ( vim.fn.has('win32') == 0 or vim.fn.matchstr( diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index e4b5206fa4..3300b9121e 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -4,8 +4,13 @@ local iswin = vim.fn.has('win32') == 1 local M = {} local function cmd_ok(cmd) - local out = vim.fn.system(cmd) - return vim.v.shell_error == 0, out + local result = vim.system(cmd, { text = true }):wait() + return result.code == 0, result.stdout +end + +local function cli_version(cmd) + local ok, out = cmd_ok(cmd) + return ok, vim.version.parse(out, { strict = false }) end -- Attempts to construct a shell command from an args list. @@ -127,14 +132,14 @@ local function clipboard() os.getenv('TMUX') and vim.fn.executable('tmux') == 1 and vim.fn.executable('pbpaste') == 1 - and not cmd_ok('pbpaste') + and not cmd_ok({ 'pbpaste' }) then - local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') + local _, tmux_version = cli_version({ 'tmux', '-V' }) local advice = { 'Install tmux 2.6+. https://superuser.com/q/231130', 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233', } - health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) + health.error('pbcopy does not work with tmux version: ' .. tostring(tmux_version), advice) end local clipboard_tool = vim.fn['provider#clipboard#Executable']() ---@type string @@ -178,9 +183,8 @@ local function node() return end - -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' - local ok, node_v = cmd_ok({ 'node', '-v' }) - health.info('Node.js: ' .. node_v) + local ok, node_v = cli_version({ 'node', '-v' }) + health.info('Node.js: ' .. tostring(node_v)) if not ok or vim.version.lt(node_v, '6.0.0') then health.warn('Nvim node.js host does not support Node ' .. node_v) -- Skip further checks, they are nonsense if nodejs is too old. @@ -213,13 +217,14 @@ local function node() end local latest_npm_cmd = ( - iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json' + iswin and { 'cmd', '/c', manager, 'info', 'neovim', '--json' } + or { manager, 'info', 'neovim', '--json' } ) local latest_npm - ok, latest_npm = cmd_ok(vim.split(latest_npm_cmd, ' ')) + ok, latest_npm = cmd_ok(latest_npm_cmd) if not ok or latest_npm:find('^%s$') then health.error( - 'Failed to run: ' .. latest_npm_cmd, + 'Failed to run: ' .. shellify(latest_npm_cmd), { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } ) return @@ -237,8 +242,8 @@ local function node() ok, current_npm = cmd_ok(current_npm_cmd) if not ok then health.error( - 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') } + 'Failed to run: ' .. shellify(current_npm_cmd), + { 'Report this issue with the output of: ', shellify(current_npm_cmd) } ) return end @@ -298,7 +303,7 @@ local function perl() ok, latest_cpan = cmd_ok(latest_cpan_cmd) if not ok or latest_cpan:find('^%s*$') then health.error( - 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), + 'Failed to run: ' .. shellify(latest_cpan_cmd), { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } ) return @@ -329,8 +334,8 @@ local function perl() ok, current_cpan = cmd_ok(current_cpan_cmd) if not ok then health.error( - 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') } + 'Failed to run: ' .. shellify(current_cpan_cmd), + { 'Report this issue with the output of: ', shellify(current_cpan_cmd) } ) return end @@ -426,12 +431,15 @@ end --- @param url string local function download(url) local has_curl = vim.fn.executable('curl') == 1 - if has_curl and vim.fn.system({ 'curl', '-V' }):find('Protocols:.*https') then - local out, rc = system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) - if rc ~= 0 then - return 'curl error with ' .. url .. ': ' .. rc - else - return out + if has_curl then + local ok, out = cmd_ok({ 'curl', '-V' }) + if ok and out:find('Protocols:.*https') then + local content, rc = system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) + if rc ~= 0 then + return 'curl error with ' .. url .. ': ' .. rc + else + return content + end end elseif vim.fn.executable('python') == 1 then local script = ([[ @@ -872,7 +880,8 @@ local function ruby() ) return end - health.info('Ruby: ' .. system({ 'ruby', '-v' })) + local _, ruby_v = cli_version({ 'ruby', '-v' }) + health.info('Ruby: ' .. tostring(ruby_v)) local host, _ = vim.provider.ruby.detect() if (not host) or host:find('^%s*$') then @@ -887,11 +896,14 @@ local function ruby() end health.info('Host: ' .. host) - local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$') - local ok, latest_gem = cmd_ok(vim.split(latest_gem_cmd, ' ')) + local latest_gem_cmd = ( + iswin and { 'cmd', '/c', 'gem', 'list', '-ra', '"^^neovim$"' } + or { 'gem', 'list', '-ra', '^neovim$' } + ) + local ok, latest_gem = cmd_ok(latest_gem_cmd) if not ok or latest_gem:find('^%s*$') then health.error( - 'Failed to run: ' .. latest_gem_cmd, + 'Failed to run: ' .. shellify(latest_gem_cmd), { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } ) return @@ -904,8 +916,8 @@ local function ruby() ok, current_gem = cmd_ok(current_gem_cmd) if not ok then health.error( - 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') } + 'Failed to run: ' .. shellify(current_gem_cmd), + { 'Report this issue with the output of: ', shellify(current_gem_cmd) } ) return end diff --git a/runtime/lua/vim/provider/perl.lua b/runtime/lua/vim/provider/perl.lua index da4af0a2a7..5118f11b0b 100644 --- a/runtime/lua/vim/provider/perl.lua +++ b/runtime/lua/vim/provider/perl.lua @@ -25,14 +25,12 @@ function M.detect() end -- if perl is available, make sure we have 5.22+ - vim.fn.system({ prog, '-e', 'use v5.22' }) - if vim.v.shell_error ~= 0 then + if vim.system({ prog, '-e', 'use v5.22' }):wait().code ~= 0 then return nil, 'Perl version is too old, 5.22+ required' end -- if perl is available, make sure the required module is available - vim.fn.system({ prog, '-W', '-MNeovim::Ext', '-e', '' }) - if vim.v.shell_error ~= 0 then + if vim.system({ prog, '-W', '-MNeovim::Ext', '-e', '' }):wait().code ~= 0 then return nil, '"Neovim::Ext" cpan module is not installed' end return prog, nil diff --git a/runtime/lua/vim/provider/ruby.lua b/runtime/lua/vim/provider/ruby.lua index 3ad86001f3..299db64b44 100644 --- a/runtime/lua/vim/provider/ruby.lua +++ b/runtime/lua/vim/provider/ruby.lua @@ -45,8 +45,8 @@ function M.detect() prog = '' else -- neovim-ruby-host could be an rbenv shim for another Ruby version. - vim.fn.system(p) - prog = vim.v.shell_error ~= 0 and '' or p + local result = vim.system({ p }):wait() + prog = result.code ~= 0 and '' or p end end local err = prog == '' and 'missing ruby or ruby-host' or '' diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index cb09183f8f..a904255490 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -6,7 +6,7 @@ --- Example: --- --- ```lua ---- local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false}) +--- local v = vim.version.parse(vim.system({'tmux', '-V'}):wait().stdout, {strict=false}) --- if vim.version.gt(v, {3, 2, 0}) then --- -- ... --- end diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 7cb57de901..b8f0a09026 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -27,8 +27,9 @@ local function run(cmd, or_die) if _trace then p('run: ' .. vim.inspect(cmd)) end - local rv = vim.trim(vim.fn.system(cmd)) or '' - if vim.v.shell_error ~= 0 then + local res = vim.system(cmd):wait() + local rv = vim.trim(res.stdout) + if res.code ~= 0 then if or_die then p(rv) os.exit(1)