From 06347a64cac5e33574713a59ace9d1d0ea4b6f82 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 May 2024 05:19:46 +0800 Subject: vim-patch:9.1.0443: Can't use blockwise selection with width for getregion() (#28985) Problem: Can't use a blockwise selection with a width for getregion(). Solution: Add support for blockwise selection with width like the return value of getregtype() or the "regtype" value of TextYankPost (zeertzjq). closes: vim/vim#14842 https://github.com/vim/vim/commit/afc2295c2201ae87bfbb42d5f5315ad0583ccabf --- runtime/lua/vim/_meta/vimfn.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index f4daacfb7d..d1455fa993 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3536,14 +3536,13 @@ function vim.fn.getreginfo(regname) end --- The optional argument {opts} is a Dict and supports the --- following items: --- ---- type Specify the region's selection type ---- (default: "v"): ---- "v" for |charwise| mode ---- "V" for |linewise| mode ---- "" for |blockwise-visual| mode +--- type Specify the region's selection type. +--- See |getregtype()| for possible values, +--- except it cannot be an empty string. +--- (default: "v") --- --- exclusive If |TRUE|, use exclusive selection ---- for the end position +--- for the end position. --- (default: follow 'selection') --- --- You can get the last selection type by |visualmode()|. -- cgit From 5d26934c7cda191f0b519c1326fa318b857ffcb8 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 24 May 2024 18:31:25 -0500 Subject: feat(lsp): update LSP healthcheck format (#28980) This is mostly an aesthetic change, although there are a few new pieces of information included. Originally I wanted to investigate including server capabilities in the healthcheck, but until we have the ability to fold/unfold text in health checks that would be too much information. --- runtime/lua/vim/lsp/health.lua | 83 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 10 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index a79ae76eb9..b5dc710cc6 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -33,16 +33,22 @@ local function check_active_clients() local clients = vim.lsp.get_clients() if next(clients) then for _, client in pairs(clients) do - local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',') - report_info( + local cmd ---@type string + if type(client.config.cmd) == 'table' then + cmd = table.concat(client.config.cmd --[[@as table]], ' ') + elseif type(client.config.cmd) == 'function' then + cmd = tostring(client.config.cmd) + end + report_info(table.concat({ + string.format('%s (id: %d)', client.name, client.id), + string.format(' Root directory: %s', vim.fn.fnamemodify(client.root_dir, ':~')), + string.format(' Command: %s', cmd), + string.format(' Settings: %s', vim.inspect(client.settings, { newline = '\n ' })), string.format( - '%s (id=%s, root_dir=%s, attached_to=[%s])', - client.name, - client.id, - vim.fn.fnamemodify(client.root_dir, ':~'), - attached_to - ) - ) + ' Attached buffers: %s', + vim.iter(pairs(client.attached_buffers)):map(tostring):join(', ') + ), + }, '\n')) end else report_info('No active clients') @@ -50,7 +56,7 @@ local function check_active_clients() end local function check_watcher() - vim.health.start('vim.lsp: File watcher') + vim.health.start('vim.lsp: File Watcher') -- Only run the check if file watching has been enabled by a client. local clients = vim.lsp.get_clients() @@ -94,11 +100,68 @@ local function check_watcher() end end +local function check_position_encodings() + vim.health.start('vim.lsp: Position Encodings') + local clients = vim.lsp.get_clients() + if next(clients) then + local position_encodings = {} ---@type table> + for _, client in pairs(clients) do + for bufnr in pairs(client.attached_buffers) do + if not position_encodings[bufnr] then + position_encodings[bufnr] = {} + end + if not position_encodings[bufnr][client.offset_encoding] then + position_encodings[bufnr][client.offset_encoding] = {} + end + table.insert(position_encodings[bufnr][client.offset_encoding], client.id) + end + end + + -- Check if any buffers are attached to multiple clients with different position encodings + local buffers = {} ---@type integer[] + for bufnr, encodings in pairs(position_encodings) do + local list = {} ---@type string[] + for k in pairs(encodings) do + list[#list + 1] = k + end + + if #list > 1 then + buffers[#buffers + 1] = bufnr + end + end + + if #buffers > 0 then + local lines = + { 'Found buffers attached to multiple clients with different position encodings.' } + for _, bufnr in ipairs(buffers) do + local encodings = position_encodings[bufnr] + local parts = {} + for encoding, client_ids in pairs(encodings) do + table.insert( + parts, + string.format('%s (client id(s): %s)', encoding:upper(), table.concat(client_ids, ', ')) + ) + end + table.insert(lines, string.format('- Buffer %d: %s', bufnr, table.concat(parts, ', '))) + end + report_warn( + table.concat(lines, '\n'), + 'Use the positionEncodings client capability to ensure all clients use the same position encoding' + ) + else + report_info('No buffers contain mixed position encodings') + end + else + report_info('No active clients') + end +end + --- Performs a healthcheck for LSP function M.check() check_log() check_active_clients() check_watcher() + check_position_encodings() end return M -- cgit From 8b2b1fba2abfb99186e3a1f0123251a3e2eae3fe Mon Sep 17 00:00:00 2001 From: glepnir Date: Fri, 3 May 2024 15:53:13 +0800 Subject: fix(float): missing default highlight for title Problem: there is missing default title highlight when highlight not defined in title text chunk. Solution: when attr is not set use default title highlight group. --- runtime/lua/vim/_meta/api.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 64c67be076..ae43863316 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1731,13 +1731,15 @@ function vim.api.nvim_open_term(buffer, opts) end --- --- • title: Title (optional) in window border, string or list. --- List should consist of `[text, highlight]` tuples. If ---- string, the default highlight group is `FloatTitle`. +--- string, or a tuple lacks a highlight, the default +--- highlight group is `FloatTitle`. --- • title_pos: Title position. Must be set with `title` --- option. Value can be one of "left", "center", or "right". --- Default is `"left"`. --- • footer: Footer (optional) in window border, string or --- list. List should consist of `[text, highlight]` tuples. ---- If string, the default highlight group is `FloatFooter`. +--- If string, or a tuple lacks a highlight, the default +--- highlight group is `FloatFooter`. --- • footer_pos: Footer position. Must be set with `footer` --- option. Value can be one of "left", "center", or "right". --- Default is `"left"`. -- cgit From 4757d497f3c85cc343f7dcbc09f95e43ba5c1314 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 May 2024 16:53:10 +0800 Subject: vim-patch:9.1.0444: Not enough tests for getregion() with multibyte chars (#29000) Problem: Not enough tests for getregion() with multibyte chars. Solution: Add a few more tests (zeertzjq). closes: vim/vim#14844 https://github.com/vim/vim/commit/dff55a335889c746a79974f7c52cdcdebad682c2 --- runtime/lua/vim/_meta/vimfn.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index d1455fa993..f256f63768 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3538,7 +3538,8 @@ function vim.fn.getreginfo(regname) end --- --- type Specify the region's selection type. --- See |getregtype()| for possible values, ---- except it cannot be an empty string. +--- except that the width can be omitted +--- and an empty string cannot be used. --- (default: "v") --- --- exclusive If |TRUE|, use exclusive selection -- cgit From bdb81afab3e5c43a33267666c2689feb284f6b52 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 25 May 2024 20:37:33 +0800 Subject: refactor(lua): rewrite vim.highlight.range() (#28986) - Use getregionpos(). - Use a single extmark for non-blockwise selection. --- runtime/lua/vim/highlight.lua | 55 +++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 13 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index f278bd357f..89298ce568 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -20,8 +20,8 @@ M.priorities = { --- @class vim.highlight.range.Opts --- @inlinedoc --- ---- Type of range. See [setreg()] ---- (default: `'charwise'`) +--- Type of range. See [getregtype()] +--- (default: `'v'` i.e. charwise) --- @field regtype? string --- --- Indicates whether the range is end-inclusive @@ -49,20 +49,49 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local priority = opts.priority or M.priorities.user local scoped = opts._scoped or false - -- TODO: in case of 'v', 'V' (not block), this should calculate equivalent - -- bounds (row, col, end_row, end_col) as multiline regions are natively - -- supported now - local region = vim.region(bufnr, start, finish, regtype, inclusive) - for linenr, cols in pairs(region) do - local end_row - if cols[2] == -1 then - end_row = linenr + 1 - cols[2] = 0 + local pos1 = type(start) == 'string' and vim.fn.getpos(start) + or { bufnr, start[1] + 1, start[2] + 1, 0 } + local pos2 = type(finish) == 'string' and vim.fn.getpos(finish) + or { bufnr, finish[1] + 1, finish[2] + 1, 0 } + + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) + pos1[2] = math.min(pos1[2], buf_line_count) + pos2[2] = math.min(pos2[2], buf_line_count) + + if pos1[2] <= 0 or pos1[3] <= 0 or pos2[2] <= 0 or pos2[3] <= 0 then + return + end + + vim.api.nvim_buf_call(bufnr, function() + local max_col1 = vim.fn.col({ pos1[2], '$' }) + pos1[3] = math.min(pos1[3], max_col1) + local max_col2 = vim.fn.col({ pos2[2], '$' }) + pos2[3] = math.min(pos2[3], max_col2) + end) + + local region = vim.fn.getregionpos(pos1, pos2, { + type = regtype, + exclusive = not inclusive, + eol = true, + }) + -- For non-blockwise selection, use a single extmark. + if regtype == 'v' or regtype == 'V' then + region = { { region[1][1], region[#region][2] } } + end + + for _, res in ipairs(region) do + local start_row = res[1][2] - 1 + local start_col = res[1][3] - 1 + local end_row = res[2][2] - 1 + local end_col = res[2][3] + if regtype == 'V' then + end_row = end_row + 1 + end_col = 0 end - api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], { + api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, { hl_group = higroup, end_row = end_row, - end_col = cols[2], + end_col = end_col, priority = priority, strict = false, scoped = scoped, -- cgit From 520c2657bb6832dc527bff94f313a1cc458238a4 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 25 May 2024 17:56:05 +0200 Subject: refactor: move provider-related to where they are used --- runtime/lua/vim/health.lua | 108 -------------------------- runtime/lua/vim/provider/health.lua | 150 ++++++++++++++++++++++++++++++------ 2 files changed, 127 insertions(+), 131 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index f40f04a064..afeba2ee9d 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -275,114 +275,6 @@ function M.error(msg, ...) collect_output(input) end -function M._provider_disabled(provider) - local loaded_var = 'loaded_' .. provider .. '_provider' - local v = vim.g[loaded_var] - if v == 0 then - M.info('Disabled (' .. loaded_var .. '=' .. v .. ').') - return true - end - return false -end - --- Handler for s:system() function. -local function system_handler(self, _, data, event) - if event == 'stderr' then - if self.add_stderr_to_output then - self.output = self.output .. table.concat(data, '') - else - self.stderr = self.stderr .. table.concat(data, '') - end - elseif event == 'stdout' then - self.output = self.output .. table.concat(data, '') - end -end - --- Attempts to construct a shell command from an args list. --- Only for display, to help users debug a failed command. -local function shellify(cmd) - if type(cmd) ~= 'table' then - return cmd - end - local escaped = {} - for i, v in ipairs(cmd) do - if v:match('[^A-Za-z_/.-]') then - escaped[i] = vim.fn.shellescape(v) - else - escaped[i] = v - end - end - return table.concat(escaped, ' ') -end - -function M._cmd_ok(cmd) - local out = vim.fn.system(cmd) - return vim.v.shell_error == 0, out -end - ---- Run a system command and timeout after 30 seconds. ---- ---- @param cmd table List of command arguments to execute ---- @param args? table Optional arguments: ---- - stdin (string): Data to write to the job's stdin ---- - stderr (boolean): Append stderr to stdout ---- - ignore_error (boolean): If true, ignore error output ---- - timeout (number): Number of seconds to wait before timing out (default 30) -function M._system(cmd, args) - args = args or {} - local stdin = args.stdin or '' - local stderr = vim.F.if_nil(args.stderr, false) - local ignore_error = vim.F.if_nil(args.ignore_error, false) - - local shell_error_code = 0 - local opts = { - add_stderr_to_output = stderr, - output = '', - stderr = '', - on_stdout = system_handler, - on_stderr = system_handler, - on_exit = function(_, data) - shell_error_code = data - end, - } - local jobid = vim.fn.jobstart(cmd, opts) - - if jobid < 1 then - local message = - string.format('Command error (job=%d): %s (in %s)', jobid, shellify(cmd), vim.uv.cwd()) - error(message) - return opts.output, 1 - end - - if stdin:find('^%s$') then - vim.fn.chansend(jobid, stdin) - end - - local res = vim.fn.jobwait({ jobid }, vim.F.if_nil(args.timeout, 30) * 1000) - if res[1] == -1 then - error('Command timed out: ' .. shellify(cmd)) - vim.fn.jobstop(jobid) - elseif shell_error_code ~= 0 and not ignore_error then - local emsg = string.format( - 'Command error (job=%d, exit code %d): %s (in %s)', - jobid, - shell_error_code, - shellify(cmd), - vim.uv.cwd() - ) - if opts.output:find('%S') then - emsg = string.format('%s\noutput: %s', emsg, opts.output) - end - if opts.stderr:find('%S') then - emsg = string.format('%s\nstderr: %s', emsg, opts.stderr) - end - error(emsg) - end - - -- return opts.output - return vim.trim(vim.fn.system(cmd)), shell_error_code -end - local path2name = function(path) if path:match('%.lua$') then -- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp" diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index 63e0da448a..d6932f651e 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -3,6 +3,112 @@ local iswin = vim.uv.os_uname().sysname == 'Windows_NT' local M = {} +local function cmd_ok(cmd) + local out = vim.fn.system(cmd) + return vim.v.shell_error == 0, out +end + +-- Attempts to construct a shell command from an args list. +-- Only for display, to help users debug a failed command. +local function shellify(cmd) + if type(cmd) ~= 'table' then + return cmd + end + local escaped = {} + for i, v in ipairs(cmd) do + if v:match('[^A-Za-z_/.-]') then + escaped[i] = vim.fn.shellescape(v) + else + escaped[i] = v + end + end + return table.concat(escaped, ' ') +end + +-- Handler for s:system() function. +local function system_handler(self, _, data, event) + if event == 'stderr' then + if self.add_stderr_to_output then + self.output = self.output .. table.concat(data, '') + else + self.stderr = self.stderr .. table.concat(data, '') + end + elseif event == 'stdout' then + self.output = self.output .. table.concat(data, '') + end +end + +--- @param cmd table List of command arguments to execute +--- @param args? table Optional arguments: +--- - stdin (string): Data to write to the job's stdin +--- - stderr (boolean): Append stderr to stdout +--- - ignore_error (boolean): If true, ignore error output +--- - timeout (number): Number of seconds to wait before timing out (default 30) +local function system(cmd, args) + args = args or {} + local stdin = args.stdin or '' + local stderr = vim.F.if_nil(args.stderr, false) + local ignore_error = vim.F.if_nil(args.ignore_error, false) + + local shell_error_code = 0 + local opts = { + add_stderr_to_output = stderr, + output = '', + stderr = '', + on_stdout = system_handler, + on_stderr = system_handler, + on_exit = function(_, data) + shell_error_code = data + end, + } + local jobid = vim.fn.jobstart(cmd, opts) + + if jobid < 1 then + local message = + string.format('Command error (job=%d): %s (in %s)', jobid, shellify(cmd), vim.uv.cwd()) + error(message) + return opts.output, 1 + end + + if stdin:find('^%s$') then + vim.fn.chansend(jobid, stdin) + end + + local res = vim.fn.jobwait({ jobid }, vim.F.if_nil(args.timeout, 30) * 1000) + if res[1] == -1 then + error('Command timed out: ' .. shellify(cmd)) + vim.fn.jobstop(jobid) + elseif shell_error_code ~= 0 and not ignore_error then + local emsg = string.format( + 'Command error (job=%d, exit code %d): %s (in %s)', + jobid, + shell_error_code, + shellify(cmd), + vim.uv.cwd() + ) + if opts.output:find('%S') then + emsg = string.format('%s\noutput: %s', emsg, opts.output) + end + if opts.stderr:find('%S') then + emsg = string.format('%s\nstderr: %s', emsg, opts.stderr) + end + error(emsg) + end + + -- return opts.output + return vim.trim(vim.fn.system(cmd)), shell_error_code +end + +local function provider_disabled(provider) + local loaded_var = 'loaded_' .. provider .. '_provider' + local v = vim.g[loaded_var] + if v == 0 then + health.info('Disabled (' .. loaded_var .. '=' .. v .. ').') + return true + end + return false +end + local function clipboard() health.start('Clipboard (optional)') @@ -10,7 +116,7 @@ local function clipboard() os.getenv('TMUX') and vim.fn.executable('tmux') == 1 and vim.fn.executable('pbpaste') == 1 - and not health._cmd_ok('pbpaste') + and not cmd_ok('pbpaste') then local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') local advice = { @@ -40,7 +146,7 @@ end local function node() health.start('Node.js provider (optional)') - if health._provider_disabled('node') then + if provider_disabled('node') then return end @@ -60,7 +166,7 @@ local function node() end -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' - local ok, node_v = health._cmd_ok({ 'node', '-v' }) + local ok, node_v = cmd_ok({ 'node', '-v' }) health.info('Node.js: ' .. 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) @@ -97,7 +203,7 @@ local function node() iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json' ) local latest_npm - ok, latest_npm = health._cmd_ok(vim.split(latest_npm_cmd, ' ')) + ok, latest_npm = cmd_ok(vim.split(latest_npm_cmd, ' ')) if not ok or latest_npm:find('^%s$') then health.error( 'Failed to run: ' .. latest_npm_cmd, @@ -115,7 +221,7 @@ local function node() local current_npm_cmd = { 'node', host, '--version' } local current_npm - ok, current_npm = health._cmd_ok(current_npm_cmd) + ok, current_npm = cmd_ok(current_npm_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), @@ -143,7 +249,7 @@ end local function perl() health.start('Perl provider (optional)') - if health._provider_disabled('perl') then + if provider_disabled('perl') then return end @@ -162,7 +268,7 @@ local function perl() -- we cannot use cpanm that is on the path, as it may not be for the perl -- set with g:perl_host_prog - local ok = health._cmd_ok({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) + local ok = cmd_ok({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) if not ok then return { perl_exec, '"App::cpanminus" module is not installed' } end @@ -174,7 +280,7 @@ local function perl() 'my $app = App::cpanminus::script->new; $app->parse_options ("--info", "-q", "Neovim::Ext"); exit $app->doit', } local latest_cpan - ok, latest_cpan = health._cmd_ok(latest_cpan_cmd) + 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, ' '), @@ -205,7 +311,7 @@ local function perl() local current_cpan_cmd = { perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION' } local current_cpan - ok, current_cpan = health._cmd_ok(current_cpan_cmd) + ok, current_cpan = cmd_ok(current_cpan_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), @@ -292,7 +398,7 @@ end 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 = health._system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) + local out, rc = system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) if rc ~= 0 then return 'curl error with ' .. url .. ': ' .. rc else @@ -305,7 +411,7 @@ local function download(url) from urllib2 import urlopen\n\ response = urlopen('" .. url .. "')\n\ print(response.read().decode('utf8'))\n" - local out, rc = health._system({ 'python', '-c', script }) + local out, rc = system({ 'python', '-c', script }) if out == '' and rc ~= 0 then return 'python urllib.request error: ' .. rc else @@ -362,7 +468,7 @@ end local function version_info(python) local pypi_version = latest_pypi_version() - local python_version, rc = health._system({ + local python_version, rc = system({ python, '-c', 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', @@ -373,7 +479,7 @@ local function version_info(python) end local nvim_path - nvim_path, rc = health._system({ + nvim_path, rc = system({ python, '-c', 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', @@ -398,7 +504,7 @@ local function version_info(python) -- Try to get neovim.VERSION (added in 0.1.11dev). local nvim_version - nvim_version, rc = health._system({ + nvim_version, rc = system({ python, '-c', 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', @@ -445,7 +551,7 @@ local function python() local host_prog_var = pyname .. '_host_prog' local python_multiple = {} - if health._provider_disabled(pyname) then + if provider_disabled(pyname) then return end @@ -487,7 +593,7 @@ local function python() end if pyenv ~= '' then - python_exe = health._system({ pyenv, 'which', pyname }, { stderr = true }) + python_exe = system({ pyenv, 'which', pyname }, { stderr = true }) if python_exe == '' then health.warn('pyenv could not find ' .. pyname .. '.') end @@ -710,9 +816,7 @@ local function python() health.info(msg) health.info( 'Python version: ' - .. health._system( - 'python -c "import platform, sys; sys.stdout.write(platform.python_version())"' - ) + .. system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"') ) health.ok('$VIRTUAL_ENV provides :!python.') end @@ -721,7 +825,7 @@ end local function ruby() health.start('Ruby provider (optional)') - if health._provider_disabled('ruby') then + if provider_disabled('ruby') then return end @@ -732,7 +836,7 @@ local function ruby() ) return end - health.info('Ruby: ' .. health._system({ 'ruby', '-v' })) + health.info('Ruby: ' .. system({ 'ruby', '-v' })) local host, _ = vim.provider.ruby.detect() if (not host) or host:find('^%s*$') then @@ -748,7 +852,7 @@ local function ruby() 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 = health._cmd_ok(vim.split(latest_gem_cmd, ' ')) + local ok, latest_gem = cmd_ok(vim.split(latest_gem_cmd, ' ')) if not ok or latest_gem:find('^%s*$') then health.error( 'Failed to run: ' .. latest_gem_cmd, @@ -761,7 +865,7 @@ local function ruby() local current_gem_cmd = { host, '--version' } local current_gem - ok, current_gem = health._cmd_ok(current_gem_cmd) + ok, current_gem = cmd_ok(current_gem_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), -- cgit From f03b1622ad1b8e2df16504631f05e7577e217854 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 25 May 2024 21:22:41 +0200 Subject: fix(lsp): handle nil root_dir in health check (#29007) The root directory could show up as something like: Root directory: ~/path/to/cwd/v:null Despite being `nil` --- runtime/lua/vim/lsp/client.lua | 3 +-- runtime/lua/vim/lsp/health.lua | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 4beb7fefda..58ea7d02b3 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -182,7 +182,7 @@ local validate = vim.validate --- It can be `null` if the client supports workspace folders but none are --- configured. --- @field workspace_folders lsp.WorkspaceFolder[]? ---- @field root_dir string +--- @field root_dir string? --- --- @field attached_buffers table --- @@ -470,7 +470,6 @@ function Client.create(config) _on_exit_cbs = ensure_list(config.on_exit), _on_attach_cbs = ensure_list(config.on_attach), _on_error_cb = config.on_error, - _root_dir = config.root_dir, _trace = get_trace(config.trace), --- Contains $/progress report messages. diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index b5dc710cc6..ffe595ab37 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -41,7 +41,10 @@ local function check_active_clients() end report_info(table.concat({ string.format('%s (id: %d)', client.name, client.id), - string.format(' Root directory: %s', vim.fn.fnamemodify(client.root_dir, ':~')), + string.format( + ' Root directory: %s', + client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~') or nil + ), string.format(' Command: %s', cmd), string.format(' Settings: %s', vim.inspect(client.settings, { newline = '\n ' })), string.format( -- cgit From 7994fdba6a1e10aad0698dfabcbaf544b4d35eb4 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 25 May 2024 21:23:04 +0200 Subject: fix(snippet): don't override unnamed register on tabstop select (#28998) --- runtime/lua/vim/snippet.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 3d8f73f362..3fd2555046 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -315,7 +315,7 @@ local function select_tabstop(tabstop) move_cursor_to(range[1] + 1, range[2] + 1) feedkeys('v') move_cursor_to(range[3] + 1, range[4]) - feedkeys('o') + feedkeys('o_') end end -- cgit From 3d39ea3ea9b6e66640e59731d155d731218e7e62 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 26 May 2024 07:11:50 +0800 Subject: vim-patch:9.1.0442: hare runtime files outdated (#29011) Problem: hare runtime files outdated Solution: runtime(hare): update hare.vim to match upstream (Amelia Clarke) closes: vim/vim#14836 https://github.com/vim/vim/commit/35dfe58a540e2fb0eff953630f8e4fcbf4bc26ca Co-authored-by: Amelia Clarke --- runtime/lua/vim/filetype.lua | 2 +- runtime/lua/vim/filetype/detect.lua | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d1fdd0aa16..2ab6cc6059 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1637,7 +1637,7 @@ local filename = { ['.xsdbcmdhistory'] = 'tcl', ['texmf.cnf'] = 'texmf', COPYING = 'text', - README = 'text', + README = detect_seq(detect.haredoc, 'text'), LICENSE = 'text', AUTHORS = 'text', tfrc = 'tf', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index ba86d8de5a..58d2666564 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -650,6 +650,30 @@ function M.header(_, bufnr) end end +--- Recursively search for Hare source files in a directory and any +--- subdirectories, up to a given depth. +--- @param dir string +--- @param depth number +--- @return boolean +local function is_hare_module(dir, depth) + depth = math.max(depth, 0) + for name, _ in vim.fs.dir(dir, { depth = depth + 1 }) do + if name:find('%.ha$') then + return true + end + end + return false +end + +--- @type vim.filetype.mapfn +function M.haredoc(path, _) + if vim.g.filetype_haredoc then + if is_hare_module(vim.fs.dirname(path), vim.g.haredoc_search_depth or 1) then + return 'haredoc' + end + end +end + --- @type vim.filetype.mapfn function M.html(_, bufnr) for _, line in ipairs(getlines(bufnr, 1, 10)) do -- cgit From 2ed6423c7e9d4911343d3e2049908f4b78ec7a55 Mon Sep 17 00:00:00 2001 From: altermo <107814000+altermo@users.noreply.github.com> Date: Sun, 26 May 2024 12:56:32 +0200 Subject: fix(tohtml): replace ipairs with pairs --- runtime/lua/tohtml.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 505de720ba..5a6fc77e5c 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -521,7 +521,7 @@ local function _styletable_extmarks_virt_text(state, extmark) hl_mode = 'blend', hl_group = 'combine', } - for opt, val in ipairs(not_supported) do + for opt, val in pairs(not_supported) do if extmark[4][opt] == val then vim.notify_once( ('Info(TOhtml): extmark.%s="%s" is not supported, HTML may be incorrect'):format(opt, val) -- cgit From 8cd9feb50166202bf55315934f14f74e63c8fcb4 Mon Sep 17 00:00:00 2001 From: altermo <107814000+altermo@users.noreply.github.com> Date: Sun, 26 May 2024 13:08:57 +0200 Subject: fix(tohtml): ignore lsp inlay hints --- runtime/lua/tohtml.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 5a6fc77e5c..f7d8538b8f 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -481,10 +481,17 @@ end --- @param state vim.tohtml.state --- @param extmark {[1]:integer,[2]:integer,[3]:integer,[4]:vim.api.keyset.set_extmark|any} -local function _styletable_extmarks_virt_text(state, extmark) +--- @param namespaces table +local function _styletable_extmarks_virt_text(state, extmark, namespaces) if not extmark[4].virt_text then return end + ---TODO(altermo) LSP semantic tokens (and some other extmarks) are only + ---generated in visible lines, and not in the whole buffer. + if (namespaces[extmark[4].ns_id] or ''):find('vim_lsp_inlayhint') then + vim.notify_once('Info(TOhtml): lsp inlay hints are not supported, HTML may be incorrect') + return + end local styletable = state.style --- @type integer,integer local row, col = extmark[2], extmark[3] @@ -586,7 +593,7 @@ local function styletable_extmarks(state) _styletable_extmarks_conceal(state, v) end for _, v in ipairs(extmarks) do - _styletable_extmarks_virt_text(state, v) + _styletable_extmarks_virt_text(state, v, namespaces) end for _, v in ipairs(extmarks) do _styletable_extmarks_virt_lines(state, v) -- cgit From 88c7997503e12088e134ba663fe352399f8fa104 Mon Sep 17 00:00:00 2001 From: altermo <107814000+altermo@users.noreply.github.com> Date: Sun, 26 May 2024 13:28:14 +0200 Subject: fix(tohtml): show how many warnings are hidden --- runtime/lua/tohtml.lua | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index f7d8538b8f..0fc349e86d 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -57,6 +57,26 @@ --- @field [3] any[][] virt_text --- @field [4] any[][] overlay_text +--- @type string[] +local notifications = {} + +---@param msg string +local function notify(msg) + if #notifications == 0 then + vim.schedule(function() + if #notifications > 1 then + vim.notify( + ('TOhtml: %s (+ %d more warnings)'):format(notifications[1], tostring(#notifications - 1)) + ) + elseif #notifications == 1 then + vim.notify('TOhtml: ' .. notifications[1]) + end + notifications = {} + end) + end + table.insert(notifications, msg) +end + local HIDE_ID = -1 -- stylua: ignore start local cterm_8_to_hex={ @@ -215,7 +235,7 @@ local function cterm_to_hex(colorstr) if hex then cterm_color_cache[color] = hex else - vim.notify_once("Info(TOhtml): Couldn't get terminal colors, using fallback") + notify("Couldn't get terminal colors, using fallback") local t_Co = tonumber(vim.api.nvim_eval('&t_Co')) if t_Co <= 8 then cterm_color_cache = cterm_8_to_hex @@ -241,7 +261,7 @@ local function get_background_color() end local hex = try_query_terminal_color('background') if not hex or not hex:match('#%x%x%x%x%x%x') then - vim.notify_once("Info(TOhtml): Couldn't get terminal background colors, using fallback") + notify("Couldn't get terminal background colors, using fallback") hex = vim.o.background == 'light' and '#ffffff' or '#000000' end background_color_cache = hex @@ -259,7 +279,7 @@ local function get_foreground_color() end local hex = try_query_terminal_color('foreground') if not hex or not hex:match('#%x%x%x%x%x%x') then - vim.notify_once("Info(TOhtml): Couldn't get terminal foreground colors, using fallback") + notify("Couldn't get terminal foreground colors, using fallback") hex = vim.o.background == 'light' and '#000000' or '#ffffff' end foreground_color_cache = hex @@ -467,7 +487,7 @@ local function _styletable_extmarks_highlight(state, extmark, namespaces) ---TODO(altermo) LSP semantic tokens (and some other extmarks) are only ---generated in visible lines, and not in the whole buffer. if (namespaces[extmark[4].ns_id] or ''):find('vim_lsp_semantic_tokens') then - vim.notify_once('Info(TOhtml): lsp semantic tokens are not supported, HTML may be incorrect') + notify('lsp semantic tokens are not supported, HTML may be incorrect') return end local srow, scol, erow, ecol = @@ -489,7 +509,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces) ---TODO(altermo) LSP semantic tokens (and some other extmarks) are only ---generated in visible lines, and not in the whole buffer. if (namespaces[extmark[4].ns_id] or ''):find('vim_lsp_inlayhint') then - vim.notify_once('Info(TOhtml): lsp inlay hints are not supported, HTML may be incorrect') + notify('lsp inlay hints are not supported, HTML may be incorrect') return end local styletable = state.style @@ -530,9 +550,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces) } for opt, val in pairs(not_supported) do if extmark[4][opt] == val then - vim.notify_once( - ('Info(TOhtml): extmark.%s="%s" is not supported, HTML may be incorrect'):format(opt, val) - ) + notify(('extmark.%s="%s" is not supported, HTML may be incorrect'):format(opt, val)) end end end @@ -618,9 +636,7 @@ local function styletable_folds(state) end end if has_folded and type(({ pcall(vim.api.nvim_eval, vim.o.foldtext) })[2]) == 'table' then - vim.notify_once( - 'Info(TOhtml): foldtext returning a table is half supported, HTML may be incorrect' - ) + notify('foldtext returning a table with highlights is not supported, HTML may be incorrect') end end -- cgit From eb37241d38ad35b9e6bfac6379dd10e60aa0350c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sun, 26 May 2024 10:27:12 -0700 Subject: fix(tohtml): properly handle multiple hl groups #29012 Problem: :TOhtml doesn't properly handle virtual text when it has multiple highlight groups. It also improperly calculates position offset for multi-byte virt_text characters. Solution: Apply the `vim.api.nvim_strwidth` broadly to properly calculate character offset, and handle the cases where the `hl` argument can be a table of multiple hl groups. --- runtime/lua/tohtml.lua | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 0fc349e86d..5e145950b7 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -188,6 +188,8 @@ local background_color_cache = nil --- @type string? local foreground_color_cache = nil +local len = vim.api.nvim_strwidth + --- @see https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands --- @param color "background"|"foreground"|integer --- @return string? @@ -312,9 +314,12 @@ local function style_line_insert_virt_text(style_line, col, val) end --- @param state vim.tohtml.state ---- @param hl string|integer|nil +--- @param hl string|integer|string[]|integer[]? --- @return nil|integer local function register_hl(state, hl) + if type(hl) == 'table' then + hl = hl[#hl] + end if type(hl) == 'nil' then return elseif type(hl) == 'string' then @@ -537,7 +542,7 @@ local function _styletable_extmarks_virt_text(state, extmark, namespaces) else style_line_insert_virt_text(styletable[row + 1], col + 1, { i[1], hlid }) end - virt_text_len = virt_text_len + #i[1] + virt_text_len = virt_text_len + len(i[1]) end if extmark[4].virt_text_pos == 'overlay' then styletable_insert_range(state, row + 1, col + 1, row + 1, col + virt_text_len + 1, HIDE_ID) @@ -782,7 +787,7 @@ local function styletable_statuscolumn(state) statuscolumn, { winid = state.winid, use_statuscol_lnum = row, highlights = true } ) - local width = vim.api.nvim_strwidth(status.str) + local width = len(status.str) if width > minwidth then minwidth = width end @@ -797,7 +802,7 @@ local function styletable_statuscolumn(state) for k, v in ipairs(hls) do local text = str:sub(v.start + 1, hls[k + 1] and hls[k + 1].start or nil) if k == #hls then - text = text .. (' '):rep(minwidth - vim.api.nvim_strwidth(str)) + text = text .. (' '):rep(minwidth - len(str)) end if text ~= '' then local hlid = register_hl(state, v.group) @@ -817,7 +822,6 @@ local function styletable_listchars(state) local function utf8_sub(str, i, j) return vim.fn.strcharpart(str, i - 1, j and j - i + 1 or nil) end - local len = vim.api.nvim_strwidth --- @type table local listchars = vim.opt_local.listchars:get() local ids = setmetatable({}, { -- cgit From c4eb0b64bd4923a72fe737837cfe234c80fb539c Mon Sep 17 00:00:00 2001 From: Guilherme Soares <48023091+guilhas07@users.noreply.github.com> Date: Mon, 27 May 2024 13:20:03 +0200 Subject: fix(treesitter): find buffer in multiple windows #28922 Problem: 1. When interacting with multiple :InspectTree and the source buffer windows there is a high chance of errors due to the window ids not being updated and validated. 2. Not all InspectTree windows were closed when the source buffer was closed. Solution: 1. Update InspectTree window id on `CursorMoved` event and validate source buffer window id before trying to navigate to it. 2. Close all InspectTree windows --- runtime/lua/vim/treesitter/dev.lua | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 5c91f101c0..ca8cf85eda 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -325,7 +325,10 @@ function M.inspect_tree(opts) opts = opts or {} + -- source buffer local buf = api.nvim_get_current_buf() + + -- window id for source buffer local win = api.nvim_get_current_win() local treeview = assert(TSTreeView:new(buf, opts.lang)) @@ -334,12 +337,14 @@ function M.inspect_tree(opts) close_win(vim.b[buf].dev_inspect) end + -- window id for tree buffer local w = opts.winid if not w then vim.cmd(opts.command or '60vnew') w = api.nvim_get_current_win() end + -- tree buffer local b = opts.bufnr if b then api.nvim_win_set_buf(w, b) @@ -375,6 +380,12 @@ function M.inspect_tree(opts) callback = function() local row = api.nvim_win_get_cursor(w)[1] local lnum, col = treeview:get(row).node:start() + + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + api.nvim_set_current_win(win) api.nvim_win_set_cursor(win, { lnum + 1, col }) end, @@ -432,6 +443,7 @@ function M.inspect_tree(opts) return true end + w = api.nvim_get_current_win() api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] local lnum, col, end_lnum, end_col = treeview:get(row).node:range() @@ -441,6 +453,11 @@ function M.inspect_tree(opts) hl_group = 'Visual', }) + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) -- Move the cursor if highlighted range is completely out of view @@ -506,7 +523,10 @@ function M.inspect_tree(opts) buffer = buf, once = true, callback = function() - close_win(w) + -- close all tree windows + for _, window in pairs(vim.fn.win_findbuf(b)) do + close_win(window) + end end, }) end -- cgit From 48251134ee59a3e2f0aeb89608fa820c21b25d4f Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 27 May 2024 08:08:23 -0500 Subject: perf: add fast path to vim.validate (#28977) For many small/simple functions (like those found in shared.lua), the runtime of vim.validate can far exceed the runtime of the function itself. Add an "overload" to vim.validate that uses a simple assertion pattern, rather than parsing a full "validation spec". --- runtime/lua/vim/shared.lua | 76 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 14 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index e9e4326057..2641d1feb0 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -214,7 +214,7 @@ end ---@param t table (table) Table ---@return T[] : List of keys function vim.tbl_keys(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table local keys = {} @@ -231,7 +231,7 @@ end ---@param t table (table) Table ---@return T[] : List of values function vim.tbl_values(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') local values = {} for _, v in @@ -332,7 +332,7 @@ end ---@param value any Value to compare ---@return boolean `true` if `t` contains `value` function vim.list_contains(t, value) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table for _, v in ipairs(t) do @@ -350,7 +350,7 @@ end ---@param t table Table to check ---@return boolean `true` if `t` is empty function vim.tbl_isempty(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') return next(t) == nil end @@ -580,7 +580,7 @@ end ---@return fun(table: table, index?: K):K, V # |for-in| iterator over sorted keys and their values ---@return T function vim.spairs(t) - assert(type(t) == 'table', ('expected table, got %s'):format(type(t))) + vim.validate('t', t, 'table') --- @cast t table -- collect the keys @@ -691,7 +691,7 @@ end ---@param t table Table ---@return integer : Number of non-nil values in table function vim.tbl_count(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table local count = 0 @@ -723,7 +723,7 @@ end ---@param s string String to trim ---@return string String with whitespace removed from its beginning and end function vim.trim(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return s:match('^%s*(.*%S)') or '' end @@ -733,7 +733,7 @@ end ---@param s string String to escape ---@return string %-escaped pattern string function vim.pesc(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) end @@ -743,7 +743,8 @@ end ---@param prefix string Prefix to match ---@return boolean `true` if `prefix` is a prefix of `s` function vim.startswith(s, prefix) - vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('prefix', prefix, 'string') return s:sub(1, #prefix) == prefix end @@ -753,7 +754,8 @@ end ---@param suffix string Suffix to match ---@return boolean `true` if `suffix` is a suffix of `s` function vim.endswith(s, suffix) - vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('suffix', suffix, 'string') return #suffix == 0 or s:sub(-#suffix) == suffix end @@ -877,8 +879,30 @@ do return true end - --- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric - --- order, until the first failure. + --- Validate function arguments. + --- + --- This function has two valid forms: + --- + --- 1. vim.validate(name: str, value: any, type: string, optional?: bool) + --- 2. vim.validate(spec: table) + --- + --- Form 1 validates that argument {name} with value {value} has the type + --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is + --- true, then {value} may be null. This form is significantly faster and + --- should be preferred for simple cases. + --- + --- Example: + --- + --- ```lua + --- function vim.startswith(s, prefix) + --- vim.validate('s', s, 'string') + --- vim.validate('prefix', prefix, 'string') + --- ... + --- end + --- ``` + --- + --- Form 2 validates a parameter specification (types and values). Specs are + --- evaluated in alphanumeric order, until the first failure. --- --- Usage example: --- @@ -930,8 +954,32 @@ do --- only if the argument is valid. Can optionally return an additional --- informative error message as the second returned value. --- - msg: (optional) error string if validation fails - function vim.validate(opt) - local ok, err_msg = is_valid(opt) + --- @overload fun(name: string, val: any, expected: string, optional?: boolean) + function vim.validate(opt, ...) + local ok = false + local err_msg ---@type string? + local narg = select('#', ...) + if narg == 0 then + ok, err_msg = is_valid(opt) + elseif narg >= 2 then + -- Overloaded signature for fast/simple cases + local name = opt --[[@as string]] + local v, expected, optional = ... ---@type string, string, boolean? + local actual = type(v) + + ok = (actual == expected) or (v == nil and optional == true) + if not ok then + err_msg = ('%s: expected %s, got %s%s'):format( + name, + expected, + actual, + v and (' (%s)'):format(v) or '' + ) + end + else + error('invalid arguments') + end + if not ok then error(err_msg, 2) end -- cgit From 292365fa1b8f543ffa2240bb30af34051ad2d7c8 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Mon, 27 May 2024 11:06:03 -0400 Subject: fix(lsp): do not detach from buffer if there are uninitialized clients (#29029) Problem: if on_lines is called before the LSP is initialized, the buffer is detached. Solution: check for uninitialized clients before detaching. --- runtime/lua/vim/lsp.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1592fd3151..60b3f3e502 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -577,7 +577,8 @@ local function buf_attach(bufnr) api.nvim_buf_attach(bufnr, false, { on_lines = function(_, _, changedtick, firstline, lastline, new_lastline) if #lsp.get_clients({ bufnr = bufnr }) == 0 then - return true -- detach + -- detach if there are no clients + return #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 end util.buf_versions[bufnr] = changedtick changetracking.send_changes(bufnr, firstline, lastline, new_lastline) -- cgit From 608543f8a90b08cbe84cea878a14f053789f45a4 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 27 May 2024 08:18:10 -0700 Subject: fix(snippet): cancel snippet session when leaving the buffer (#29031) --- runtime/lua/vim/snippet.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 3fd2555046..8e384e0f97 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -395,6 +395,15 @@ local function setup_autocmds(bufnr) end end, }) + + vim.api.nvim_create_autocmd('BufLeave', { + group = snippet_group, + desc = 'Stop the snippet session when leaving the buffer', + buffer = bufnr, + callback = function() + M.stop() + end, + }) end --- Expands the given snippet text. -- cgit From 6e8a728e3dad747d0c46dc47a530b76e8997bc08 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Sat, 25 May 2024 20:35:37 +0200 Subject: refactor: fix luals type warnings --- runtime/lua/tohtml.lua | 4 +- runtime/lua/vim/_meta.lua | 2 + runtime/lua/vim/_meta/vimfn.lua | 2 +- runtime/lua/vim/deprecated/health.lua | 2 +- runtime/lua/vim/health.lua | 12 ++--- runtime/lua/vim/health/health.lua | 3 +- runtime/lua/vim/lsp/util.lua | 4 +- runtime/lua/vim/provider/health.lua | 82 +++++++++++++++++------------------ 8 files changed, 56 insertions(+), 55 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/tohtml.lua b/runtime/lua/tohtml.lua index 5e145950b7..120247ed4e 100644 --- a/runtime/lua/tohtml.lua +++ b/runtime/lua/tohtml.lua @@ -65,9 +65,7 @@ local function notify(msg) if #notifications == 0 then vim.schedule(function() if #notifications > 1 then - vim.notify( - ('TOhtml: %s (+ %d more warnings)'):format(notifications[1], tostring(#notifications - 1)) - ) + vim.notify(('TOhtml: %s (+ %d more warnings)'):format(notifications[1], #notifications - 1)) elseif #notifications == 1 then vim.notify('TOhtml: ' .. notifications[1]) end diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 731dd5b923..c9f207cb20 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -34,3 +34,5 @@ vim.uri_from_fname = uri.uri_from_fname vim.uri_from_bufnr = uri.uri_from_bufnr vim.uri_to_fname = uri.uri_to_fname vim.uri_to_bufnr = uri.uri_to_bufnr + +vim.provider = require('vim.provider') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index f256f63768..84bb26a135 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1642,7 +1642,7 @@ function vim.fn.execute(command, silent) end --- If {expr} starts with "./" the |current-directory| is used. --- --- @param expr any ---- @return any +--- @return string function vim.fn.exepath(expr) end --- The result is a Number, which is |TRUE| if {expr} is diff --git a/runtime/lua/vim/deprecated/health.lua b/runtime/lua/vim/deprecated/health.lua index 0f6b1f578c..64a755b248 100644 --- a/runtime/lua/vim/deprecated/health.lua +++ b/runtime/lua/vim/deprecated/health.lua @@ -1,7 +1,7 @@ local M = {} local health = vim.health -local deprecated = {} +local deprecated = {} ---@type {[1]: string, [2]: table, [3]: string}[] function M.check() if next(deprecated) == nil then diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index afeba2ee9d..236f9da752 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -104,10 +104,10 @@ local function filepath_to_healthcheck(path) local subpath = path:gsub('.*lua/', '') if vim.fs.basename(subpath) == 'health.lua' then -- */health.lua - name = assert(vim.fs.dirname(subpath)) + name = vim.fs.dirname(subpath) else -- */health/init.lua - name = assert(vim.fs.dirname(assert(vim.fs.dirname(subpath)))) + name = vim.fs.dirname(vim.fs.dirname(subpath)) end name = name:gsub('/', '.') @@ -301,11 +301,13 @@ end local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' } --- :checkhealth completion function used by cmdexpand.c get_healthcheck_names() M._complete = function() - local unique = vim + local unique = vim ---@type table + ---@param pattern string .iter(vim.tbl_map(function(pattern) return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true)) end, PATTERNS)) :flatten() + ---@param t table :fold({}, function(t, name) t[name] = true -- Remove duplicates return t @@ -364,7 +366,7 @@ function M._check(mods, plugin_names) vim.fn.call(func, {}) else local f = assert(loadstring(func)) - local ok, output = pcall(f) + local ok, output = pcall(f) ---@type boolean, string if not ok then M.error( string.format('Failed to run healthcheck for "%s" plugin. Exception:\n%s\n', name, output) @@ -391,7 +393,7 @@ function M._check(mods, plugin_names) end s_output[#s_output + 1] = '' s_output = vim.list_extend(header, s_output) - vim.fn.append('$', s_output) + vim.fn.append(vim.fn.line('$'), s_output) vim.cmd.redraw() end diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index 5bc03199ee..235dacb82a 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -239,6 +239,7 @@ local function check_tmux() return end + ---@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)) @@ -378,7 +379,7 @@ local function check_terminal() 'SSH_TTY', }) do if vim.env[env_var] then - health.info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var])) + health.info(string.format('$%s="%s"', env_var, vim.env[env_var])) end end end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5a229a1169..0099e82f52 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -616,7 +616,7 @@ function M.rename(old_fname, new_fname, opts) buf_rename[b] = { from = old_bname, to = new_bname } end - local newdir = assert(vim.fs.dirname(new_fname)) + local newdir = vim.fs.dirname(new_fname) vim.fn.mkdir(newdir, 'p') local ok, err = os.rename(old_fname_full, new_fname) @@ -625,7 +625,7 @@ function M.rename(old_fname, new_fname, opts) local old_undofile = vim.fn.undofile(old_fname_full) if uv.fs_stat(old_undofile) ~= nil then local new_undofile = vim.fn.undofile(new_fname) - vim.fn.mkdir(assert(vim.fs.dirname(new_undofile)), 'p') + vim.fn.mkdir(vim.fs.dirname(new_undofile), 'p') os.rename(old_undofile, new_undofile) end diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index d6932f651e..fa2c452268 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -95,10 +95,10 @@ local function system(cmd, args) error(emsg) end - -- return opts.output return vim.trim(vim.fn.system(cmd)), shell_error_code end +---@param provider string local function provider_disabled(provider) local loaded_var = 'loaded_' .. provider .. '_provider' local v = vim.g[loaded_var] @@ -126,9 +126,9 @@ local function clipboard() health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) end - local clipboard_tool = vim.fn['provider#clipboard#Executable']() + local clipboard_tool = vim.fn['provider#clipboard#Executable']() ---@type string if vim.g.clipboard ~= nil and clipboard_tool == '' then - local error_message = vim.fn['provider#clipboard#Error']() + local error_message = vim.fn['provider#clipboard#Error']() ---@type string health.error( error_message, "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." @@ -179,7 +179,7 @@ local function node() ) end - local node_detect_table = vim.fn['provider#node#Detect']() + local node_detect_table = vim.fn['provider#node#Detect']() ---@type string[] local host = node_detect_table[1] if host:find('^%s*$') then health.warn('Missing "neovim" npm (or yarn, pnpm) package.', { @@ -290,7 +290,7 @@ local function perl() elseif latest_cpan[1] == '!' then local cpanm_errs = vim.split(latest_cpan, '!') if cpanm_errs[1]:find("Can't write to ") then - local advice = {} + local advice = {} ---@type string[] for i = 2, #cpanm_errs do advice[#advice + 1] = cpanm_errs[i] end @@ -303,7 +303,7 @@ local function perl() return end end - latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]]) + latest_cpan = tostring(vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]])) if latest_cpan:find('^%s*$') then health.error('Cannot parse version number from cpanm output: ' .. latest_cpan) return @@ -349,9 +349,11 @@ local function python_exepath(invocation) return vim.fs.normalize(vim.trim(p.stdout)) end --- Check if pyenv is available and a valid pyenv root can be found, then return --- their respective paths. If either of those is invalid, return two empty --- strings, effectively ignoring pyenv. +--- Check if pyenv is available and a valid pyenv root can be found, then return +--- their respective paths. If either of those is invalid, return two empty +--- strings, effectively ignoring pyenv. +--- +--- @return {[1]: string, [2]: string} local function check_for_pyenv() local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) @@ -394,7 +396,9 @@ local function check_bin(bin) return true end --- Fetch the contents of a URL. +--- Fetch the contents of a URL. +--- +--- @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 @@ -429,25 +433,24 @@ local function download(url) return message end --- Get the latest Nvim Python client (pynvim) version from PyPI. +--- Get the latest Nvim Python client (pynvim) version from PyPI. local function latest_pypi_version() local pypi_version = 'unable to get pypi response' local pypi_response = download('https://pypi.python.org/pypi/pynvim/json') if pypi_response ~= '' then local pcall_ok, output = pcall(vim.fn.json_decode, pypi_response) - local pypi_data - if pcall_ok then - pypi_data = output - else + if not pcall_ok then return 'error: ' .. pypi_response end + local pypi_data = output local pypi_element = pypi_data['info'] or {} pypi_version = pypi_element['version'] or 'unable to parse' end return pypi_version end +--- @param s string local function is_bad_response(s) local lower = s:lower() return vim.startswith(lower, 'unable') @@ -455,16 +458,18 @@ local function is_bad_response(s) or vim.startswith(lower, 'outdated') end --- Get version information using the specified interpreter. The interpreter is --- used directly in case breaking changes were introduced since the last time --- Nvim's Python client was updated. --- --- Returns: { --- {python executable version}, --- {current nvim version}, --- {current pypi nvim status}, --- {installed version status} --- } +--- Get version information using the specified interpreter. The interpreter is +--- used directly in case breaking changes were introduced since the last time +--- Nvim's Python client was updated. +--- +--- @param python string +--- +--- Returns: { +--- {python executable version}, +--- {current nvim version}, +--- {current pypi nvim status}, +--- {installed version status} +--- } local function version_info(python) local pypi_version = latest_pypi_version() @@ -512,9 +517,9 @@ local function version_info(python) if rc ~= 0 or nvim_version == '' then nvim_version = 'unable to find pynvim module version' local base = vim.fs.basename(nvim_path) - local metas = vim.fn.glob(base .. '-*/METADATA', 1, 1) - vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', 1, 1)) - vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', 1, 1)) + local metas = vim.fn.glob(base .. '-*/METADATA', true, 1) + vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', true, 1)) + vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', true, 1)) metas = table.sort(metas, compare) if metas and next(metas) ~= nil then @@ -544,14 +549,13 @@ end local function python