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/vim') 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/vim') 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/vim') 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/vim') 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/vim') 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/vim') 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/vim') 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/vim') 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/vim') 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 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/vim') 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/vim') 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/vim') 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/vim') 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/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 +++++++++++++++++------------------ 7 files changed, 55 insertions(+), 52 deletions(-) (limited to 'runtime/lua/vim') 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() health.start('Python 3 provider (optional)') - local pyname = 'python3' ---@type string? local python_exe = '' local virtual_env = os.getenv('VIRTUAL_ENV') local venv = virtual_env and vim.fn.resolve(virtual_env) or '' - local host_prog_var = pyname .. '_host_prog' - local python_multiple = {} + local host_prog_var = 'python3_host_prog' + local python_multiple = {} ---@type string[] - if provider_disabled(pyname) then + if provider_disabled('python3') then return end @@ -564,8 +568,7 @@ local function python() health.info(message) end - local pythonx_warnings - pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') + local pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') if not pyname then health.warn( @@ -653,12 +656,7 @@ local function python() ) health.warn('pyenv is not set up optimally.', advice) elseif venv ~= '' then - local venv_root - if pyenv_root ~= '' then - venv_root = pyenv_root - else - venv_root = vim.fs.dirname(venv) - end + local venv_root = pyenv_root ~= '' and pyenv_root or vim.fs.dirname(venv) if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then local advice = string.format( @@ -743,9 +741,9 @@ local function python() health.ok('no $VIRTUAL_ENV') return end - local errors = {} + local errors = {} ---@type string[] -- Keep hints as dict keys in order to discard duplicates. - local hints = {} + local hints = {} ---@type table -- The virtualenv should contain some Python executables, and those -- executables should be first both on Nvim's $PATH and the $PATH of -- subshells launched from Nvim. -- cgit From ff097f2091e7a970e5b12960683b4dade5563040 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sun, 4 Feb 2024 14:13:23 -0800 Subject: feat(lsp): completion side effects --- runtime/lua/vim/lsp.lua | 5 +- runtime/lua/vim/lsp/_completion.lua | 276 -------------- runtime/lua/vim/lsp/client.lua | 23 +- runtime/lua/vim/lsp/completion.lua | 734 ++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/lsp/protocol.lua | 10 +- 6 files changed, 757 insertions(+), 293 deletions(-) delete mode 100644 runtime/lua/vim/lsp/_completion.lua create mode 100644 runtime/lua/vim/lsp/completion.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1592fd3151..da3d4d91f2 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -3,7 +3,6 @@ local validate = vim.validate local lsp = vim._defer_require('vim.lsp', { _changetracking = ..., --- @module 'vim.lsp._changetracking' - _completion = ..., --- @module 'vim.lsp._completion' _dynamic = ..., --- @module 'vim.lsp._dynamic' _snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar' _tagfunc = ..., --- @module 'vim.lsp._tagfunc' @@ -11,6 +10,7 @@ local lsp = vim._defer_require('vim.lsp', { buf = ..., --- @module 'vim.lsp.buf' client = ..., --- @module 'vim.lsp.client' codelens = ..., --- @module 'vim.lsp.codelens' + completion = ..., --- @module 'vim.lsp.completion' diagnostic = ..., --- @module 'vim.lsp.diagnostic' handlers = ..., --- @module 'vim.lsp.handlers' inlay_hint = ..., --- @module 'vim.lsp.inlay_hint' @@ -1002,8 +1002,7 @@ end --- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=1: list of matches (actually just calls |complete()|) function lsp.omnifunc(findstart, base) - log.debug('omnifunc.findstart', { findstart = findstart, base = base }) - return vim.lsp._completion.omnifunc(findstart, base) + return vim.lsp.completion._omnifunc(findstart, base) end --- @class vim.lsp.formatexpr.Opts diff --git a/runtime/lua/vim/lsp/_completion.lua b/runtime/lua/vim/lsp/_completion.lua deleted file mode 100644 index a169f96565..0000000000 --- a/runtime/lua/vim/lsp/_completion.lua +++ /dev/null @@ -1,276 +0,0 @@ -local M = {} -local api = vim.api -local lsp = vim.lsp -local protocol = lsp.protocol -local ms = protocol.Methods - ---- @alias vim.lsp.CompletionResult lsp.CompletionList | lsp.CompletionItem[] - --- TODO(mariasolos): Remove this declaration once we figure out a better way to handle --- literal/anonymous types (see https://github.com/neovim/neovim/pull/27542/files#r1495259331). ---- @class lsp.ItemDefaults ---- @field editRange lsp.Range | { insert: lsp.Range, replace: lsp.Range } | nil ---- @field insertTextFormat lsp.InsertTextFormat? ---- @field insertTextMode lsp.InsertTextMode? ---- @field data any - ----@param input string unparsed snippet ----@return string parsed snippet -local function parse_snippet(input) - local ok, parsed = pcall(function() - return vim.lsp._snippet_grammar.parse(input) - end) - return ok and tostring(parsed) or input -end - ---- Returns text that should be inserted when selecting completion item. The ---- precedence is as follows: textEdit.newText > insertText > label ---- ---- See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion ---- ----@param item lsp.CompletionItem ----@return string -local function get_completion_word(item) - if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= '' then - if item.insertTextFormat == protocol.InsertTextFormat.PlainText then - return item.textEdit.newText - else - return parse_snippet(item.textEdit.newText) - end - elseif item.insertText ~= nil and item.insertText ~= '' then - if item.insertTextFormat == protocol.InsertTextFormat.PlainText then - return item.insertText - else - return parse_snippet(item.insertText) - end - end - return item.label -end - ---- Applies the given defaults to the completion item, modifying it in place. ---- ---- @param item lsp.CompletionItem ---- @param defaults lsp.ItemDefaults? -local function apply_defaults(item, defaults) - if not defaults then - return - end - - item.insertTextFormat = item.insertTextFormat or defaults.insertTextFormat - item.insertTextMode = item.insertTextMode or defaults.insertTextMode - item.data = item.data or defaults.data - if defaults.editRange then - local textEdit = item.textEdit or {} - item.textEdit = textEdit - textEdit.newText = textEdit.newText or item.textEditText or item.insertText - if defaults.editRange.start then - textEdit.range = textEdit.range or defaults.editRange - elseif defaults.editRange.insert then - textEdit.insert = defaults.editRange.insert - textEdit.replace = defaults.editRange.replace - end - end -end - ----@param result vim.lsp.CompletionResult ----@return lsp.CompletionItem[] -local function get_items(result) - if result.items then - for _, item in ipairs(result.items) do - ---@diagnostic disable-next-line: param-type-mismatch - apply_defaults(item, result.itemDefaults) - end - return result.items - else - return result - end -end - ---- Turns the result of a `textDocument/completion` request into vim-compatible ---- |complete-items|. ---- ----@param result vim.lsp.CompletionResult Result of `textDocument/completion` ----@param prefix string prefix to filter the completion items ----@return table[] ----@see complete-items -function M._lsp_to_complete_items(result, prefix) - local items = get_items(result) - if vim.tbl_isempty(items) then - return {} - end - - local function matches_prefix(item) - return vim.startswith(get_completion_word(item), prefix) - end - - items = vim.tbl_filter(matches_prefix, items) --[[@as lsp.CompletionItem[]|]] - table.sort(items, function(a, b) - return (a.sortText or a.label) < (b.sortText or b.label) - end) - - local matches = {} - for _, item in ipairs(items) do - local info = '' - local documentation = item.documentation - if documentation then - if type(documentation) == 'string' and documentation ~= '' then - info = documentation - elseif type(documentation) == 'table' and type(documentation.value) == 'string' then - info = documentation.value - else - vim.notify( - ('invalid documentation value %s'):format(vim.inspect(documentation)), - vim.log.levels.WARN - ) - end - end - local word = get_completion_word(item) - table.insert(matches, { - word = word, - abbr = item.label, - kind = protocol.CompletionItemKind[item.kind] or 'Unknown', - menu = item.detail or '', - info = #info > 0 and info or nil, - icase = 1, - dup = 1, - empty = 1, - user_data = { - nvim = { - lsp = { - completion_item = item, - }, - }, - }, - }) - end - return matches -end - ----@param lnum integer 0-indexed ----@param items lsp.CompletionItem[] -local function adjust_start_col(lnum, line, items, encoding) - local min_start_char = nil - for _, item in pairs(items) do - if item.textEdit and item.textEdit.range.start.line == lnum then - if min_start_char and min_start_char ~= item.textEdit.range.start.character then - return nil - end - min_start_char = item.textEdit.range.start.character - end - end - if min_start_char then - return vim.lsp.util._str_byteindex_enc(line, min_start_char, encoding) - else - return nil - end -end - ----@private ----@param line string line content ----@param lnum integer 0-indexed line number ----@param client_start_boundary integer 0-indexed word boundary ----@param server_start_boundary? integer 0-indexed word boundary, based on textEdit.range.start.character ----@param result vim.lsp.CompletionResult ----@param encoding string ----@return table[] matches ----@return integer? server_start_boundary -function M._convert_results( - line, - lnum, - cursor_col, - client_start_boundary, - server_start_boundary, - result, - encoding -) - -- Completion response items may be relative to a position different than `client_start_boundary`. - -- Concrete example, with lua-language-server: - -- - -- require('plenary.asy| - -- ▲ ▲ ▲ - -- │ │ └── cursor_pos: 20 - -- │ └────── client_start_boundary: 17 - -- └────────────── textEdit.range.start.character: 9 - -- .newText = 'plenary.async' - --