From a5ade3c63d88e93244c43ff0f0635f4774f890ce Mon Sep 17 00:00:00 2001 From: glepnir Date: Thu, 29 Feb 2024 18:50:40 +0800 Subject: fix(snippet): correct indent with newline Problem: snippet newline use before line indent after expand. Solution: it should level + 1. --- runtime/lua/vim/snippet.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 09b7576d97..a660d6f301 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -446,14 +446,18 @@ function M.expand(input) base_indent = base_indent .. (snippet_lines[#snippet_lines]:match('(^%s*)%S') or '') --- @type string end + local shiftwidth = vim.fn.shiftwidth() + local curbuf = vim.api.nvim_get_current_buf() + local expandtab = vim.bo[curbuf].expandtab local lines = vim.iter.map(function(i, line) -- Replace tabs by spaces. - if vim.o.expandtab then - line = line:gsub('\t', (' '):rep(vim.fn.shiftwidth())) --- @type string + if expandtab then + line = line:gsub('\t', (' '):rep(shiftwidth)) --- @type string end -- Add the base indentation. if i > 1 then - line = base_indent .. line + line = #line ~= 0 and base_indent .. line + or (expandtab and (' '):rep(shiftwidth) or '\t'):rep(vim.fn.indent('.') / shiftwidth + 1) end return line end, ipairs(text_to_lines(text))) -- cgit From b1577d371a6db43222de9e3a525def82320ebdb1 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Feb 2024 21:44:42 +0000 Subject: fix(api): make win_set_config with "win" for splits need "split/vertical" Problem: currently, for splits, nvim_win_set_config accepts win without any of split or vertical set, which has little effect and seems error-prone. Solution: require at least one of split or vertical to also be set for splits. Also, update nvim_win_set_config docs, as it's no longer limited to just floating and external windows. --- runtime/lua/vim/_meta/api.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 94eab72291..4a179d49f3 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -2207,11 +2207,11 @@ function vim.api.nvim_win_remove_ns(window, ns_id) end --- @param buffer integer Buffer handle function vim.api.nvim_win_set_buf(window, buffer) end ---- Configures window layout. Currently only for floating and external windows ---- (including changing a split window to those layouts). +--- Configures window layout. Cannot be used to move the last window in a +--- tabpage to a different one. --- ---- When reconfiguring a floating window, absent option keys will not be ---- changed. `row`/`col` and `relative` must be reconfigured together. +--- When reconfiguring a window, absent option keys will not be changed. +--- `row`/`col` and `relative` must be reconfigured together. --- --- @param window integer Window handle, or 0 for current window --- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()` -- cgit From a70eae57bd44208a77b5ac29839e8a39ab3c9cd8 Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 11 Feb 2024 22:53:37 +0000 Subject: fix(api): make open_win block only enter/leave events if !enter && !noautocmd Problem: nvim_open_win blocking all win_set_buf autocommands when !enter && !noautocmd is too aggressive. Solution: temporarily block WinEnter/Leave and BufEnter/Leave events when setting the buffer. Delegate the firing of BufWinEnter back to win_set_buf, which also has the advantage of keeping the timing consistent (e.g: before the epilogue in enter_buffer, which also handles restoring the cursor position if autocommands didn't change it, among other things). Reword the documentation for noautocmd a bit. I pondered modifying do_buffer and callees to allow for BufEnter/Leave being conditionally disabled, but it seems too invasive (and potentially error-prone, especially if new code paths to BufEnter/Leave are added in the future). Unfortunately, doing this has the drawback of blocking ALL such events for the duration, which also means blocking unrelated such events; like if window switching occurs in a ++nested autocmd fired by win_set_buf. If this turns out to be a problem in practice, a different solution specialized for nvim_open_win could be considered. :-) --- runtime/lua/vim/_meta/api.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 4a179d49f3..f2f5f43c26 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1719,9 +1719,9 @@ function vim.api.nvim_open_term(buffer, opts) end --- • footer_pos: Footer position. Must be set with `footer` --- option. Value can be one of "left", "center", or "right". --- Default is `"left"`. ---- • noautocmd: If true then no buffer-related autocommand ---- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may ---- fire from calling this function. +--- • noautocmd: If true then autocommands triggered from +--- setting the `buffer` to display are blocked (e.g: +--- `BufEnter`, `BufLeave`, `BufWinEnter`). --- • fixed: If true when anchor is NW or SW, the float window --- would be kept fixed even if the window would be truncated. --- • hide: If true the floating window will be hidden. -- cgit From 24dfa47e4f4ca41d0c5f8c1c0f851602362c81d3 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 24 Feb 2024 23:18:50 +0000 Subject: vim-patch:partial:9.1.0117: Stop split-moving from firing WinNew and WinNewPre autocommands Problem: win_splitmove fires WinNewPre and possibly WinNew when moving windows, even though no new windows are created. Solution: don't fire WinNew and WinNewPre when inserting an existing window, even if it isn't the current window. Improve the accuracy of related documentation. (Sean Dewar) https://github.com/vim/vim/commit/96cc4aef3d47d0fd70e68908af3d48a0dce8ea70 Partial as WinNewPre has not been ported yet (it currently has problems anyway). --- runtime/lua/vim/_meta/vimfn.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index ac25547212..ee68f669f8 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10598,10 +10598,10 @@ function vim.fn.win_move_statusline(nr, offset) end --- @return any function vim.fn.win_screenpos(nr) end ---- Move the window {nr} to a new split of the window {target}. ---- This is similar to moving to {target}, creating a new window ---- using |:split| but having the same contents as window {nr}, and ---- then closing {nr}. +--- Temporarily switch to window {target}, then move window {nr} +--- to a new split adjacent to {target}. +--- Unlike commands such as |:split|, no new windows are created +--- (the |window-ID| of window {nr} is unchanged after the move). --- --- Both {nr} and {target} can be window numbers or |window-ID|s. --- Both must be in the current tab page. -- cgit From 06fcf71bd0953baf9dc6d4c4bddf586c448f5ca6 Mon Sep 17 00:00:00 2001 From: Oscar Creator Date: Sat, 9 Mar 2024 17:10:58 +0100 Subject: fix(fswatch): --latency is locale dependent --- runtime/lua/vim/_watch.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 97c5481ad1..cf2689861a 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -303,6 +303,8 @@ function M.fswatch(path, opts, callback) fswatch_output_handler(line, opts, callback) end end, + -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. + env = { LC_NUMERIC = 'C' }, }) return function() -- cgit From 09a919f313ec8ae691798e45ee459a4467ce5d6a Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 30 Dec 2023 21:08:07 -0800 Subject: docs: more accurate typing for vim.tbl_extend --- runtime/lua/vim/shared.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index bd553598c7..a9eebf36da 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -402,7 +402,7 @@ end --- ---@see |extend()| --- ----@param behavior string Decides what to do if a key is found in more than one map: +---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map @@ -418,7 +418,7 @@ end --- ---@generic T1: table ---@generic T2: table ----@param behavior "error"|"keep"|"force" (string) Decides what to do if a key is found in more than one map: +---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map -- cgit From a09ddd7ce55037edc9747a682810fba6a26bc201 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 9 Mar 2024 12:21:01 +0000 Subject: docs(editorconfig): move to source --- runtime/lua/vim/_editor.lua | 1 - runtime/lua/vim/_meta/builtin.lua | 1 - 2 files changed, 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 6cf77b4648..f527fc194c 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -74,7 +74,6 @@ vim.log = { --- Examples: --- --- ```lua ---- --- local on_exit = function(obj) --- print(obj.code) --- print(obj.signal) diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 9a67667f02..ef9821fa32 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -215,7 +215,6 @@ function vim.schedule(fn) end --- Examples: --- --- ```lua ---- --- --- --- -- Wait for 100 ms, allowing other events to process --- vim.wait(100, function() end) -- cgit From 141182d6c6c06ad56413b81a518ba9b777a0cbe0 Mon Sep 17 00:00:00 2001 From: Colin Kennedy Date: Mon, 25 Dec 2023 20:41:09 -0800 Subject: vim-patch:9.1.0147: Cannot keep a buffer focused in a window Problem: Cannot keep a buffer focused in a window (Amit Levy) Solution: Add the 'winfixbuf' window-local option (Colin Kennedy) fixes: vim/vim#6445 closes: vim/vim#13903 https://github.com/vim/vim/commit/215703563757a4464907ead6fb9edaeb7f430bea N/A patch: vim-patch:58f1e5c0893a --- runtime/lua/vim/_meta/options.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 757720d8fb..e9ac2fe08f 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6746,6 +6746,8 @@ vim.bo.swf = vim.bo.swapfile --- "split" when both are present. --- uselast If included, jump to the previously used window when --- jumping to errors with `quickfix` commands. +--- If a window has 'winfixbuf' enabled, 'switchbuf' is currently not +--- applied to the split window. --- --- @type string vim.o.switchbuf = "uselast" @@ -7874,6 +7876,18 @@ vim.o.wi = vim.o.window vim.go.window = vim.o.window vim.go.wi = vim.go.window +--- If enabled, the buffer and any window that displays it are paired. +--- For example, attempting to change the buffer with `:edit` will fail. +--- Other commands which change a window's buffer such as `:cnext` will +--- also skip any window with 'winfixbuf' enabled. However if a command +--- has an "!" option, a window can be forced to switch buffers. +--- +--- @type boolean +vim.o.winfixbuf = false +vim.o.wfb = vim.o.winfixbuf +vim.wo.winfixbuf = vim.o.winfixbuf +vim.wo.wfb = vim.wo.winfixbuf + --- Keep the window height when windows are opened or closed and --- 'equalalways' is set. Also for `CTRL-W_=`. Set by default for the --- `preview-window` and `quickfix-window`. -- cgit From 0f20b7d803779950492c2838e2b042a38f4ee22f Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Mon, 11 Mar 2024 02:02:52 +0100 Subject: docs: adjust fswatch overflow message to mention docs with info - Add :h fswatch-limitations that notifies user about default inotify limitations on linux and how to adjust them - Check for Event queue overflow message from fswatch and refer user to new documentation Signed-off-by: Tomas Slusny --- runtime/lua/vim/_watch.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index cf2689861a..542e770246 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -289,6 +289,9 @@ function M.fswatch(path, opts, callback) end if data and #vim.trim(data) > 0 then + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then + data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + end vim.schedule(function() vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) end) -- cgit From 9cc755ad6a60e2b028d61c1dca62f8fe20f652d7 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 12 Mar 2024 05:39:21 +0800 Subject: vim-patch:0049a495c8d4 (#27817) runtime(doc): improve 'winfixbuf' docs (vim/vim#14180) - Make it not sound like a buffer option. - "!" is called a modifier, not an option. https://github.com/vim/vim/commit/0049a495c8d4a597773587f622d8cc8573c2eb75 --- runtime/lua/vim/_meta/options.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e9ac2fe08f..cba52f0afa 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -7876,11 +7876,11 @@ vim.o.wi = vim.o.window vim.go.window = vim.o.window vim.go.wi = vim.go.window ---- If enabled, the buffer and any window that displays it are paired. +--- If enabled, the window and the buffer it is displaying are paired. --- For example, attempting to change the buffer with `:edit` will fail. --- Other commands which change a window's buffer such as `:cnext` will ---- also skip any window with 'winfixbuf' enabled. However if a command ---- has an "!" option, a window can be forced to switch buffers. +--- also skip any window with 'winfixbuf' enabled. However if an Ex +--- command has a "!" modifier, it can force switching buffers. --- --- @type boolean vim.o.winfixbuf = false -- cgit From a74e869ffa503cc9c2d21836e24fec7a7ffca147 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 12 Mar 2024 06:51:53 +0100 Subject: docs: small fixes (#27364) Co-authored-by: C.D. MacEachern Co-authored-by: Ynda Jas Co-authored-by: Owen Hines Co-authored-by: Wanten <41904684+WantenMN@users.noreply.github.com> Co-authored-by: lukasvrenner <118417051+lukasvrenner@users.noreply.github.com> Co-authored-by: cuinix <915115094@qq.com> --- runtime/lua/vim/lsp/client.lua | 2 +- runtime/lua/vim/lsp/util.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index ff0db166d5..d48be131f3 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -15,7 +15,7 @@ local validate = vim.validate --- @inlinedoc --- --- Allow using incremental sync for buffer edits ---- (defailt: `true`) +--- (default: `true`) --- @field allow_incremental_sync? boolean --- --- Debounce `didChange` notifications to the server by the given number in milliseconds. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index f8e5b6a90d..fc99f54d03 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -690,7 +690,7 @@ end --- --- It deletes existing buffers that conflict with the renamed file name only when --- * `opts` requests overwriting; or ---- * the conflicting buffers are not loaded, so that deleting thme does not result in data loss. +--- * the conflicting buffers are not loaded, so that deleting them does not result in data loss. --- --- @param old_fname string --- @param new_fname string -- cgit From 41fb98d6fab5aa02ef370d1b2b283b078517ffa4 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Tue, 12 Mar 2024 08:15:55 +0100 Subject: fix: move fswatch linux check inside of vim.schedule (#27824) Fixes issue reported in the original PR: https://github.com/neovim/neovim/pull/27810 Signed-off-by: Tomas Slusny --- runtime/lua/vim/_watch.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 542e770246..23c810099e 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -289,10 +289,11 @@ function M.fswatch(path, opts, callback) end if data and #vim.trim(data) > 0 then - if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then - data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' - end vim.schedule(function() + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then + data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + end + vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) end) end -- cgit From cb46f6e467268edf917cc3617b4c024a66b256de Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 12 Mar 2024 09:32:17 -0500 Subject: feat(treesitter): support URLs (#27132) Tree-sitter queries can add URLs to a capture using the `#set!` directive, e.g. (inline_link (link_text) @text.reference (link_destination) @text.uri (#set! @text.reference "url" @text.uri)) The pattern above is included by default in the `markdown_inline` highlight query so that users with supporting terminals will see hyperlinks. For now, this creates a hyperlink for *all* Markdown URLs of the pattern [link text](link url), even if `link url` does not contain a valid protocol (e.g. if `link url` is a path to a file). We may wish to change this in the future to only linkify when the URL has a valid protocol scheme, but for now we delegate handling this to the terminal emulator. In order to support directives which reference other nodes, the highlighter must be updated to use `iter_matches` rather than `iter_captures`. The former provides the `match` table which maps capture IDs to nodes. However, this has its own challenges: - `iter_matches` does not guarantee the order in which patterns are iterated matches the order in the query file. So we must enforce ordering manually using "subpriorities" (#27131). The pattern index of each match dictates the extmark's subpriority. - When injections are used, the highlighter contains multiple trees. The pattern indices of each tree must be offset relative to the maximum pattern index from all previous trees to ensure that extmarks appear in the correct order. - The `iter_captures` implementation currently has a bug where the "match" table is only returned for the first capture within a pattern (see #27274). This bug means that `#set!` directives in a query apply only to the first capture within a pattern. Unfortunately, many queries in the wild have come to depend on this behavior. `iter_matches` does not share this flaw, so switching to `iter_matches` exposed bugs in existing highlight queries. These queries have been updated in this repo, but may still need to be updated by users. The `#set!` directive applies to the _entire_ query pattern when used without a capture argument. To make `#set!` apply only to a single capture, the capture must be given as an argument. --- runtime/lua/vim/treesitter/highlighter.lua | 91 ++++++++++++++++++++++-------- 1 file changed, 66 insertions(+), 25 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 388680259a..cc5e11d632 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range') local ns = api.nvim_create_namespace('treesitter/highlighter') ----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata +---@alias vim.treesitter.highlighter.Iter fun(): integer, table, vim.treesitter.query.TSMetadata ---@class (private) vim.treesitter.highlighter.Query ---@field private _query vim.treesitter.Query? @@ -248,6 +248,13 @@ end ---@param line integer ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) + -- Track the maximum pattern index encountered in each tree. For subsequent + -- trees, the subpriority passed to nvim_buf_set_extmark is offset by the + -- largest pattern index from the prior tree. This ensures that extmarks + -- from subsequent trees always appear "on top of" extmarks from previous + -- trees (e.g. injections should always appear over base highlights). + local pattern_offset = 0 + self:for_each_highlight_state(function(state) local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() @@ -258,22 +265,24 @@ local function on_line_impl(self, buf, line, is_spell_nav) end if state.iter == nil or state.next_row < line then - state.iter = - state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) + state.iter = state.highlighter_query + :query() + :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end + local max_pattern_index = -1 while line >= state.next_row do - local capture, node, metadata = state.iter(line) + local pattern, match, metadata = state.iter() - local range = { root_end_row + 1, 0, root_end_row + 1, 0 } - if node then - range = vim.treesitter.get_range(node, buf, metadata and metadata[capture]) + if pattern and pattern > max_pattern_index then + max_pattern_index = pattern end - local start_row, start_col, end_row, end_col = Range.unpack4(range) - if capture then - local hl = state.highlighter_query:get_hl_from_capture(capture) + if not match then + state.next_row = root_end_row + 1 + end + for capture, nodes in pairs(match or {}) do local capture_name = state.highlighter_query:query().captures[capture] local spell = nil ---@type boolean? if capture_name == 'spell' then @@ -282,28 +291,60 @@ local function on_line_impl(self, buf, line, is_spell_nav) spell = false end + local hl = state.highlighter_query:get_hl_from_capture(capture) + -- Give nospell a higher priority so it always overrides spell captures. local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then - local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter) - + spell_pri_offset - api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { - end_line = end_row, - end_col = end_col, - hl_group = hl, - ephemeral = true, - priority = priority, - conceal = metadata.conceal, - spell = spell, - }) + -- The "priority" attribute can be set at the pattern level or on a particular capture + local priority = ( + tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) + or vim.highlight.priorities.treesitter + ) + spell_pri_offset + + local url = metadata[capture] and metadata[capture].url ---@type string|number|nil + if type(url) == 'number' then + if match and match[url] then + -- Assume there is only one matching node. If there is more than one, take the URL + -- from the first. + local other_node = match[url][1] + url = vim.treesitter.get_node_text(other_node, buf, { + metadata = metadata[url], + }) + else + url = nil + end end - end - if start_row > line then - state.next_row = start_row + -- The "conceal" attribute can be set at the pattern level or on a particular capture + local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal + + for _, node in ipairs(nodes) do + local range = vim.treesitter.get_range(node, buf, metadata[capture]) + local start_row, start_col, end_row, end_col = Range.unpack4(range) + + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then + api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { + end_line = end_row, + end_col = end_col, + hl_group = hl, + ephemeral = true, + priority = priority, + _subpriority = pattern_offset + pattern, + conceal = conceal, + spell = spell, + url = url, + }) + end + + if start_row > line then + state.next_row = start_row + end + end end end + + pattern_offset = pattern_offset + max_pattern_index end) end -- cgit From dc7ccd6bca81dfa6ade6462a6e30770c63d48266 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:13:40 -0500 Subject: fix(treesitter): use 0 as initial value for computing maximum (#27837) Using -1 as the initial value can cause the pattern offset to become negative, which in turn results in a negative subpriority, which fails validation in nvim_buf_set_extmark. --- runtime/lua/vim/treesitter/highlighter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index cc5e11d632..cbab5e990e 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -270,7 +270,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end - local max_pattern_index = -1 + local max_pattern_index = 0 while line >= state.next_row do local pattern, match, metadata = state.iter() -- cgit From ca7b603d02ecd1ed4098f487cd01acd470ca6a74 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:08:50 +0000 Subject: vim-patch:9.1.0170: Re-allow curwin == prevwin, but document it instead Problem: more places exist where curwin == prevwin, and it may even be expected in some cases. Solution: revert v9.1.0001, but document that it's possible instead. (Sean Dewar) I've had a change of heart for the following reasons: - A quick 'n dirty [GitHub code search](https://github.com/search?q=%2F%28winnr%5C%28%5C%29%5Cs*%3D%3D%5Cs*winnr%5C%28%5B%27%22%5D%23%5B%27%22%5D%5C%29%7Cwinnr%5C%28%5B%27%22%5D%23%5B%27%22%5D%5C%29%5Cs*%3D%3D%5Cs*winnr%5C%28%5C%29%29%2F&type=code) reveals some cases where it's expected in the wild. Particularly, it made me aware `winnr() == winnr('#')` is possible when curwin is changed temporarily during the evaluation of a &statusline expression item (`%{...}`), and is used to show something different on the statusline belonging to the previous window; that behaviour wasn't changed in v9.1.0001, but it means curwin == prevwin makes sense in some cases. - The definition and call sites of back_to_prevwin imply some expectation that prevwin == wp (== curwin) is possible, as it's used to skip entering the prevwin in that case. - Prior to v9.1.0001, `:wincmd p` would not beep in the case that was patched in v9.1.0001, but now does. That resulted in vim/vim#14047 being opened, as it affected the CtrlP plugin. I find it odd that `:wincmd p` had cases where it wouldn't beep despite doing nothing, but it may be preferable to keep things that way (or instead also beep if curwin == prevwin, if that's preferred). - After more digging, I found cases in win_free_mem, enter_tabpage, aucmd_restbuf and qf_open_new_cwindow where curwin == prevwin is possible (many of them from autocommands). Others probably exist too, especially in places where curwin is changed temporarily. fixes: vim/vim#14047 closes: vim/vim#14186 https://github.com/vim/vim/commit/d64801e913314d2e19dbb38f60e6d285238debff --- runtime/lua/vim/_meta/vimfn.lua | 4 +++- 1 file changed, 3 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 ee68f669f8..a51f89227b 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10724,7 +10724,9 @@ function vim.fn.winline() end --- # the number of the last accessed window (where --- |CTRL-W_p| goes to). If there is no previous --- window or it is in another tab page 0 is ---- returned. +--- returned. May refer to the current window in +--- some cases (e.g. when evaluating 'statusline' +--- expressions). --- {N}j the number of the Nth window below the --- current window (where |CTRL-W_j| goes to). --- {N}k the number of the Nth window above the current -- cgit From c048beef6c034a46e324fcea7210082d48db32ee Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:41:35 +0000 Subject: vim-patch:9a660d2883f9 runtime(doc): add reference to matchbufline() at :h search() related: vim/vim#14173 https://github.com/vim/vim/commit/9a660d2883f92b3a3761c964dc14363a8f70c8d8 Co-authored-by: Christian Brabandt --- runtime/lua/vim/_meta/vimfn.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index a51f89227b..2b93ea7d4e 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -7259,6 +7259,7 @@ function vim.fn.screenstring(row, col) end --- When a match has been found its line number is returned. --- If there is no match a 0 is returned and the cursor doesn't --- move. No error message is given. +--- To get the matched string, use |matchbufline()|. --- --- {flags} is a String, which can contain these character flags: --- 'b' search Backward instead of forward -- cgit From bbb68e2a034ad3aaea99178c09301ca458ee8dff Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Wed, 13 Mar 2024 22:06:39 +0000 Subject: vim-patch:9.1.0175: wrong window positions with 'winfix{width,height}' (#27845) Problem: winframe functions incorrectly recompute window positions if the altframe wasn't adjacent to the closed frame, which is possible if adjacent windows had 'winfix{width,height}' set. Solution: recompute for windows within the parent of the altframe and closed frame. Skip this (as before) if the altframe was top/left, but only if adjacent to the closed frame, as positions won't change in that case. Also correct the return value documentation for win_screenpos. (Sean Dewar) The issue revealed itself after removing the win_comp_pos call below winframe_restore in win_splitmove. Similarly, wrong positions could result from windows closed in other tabpages, as win_free_mem uses winframe_remove (at least until it is entered later, where enter_tabpage calls win_comp_pos). NOTE: As win_comp_pos handles only curtab, it's possible via other means for positions in non-current tabpages to be wrong (e.g: after changing 'laststatus', 'showtabline', etc.). Given enter_tabpage recomputes it, maybe it's intentional as an optimization? Should probably be documented in win_screenpos then, but I won't address that here. closes: vim/vim#14191 Nvim: don't reuse "wp" for "topleft" in winframe_remove, so the change integrates better with the call to winframe_find_altwin before it. https://github.com/vim/vim/commit/5866bc3a0f54115d5982fdc09bdbe4c45069265a --- runtime/lua/vim/_meta/vimfn.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 2b93ea7d4e..fb5e2a727e 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -10592,8 +10592,7 @@ function vim.fn.win_move_statusline(nr, offset) end --- [1, 1], unless there is a tabline, then it is [2, 1]. --- {nr} can be the window number or the |window-ID|. Use zero --- for the current window. ---- Returns [0, 0] if the window cannot be found in the current ---- tabpage. +--- Returns [0, 0] if the window cannot be found. --- --- @param nr integer --- @return any -- cgit From 12faaf40f487132b9397d9f3e59e44840985612c Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Mar 2024 14:40:41 +0000 Subject: fix(treesitter): highlight injections properly `on_line_impl` doesn't highlight single lines, so using pattern indexes to offset priority doesn't work. --- runtime/lua/vim/treesitter/highlighter.lua | 26 ++++++++++++-------------- runtime/lua/vim/treesitter/languagetree.lua | 16 ++++++++++------ 2 files changed, 22 insertions(+), 20 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index cbab5e990e..6175977b49 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -57,6 +57,7 @@ end ---@field next_row integer ---@field iter vim.treesitter.highlighter.Iter? ---@field highlighter_query vim.treesitter.highlighter.Query +---@field level integer Injection level ---@nodoc ---@class vim.treesitter.highlighter @@ -192,12 +193,20 @@ function TSHighlighter:prepare_highlight_states(srow, erow) return end + local level = 0 + local t = tree + while t do + t = t:parent() + level = level + 1 + end + -- _highlight_states should be a list so that the highlights are added in the same order as -- for_each_tree traversal. This ensures that parents' highlight don't override children's. table.insert(self._highlight_states, { tstree = tstree, next_row = 0, iter = nil, + level = level, highlighter_query = highlighter_query, }) end) @@ -248,14 +257,10 @@ end ---@param line integer ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) - -- Track the maximum pattern index encountered in each tree. For subsequent - -- trees, the subpriority passed to nvim_buf_set_extmark is offset by the - -- largest pattern index from the prior tree. This ensures that extmarks - -- from subsequent trees always appear "on top of" extmarks from previous - -- trees (e.g. injections should always appear over base highlights). - local pattern_offset = 0 - self:for_each_highlight_state(function(state) + -- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark + -- so injections always appear over base highlights. + local pattern_offset = state.level * 1000 local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() @@ -270,14 +275,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) end - local max_pattern_index = 0 while line >= state.next_row do local pattern, match, metadata = state.iter() - if pattern and pattern > max_pattern_index then - max_pattern_index = pattern - end - if not match then state.next_row = root_end_row + 1 end @@ -343,8 +343,6 @@ local function on_line_impl(self, buf, line, is_spell_nav) end end end - - pattern_offset = pattern_offset + max_pattern_index end) end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 62714d3f1b..ec933f5194 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -81,7 +81,7 @@ local TSCallbackNames = { ---List of regions this tree should manage and parse. If nil then regions are ---taken from _trees. This is mostly a short-lived cache for included_regions() ---@field private _lang string Language name ----@field private _parent_lang? string Parent language name +---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees table Reference to parsed tree (one for each language). ---Each key is the index of region, which is synced with _regions and _valid. @@ -106,9 +106,8 @@ LanguageTree.__index = LanguageTree ---@param source (integer|string) Buffer or text string to parse ---@param lang string Root language of this tree ---@param opts vim.treesitter.LanguageTree.new.Opts? ----@param parent_lang? string Parent language name of this tree ---@return vim.treesitter.LanguageTree parser object -function LanguageTree.new(source, lang, opts, parent_lang) +function LanguageTree.new(source, lang, opts) language.add(lang) opts = opts or {} @@ -122,7 +121,6 @@ function LanguageTree.new(source, lang, opts, parent_lang) local self = { _source = source, _lang = lang, - _parent_lang = parent_lang, _children = {}, _trees = {}, _opts = opts, @@ -505,19 +503,25 @@ function LanguageTree:add_child(lang) self:remove_child(lang) end - local child = LanguageTree.new(self._source, lang, self._opts, self:lang()) + local child = LanguageTree.new(self._source, lang, self._opts) -- Inherit recursive callbacks for nm, cb in pairs(self._callbacks_rec) do vim.list_extend(child._callbacks_rec[nm], cb) end + child._parent = self self._children[lang] = child self:_do_callback('child_added', self._children[lang]) return self._children[lang] end +--- @package +function LanguageTree:parent() + return self._parent +end + --- Removes a child language from this |LanguageTree|. --- ---@private @@ -792,7 +796,7 @@ function LanguageTree:_get_injection(match, metadata) local combined = metadata['injection.combined'] ~= nil local injection_lang = metadata['injection.language'] --[[@as string?]] local lang = metadata['injection.self'] ~= nil and self:lang() - or metadata['injection.parent'] ~= nil and self._parent_lang + or metadata['injection.parent'] ~= nil and self._parent or (injection_lang and resolve_lang(injection_lang)) local include_children = metadata['injection.include-children'] ~= nil -- cgit From 00c4962cd241044c9f02de39b34ca24b2711de43 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 13 Mar 2024 14:56:11 +0000 Subject: refactor(treesitter): move some logic into functions --- runtime/lua/vim/treesitter/highlighter.lua | 62 +++++++++++++++++++----------- 1 file changed, 40 insertions(+), 22 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 6175977b49..7bc6e5c019 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -252,6 +252,44 @@ function TSHighlighter:get_query(lang) return self._queries[lang] end +--- @param match table +--- @param bufnr integer +--- @param capture integer +--- @param metadata vim.treesitter.query.TSMetadata +--- @return string? +local function get_url(match, bufnr, capture, metadata) + ---@type string|number|nil + local url = metadata[capture] and metadata[capture].url + + if not url or type(url) == 'string' then + return url + end + + if not match or not match[url] then + return + end + + -- Assume there is only one matching node. If there is more than one, take the URL + -- from the first. + local other_node = match[url][1] + + return vim.treesitter.get_node_text(other_node, bufnr, { + metadata = metadata[url], + }) +end + +--- @param capture_name string +--- @return boolean?, integer +local function get_spell(capture_name) + if capture_name == 'spell' then + return true, 0 + elseif capture_name == 'nospell' then + -- Give nospell a higher priority so it always overrides spell captures. + return false, 1 + end + return nil, 0 +end + ---@param self vim.treesitter.highlighter ---@param buf integer ---@param line integer @@ -284,37 +322,17 @@ local function on_line_impl(self, buf, line, is_spell_nav) for capture, nodes in pairs(match or {}) do local capture_name = state.highlighter_query:query().captures[capture] - local spell = nil ---@type boolean? - if capture_name == 'spell' then - spell = true - elseif capture_name == 'nospell' then - spell = false - end + local spell, spell_pri_offset = get_spell(capture_name) local hl = state.highlighter_query:get_hl_from_capture(capture) - -- Give nospell a higher priority so it always overrides spell captures. - local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - -- The "priority" attribute can be set at the pattern level or on a particular capture local priority = ( tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) or vim.highlight.priorities.treesitter ) + spell_pri_offset - local url = metadata[capture] and metadata[capture].url ---@type string|number|nil - if type(url) == 'number' then - if match and match[url] then - -- Assume there is only one matching node. If there is more than one, take the URL - -- from the first. - local other_node = match[url][1] - url = vim.treesitter.get_node_text(other_node, buf, { - metadata = metadata[url], - }) - else - url = nil - end - end + local url = get_url(match, buf, capture, metadata) -- The "conceal" attribute can be set at the pattern level or on a particular capture local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal -- cgit From 120c4ec855bc654ae067fafdb63bb16460d97c88 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 28 Feb 2024 18:47:47 +0100 Subject: fix(terminal): disable reflow again reverts https://github.com/neovim/neovim/commit/c855eee919f2d4edc9b9fa91b277454290fbabfe This setting introduces constant CI failures on macos (see https://github.com/neovim/neovim/issues/23762). --- runtime/lua/vim/_meta/options.lua | 3 --- 1 file changed, 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index cba52f0afa..ac366197cc 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -5195,9 +5195,6 @@ vim.wo.scr = vim.wo.scroll --- Minimum is 1, maximum is 100000. --- Only in `terminal` buffers. --- ---- Note: Lines that are not visible and kept in scrollback are not ---- reflown when the terminal buffer is resized horizontally. ---- --- @type integer vim.o.scrollback = -1 vim.o.scbk = vim.o.scrollback -- cgit From d326e04860427b0a6a0b66da86fae8e5d23c8a7c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 15 Mar 2024 08:05:59 +0800 Subject: vim-patch:9.1.0181: no overflow check for string formatting (#27863) Problem: no overflow check for string formatting Solution: Check message formatting function for overflow. (Chris van Willegen) closes: vim/vim#13799 https://github.com/vim/vim/commit/c35fc03dbd47582b256776fb11f11d8ceb24f8f0 Co-authored-by: Christ van Willegen --- runtime/lua/vim/_meta/vimfn.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index fb5e2a727e..da251f89ad 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -6520,6 +6520,9 @@ function vim.fn.prevnonblank(lnum) end --- echo printf("%1$*2$.*3$f", 1.4142135, 6, 2) --- < 1.41 --- +--- You will get an overflow error |E1510|, when the field-width +--- or precision will result in a string longer than 6400 chars. +--- --- *E1500* --- You cannot mix positional and non-positional arguments: >vim --- echo printf("%s%1$s", "One", "Two") -- cgit From 14e4b6bbd8640675d7393bdeb3e93d74ab875ff1 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 16 Mar 2024 17:11:42 +0000 Subject: refactor(lua): type annotations --- runtime/lua/vim/highlight.lua | 27 +++++++++++---- runtime/lua/vim/keymap.lua | 40 ++++++++++++++-------- runtime/lua/vim/secure.lua | 2 +- runtime/lua/vim/snippet.lua | 15 +++++---- runtime/lua/vim/termcap.lua | 2 +- runtime/lua/vim/text.lua | 4 +-- runtime/lua/vim/treesitter/languagetree.lua | 38 ++++++++++++--------- runtime/lua/vim/treesitter/query.lua | 51 +++++++++++++---------------- 8 files changed, 105 insertions(+), 74 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index effe280dee..09d3ea5707 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -40,6 +40,23 @@ M.priorities = { user = 200, } +--- @class vim.highlight.range.Opts +--- @inlinedoc +--- +--- Type of range. See [setreg()] +--- (default: `'charwise'`) +--- @field regtype? string +--- +--- Indicates whether the range is end-inclusive +--- (default: `false`) +--- @field inclusive? boolean +--- +--- Indicates priority of highlight +--- (default: `vim.highlight.priorities.user`) +--- @field priority? integer +--- +--- @field package _scoped? boolean + --- Apply highlight group to range of text. --- ---@param bufnr integer Buffer number to apply highlighting to @@ -47,10 +64,7 @@ M.priorities = { ---@param higroup string Highlight group to use for highlighting ---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()| ---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()| ----@param opts table|nil Optional parameters ---- - regtype type of range (see |setreg()|, default charwise) ---- - inclusive boolean indicating whether the range is end-inclusive (default false) ---- - priority number indicating priority of highlight (default priorities.user) +---@param opts? vim.highlight.range.Opts function M.range(bufnr, ns, higroup, start, finish, opts) opts = opts or {} local regtype = opts.regtype or 'v' @@ -80,8 +94,8 @@ function M.range(bufnr, ns, higroup, start, finish, opts) end local yank_ns = api.nvim_create_namespace('hlyank') -local yank_timer -local yank_cancel +local yank_timer --- @type uv.uv_timer_t? +local yank_cancel --- @type fun()? --- Highlight the yanked text --- @@ -128,6 +142,7 @@ function M.on_yank(opts) local winid = vim.api.nvim_get_current_win() if yank_timer then yank_timer:close() + assert(yank_cancel) yank_cancel() end diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 84e9b4197d..ec00c56c7a 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -1,5 +1,20 @@ local keymap = {} +--- Table of |:map-arguments|. +--- Same as |nvim_set_keymap()| {opts}, except: +--- - {replace_keycodes} defaults to `true` if "expr" is `true`. +--- +--- Also accepts: +--- @class vim.keymap.set.Opts : vim.api.keyset.keymap +--- @inlinedoc +--- +--- Creates buffer-local mapping, `0` or `true` for current buffer. +--- @field buffer? integer|boolean +--- +--- Make the mapping recursive. Inverse of {noremap}. +--- (Default: `false`) +--- @field remap? boolean + --- Adds a new |mapping|. --- Examples: --- @@ -18,20 +33,12 @@ local keymap = {} --- vim.keymap.set('n', '[%%', '(MatchitNormalMultiBackward)') --- ``` --- ----@param mode string|table Mode short-name, see |nvim_set_keymap()|. +---@param mode string|string[] Mode short-name, see |nvim_set_keymap()|. --- Can also be list of modes to create mapping on multiple modes. ---@param lhs string Left-hand side |{lhs}| of the mapping. ---@param rhs string|function Right-hand side |{rhs}| of the mapping, can be a Lua function. --- ----@param opts table|nil Table of |:map-arguments|. ---- - Same as |nvim_set_keymap()| {opts}, except: ---- - "replace_keycodes" defaults to `true` if "expr" is `true`. ---- - "noremap": inverse of "remap" (see below). ---- - Also accepts: ---- - "buffer": (integer|boolean) Creates buffer-local mapping, `0` or `true` ---- for current buffer. ---- - "remap": (boolean) Make the mapping recursive. Inverse of "noremap". ---- Defaults to `false`. +---@param opts? vim.keymap.set.Opts ---@see |nvim_set_keymap()| ---@see |maparg()| ---@see |mapcheck()| @@ -81,6 +88,13 @@ function keymap.set(mode, lhs, rhs, opts) end end +--- @class vim.keymap.del.Opts +--- @inlinedoc +--- +--- Remove a mapping from the given buffer. +--- When `0` or `true`, use the current buffer. +--- @field buffer? integer|boolean + --- Remove an existing mapping. --- Examples: --- @@ -92,11 +106,8 @@ end --- ---@param modes string|string[] ---@param lhs string ----@param opts table|nil A table of optional arguments: ---- - "buffer": (integer|boolean) Remove a mapping from the given buffer. ---- When `0` or `true`, use the current buffer. +---@param opts? vim.keymap.del.Opts ---@see |vim.keymap.set()| ---- function keymap.del(modes, lhs, opts) vim.validate({ mode = { modes, { 's', 't' } }, @@ -106,6 +117,7 @@ function keymap.del(modes, lhs, opts) opts = opts or {} modes = type(modes) == 'string' and { modes } or modes + --- @cast modes string[] local buffer = false ---@type false|integer if opts.buffer ~= nil then diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 3992eef78a..41a3d3ba25 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -126,7 +126,7 @@ end --- --- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. --- ----@param opts? vim.trust.opts +---@param opts vim.trust.opts ---@return boolean success true if operation was successful ---@return string msg full path if operation was successful, else error message function M.trust(opts) diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 2ffd89367f..a1e3360b2d 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -254,9 +254,10 @@ local function display_choices(tabstop) assert(tabstop.choices, 'Tabstop has no choices') local start_col = tabstop:get_range()[2] + 1 - local matches = vim.iter.map(function(choice) - return { word = choice } - end, tabstop.choices) + local matches = {} --- @type table[] + for _, choice in ipairs(tabstop.choices) do + matches[#matches + 1] = { word = choice } + end vim.defer_fn(function() vim.fn.complete(start_col, matches) @@ -449,7 +450,9 @@ function M.expand(input) local shiftwidth = vim.fn.shiftwidth() local curbuf = vim.api.nvim_get_current_buf() local expandtab = vim.bo[curbuf].expandtab - local lines = vim.iter.map(function(i, line) + + local lines = {} --- @type string[] + for i, line in ipairs(text_to_lines(text)) do -- Replace tabs by spaces. if expandtab then line = line:gsub('\t', (' '):rep(shiftwidth)) --- @type string @@ -459,8 +462,8 @@ function M.expand(input) line = #line ~= 0 and base_indent .. line or (expandtab and (' '):rep(shiftwidth) or '\t'):rep(vim.fn.indent('.') / shiftwidth + 1) end - return line - end, ipairs(text_to_lines(text))) + lines[#lines + 1] = line + end table.insert(snippet_text, table.concat(lines, '\n')) end diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index ec29acca48..6152f50730 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -12,7 +12,7 @@ local M = {} --- emulator supports the XTGETTCAP sequence. --- --- @param caps string|table A terminal capability or list of capabilities to query ---- @param cb function(cap:string, found:bool, seq:string?) Callback function which is called for +--- @param cb fun(cap:string, found:bool, seq:string?) Callback function which is called for --- each capability in {caps}. {found} is set to true if the capability was found or false --- otherwise. {seq} is the control sequence for the capability if found, or nil for --- boolean capabilities. diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua index 576b962838..bc90d490aa 100644 --- a/runtime/lua/vim/text.lua +++ b/runtime/lua/vim/text.lua @@ -5,7 +5,7 @@ local M = {} --- Hex encode a string. --- --- @param str string String to encode ---- @return string Hex encoded string +--- @return string : Hex encoded string function M.hexencode(str) local bytes = { str:byte(1, #str) } local enc = {} ---@type string[] @@ -18,7 +18,7 @@ end --- Hex decode a string. --- --- @param enc string String to decode ---- @return string Decoded string +--- @return string : Decoded string function M.hexdecode(enc) assert(#enc % 2 == 0, 'string must have an even number of hex characters') local str = {} ---@type string[] diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index ec933f5194..3b5d8953c9 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -156,8 +156,10 @@ function LanguageTree:_set_logger() local lang = self:lang() - vim.fn.mkdir(vim.fn.stdpath('log'), 'p') - local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'treesitter.log') + local logdir = vim.fn.stdpath('log') --[[@as string]] + + vim.fn.mkdir(logdir, 'p') + local logfilename = vim.fs.joinpath(logdir, 'treesitter.log') local logfile, openerr = io.open(logfilename, 'a+') @@ -463,7 +465,7 @@ end --- Invokes the callback for each |LanguageTree| and its children recursively --- ---@param fn fun(tree: vim.treesitter.LanguageTree, lang: string) ----@param include_self boolean|nil Whether to include the invoking tree in the results +---@param include_self? boolean Whether to include the invoking tree in the results function LanguageTree:for_each_child(fn, include_self) vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11') if include_self then @@ -796,7 +798,7 @@ function LanguageTree:_get_injection(match, metadata) local combined = metadata['injection.combined'] ~= nil local injection_lang = metadata['injection.language'] --[[@as string?]] local lang = metadata['injection.self'] ~= nil and self:lang() - or metadata['injection.parent'] ~= nil and self._parent + or metadata['injection.parent'] ~= nil and self._parent:lang() or (injection_lang and resolve_lang(injection_lang)) local include_children = metadata['injection.include-children'] ~= nil @@ -1058,20 +1060,19 @@ function LanguageTree:_on_detach(...) end end ---- Registers callbacks for the |LanguageTree|. ----@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers: ---- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback. +--- Registers callbacks for the [LanguageTree]. +---@param cbs table An [nvim_buf_attach()]-like table argument with the following handlers: +--- - `on_bytes` : see [nvim_buf_attach()], but this will be called _after_ the parsers callback. --- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes. --- It will be passed two arguments: a table of the ranges (as node ranges) that --- changed and the changed tree. --- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_removed` : emitted when a child is removed from the tree. ---- - `on_detach` : emitted when the buffer is detached, see |nvim_buf_detach_event|. +--- - `on_detach` : emitted when the buffer is detached, see [nvim_buf_detach_event]. --- Takes one argument, the number of the buffer. --- @param recursive? boolean Apply callbacks recursively for all children. Any new children will --- also inherit the callbacks. function LanguageTree:register_cbs(cbs, recursive) - ---@cast cbs table if not cbs then return end @@ -1112,12 +1113,18 @@ function LanguageTree:contains(range) return false end +--- @class vim.treesitter.LanguageTree.tree_for_range.Opts +--- @inlinedoc +--- +--- Ignore injected languages +--- (default: `true`) +--- @field ignore_injections? boolean + --- Gets the tree that contains {range}. --- ---@param range Range4 `{ start_line, start_col, end_line, end_col }` ----@param opts table|nil Optional keyword arguments: ---- - ignore_injections boolean Ignore injected languages (default true) ----@return TSTree|nil +---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts +---@return TSTree? function LanguageTree:tree_for_range(range, opts) opts = opts or {} local ignore = vim.F.if_nil(opts.ignore_injections, true) @@ -1143,9 +1150,8 @@ end --- Gets the smallest named node that contains {range}. --- ---@param range Range4 `{ start_line, start_col, end_line, end_col }` ----@param opts table|nil Optional keyword arguments: ---- - ignore_injections boolean Ignore injected languages (default true) ----@return TSNode | nil Found node +---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts +---@return TSNode? function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) if tree then @@ -1156,7 +1162,7 @@ end --- Gets the appropriate language that contains {range}. --- ---@param range Range4 `{ start_line, start_col, end_line, end_col }` ----@return vim.treesitter.LanguageTree Managing {range} +---@return vim.treesitter.LanguageTree tree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do if child:contains(range) then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index a086f5e876..67b8c596b8 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -88,7 +88,7 @@ end --- ---@param lang string Language to get query for ---@param query_name string Name of the query to load (e.g., "highlights") ----@param is_included (boolean|nil) Internal parameter, most of the time left as `nil` +---@param is_included? boolean Internal parameter, most of the time left as `nil` ---@return string[] query_files List of files to load for given query and language function M.get_files(lang, query_name, is_included) local query_path = string.format('queries/%s/%s.scm', lang, query_name) @@ -211,7 +211,7 @@ end ---@param lang string Language to use for the query ---@param query_name string Name of the query (e.g. "highlights") --- ----@return vim.treesitter.Query|nil : Parsed query. `nil` if no query files are found. +---@return vim.treesitter.Query? : Parsed query. `nil` if no query files are found. M.get = vim.func._memoize('concat-2', function(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -242,9 +242,9 @@ end) ---@param lang string Language to use for the query ---@param query string Query in s-expr syntax --- ----@return vim.treesitter.Query Parsed query +---@return vim.treesitter.Query : Parsed query --- ----@see |vim.treesitter.query.get()| +---@see [vim.treesitter.query.get()] M.parse = vim.func._memoize('concat-2', function(lang, query) language.add(lang) @@ -618,20 +618,23 @@ local directive_handlers = { end, } +--- @class vim.treesitter.query.add_predicate.Opts +--- @inlinedoc +--- +--- Override an existing predicate of the same name +--- @field force? boolean +--- +--- Use the correct implementation of the match table where capture IDs map to +--- a list of nodes instead of a single node. Defaults to false (for backward +--- compatibility). This option will eventually become the default and removed. +--- @field all? boolean + --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param opts table Optional options: ---- - force (boolean): Override an existing ---- predicate of the same name ---- - all (boolean): Use the correct ---- implementation of the match table where ---- capture IDs map to a list of nodes instead ---- of a single node. Defaults to false (for ---- backward compatibility). This option will ---- eventually become the default and removed. +---@param opts vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) -- Backward compatibility: old signature had "force" as boolean argument if type(opts) == 'boolean' then @@ -669,20 +672,12 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table, pattern: integer, source: integer|string, predicate: any[], metadata: table) --- - match: A table mapping capture IDs to a list of captured nodes --- - pattern: the index of the matching pattern in the query file --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` ----@param opts table Optional options: ---- - force (boolean): Override an existing ---- predicate of the same name ---- - all (boolean): Use the correct ---- implementation of the match table where ---- capture IDs map to a list of nodes instead ---- of a single node. Defaults to false (for ---- backward compatibility). This option will ---- eventually become the default and removed. +---@param opts vim.treesitter.query.add_predicate.Opts function M.add_directive(name, handler, opts) -- Backward compatibility: old signature had "force" as boolean argument if type(opts) == 'boolean' then @@ -711,13 +706,13 @@ function M.add_directive(name, handler, opts) end --- Lists the currently available directives to use in queries. ----@return string[] List of supported directives. +---@return string[] : Supported directives. function M.list_directives() return vim.tbl_keys(directive_handlers) end --- Lists the currently available predicates to use in queries. ----@return string[] List of supported predicates. +---@return string[] : Supported predicates. function M.list_predicates() return vim.tbl_keys(predicate_handlers) end @@ -792,8 +787,8 @@ end --- Returns the start and stop value if set else the node's range. -- When the node's range is used, the stop is incremented by 1 -- to make the search inclusive. ----@param start integer|nil ----@param stop integer|nil +---@param start integer? +---@param stop integer? ---@param node TSNode ---@return integer, integer local function value_or_node_range(start, stop, node) -- cgit From 77a9f3395bd1e7184f4d735c01e50285e30477ab Mon Sep 17 00:00:00 2001 From: Takuya Tokuda Date: Mon, 18 Mar 2024 05:04:59 +0900 Subject: fix(lsp): create codelens request parameters for each buffer (#27699) --- runtime/lua/vim/lsp/codelens.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 48c096c0c1..d2557ca9d7 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -299,12 +299,12 @@ function M.refresh(opts) local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr) local buffers = bufnr and { bufnr } or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs()) - local params = { - textDocument = util.make_text_document_params(), - } for _, buf in ipairs(buffers) do if not active_refreshes[buf] then + local params = { + textDocument = util.make_text_document_params(buf), + } active_refreshes[buf] = true vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens) end -- cgit From 3b29b39e6deb212456eba691bc79b17edaa8717b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 17 Mar 2024 18:02:40 +0000 Subject: fix(treesitter): revert to using iter_captures in highlighter Fixes #27895 --- runtime/lua/vim/treesitter/highlighter.lua | 74 ++++++++++++------------------ runtime/lua/vim/treesitter/query.lua | 13 +++--- 2 files changed, 36 insertions(+), 51 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 7bc6e5c019..1e6f128461 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range') local ns = api.nvim_create_namespace('treesitter/highlighter') ----@alias vim.treesitter.highlighter.Iter fun(): integer, table, vim.treesitter.query.TSMetadata +---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table ---@class (private) vim.treesitter.highlighter.Query ---@field private _query vim.treesitter.Query? @@ -57,7 +57,6 @@ end ---@field next_row integer ---@field iter vim.treesitter.highlighter.Iter? ---@field highlighter_query vim.treesitter.highlighter.Query ----@field level integer Injection level ---@nodoc ---@class vim.treesitter.highlighter @@ -193,20 +192,12 @@ function TSHighlighter:prepare_highlight_states(srow, erow) return end - local level = 0 - local t = tree - while t do - t = t:parent() - level = level + 1 - end - -- _highlight_states should be a list so that the highlights are added in the same order as -- for_each_tree traversal. This ensures that parents' highlight don't override children's. table.insert(self._highlight_states, { tstree = tstree, next_row = 0, iter = nil, - level = level, highlighter_query = highlighter_query, }) end) @@ -296,9 +287,6 @@ end ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) self:for_each_highlight_state(function(state) - -- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark - -- so injections always appear over base highlights. - local pattern_offset = state.level * 1000 local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() @@ -308,23 +296,25 @@ local function on_line_impl(self, buf, line, is_spell_nav) end if state.iter == nil or state.next_row < line then - state.iter = state.highlighter_query - :query() - :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true }) + state.iter = + state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end while line >= state.next_row do - local pattern, match, metadata = state.iter() + local capture, node, metadata, match = state.iter(line) - if not match then - state.next_row = root_end_row + 1 + local range = { root_end_row + 1, 0, root_end_row + 1, 0 } + if node then + range = vim.treesitter.get_range(node, buf, metadata and metadata[capture]) end + local start_row, start_col, end_row, end_col = Range.unpack4(range) + + if capture then + local hl = state.highlighter_query:get_hl_from_capture(capture) - for capture, nodes in pairs(match or {}) do local capture_name = state.highlighter_query:query().captures[capture] - local spell, spell_pri_offset = get_spell(capture_name) - local hl = state.highlighter_query:get_hl_from_capture(capture) + local spell, spell_pri_offset = get_spell(capture_name) -- The "priority" attribute can be set at the pattern level or on a particular capture local priority = ( @@ -332,34 +322,28 @@ local function on_line_impl(self, buf, line, is_spell_nav) or vim.highlight.priorities.treesitter ) + spell_pri_offset - local url = get_url(match, buf, capture, metadata) - -- The "conceal" attribute can be set at the pattern level or on a particular capture local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal - for _, node in ipairs(nodes) do - local range = vim.treesitter.get_range(node, buf, metadata[capture]) - local start_row, start_col, end_row, end_col = Range.unpack4(range) - - if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then - api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { - end_line = end_row, - end_col = end_col, - hl_group = hl, - ephemeral = true, - priority = priority, - _subpriority = pattern_offset + pattern, - conceal = conceal, - spell = spell, - url = url, - }) - end - - if start_row > line then - state.next_row = start_row - end + local url = get_url(match, buf, capture, metadata) + + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then + api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { + end_line = end_row, + end_col = end_col, + hl_group = hl, + ephemeral = true, + priority = priority, + conceal = conceal, + spell = spell, + url = url, + }) end end + + if start_row > line then + state.next_row = start_row + end end end) end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 67b8c596b8..30cd00c617 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -811,12 +811,13 @@ end --- as the {node}, i.e., to get syntax highlight matches in the current --- viewport). When omitted, the {start} and {stop} row values are used from the given node. --- ---- The iterator returns three values: a numeric id identifying the capture, ---- the captured node, and metadata from any directives processing the match. +--- The iterator returns four values: a numeric id identifying the capture, +--- the captured node, metadata from any directives processing the match, +--- and the match itself. --- The following example shows how to get captures by name: --- --- ```lua ---- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do +--- for id, node, metadata, match in query:iter_captures(tree:root(), bufnr, first, last) do --- local name = query.captures[id] -- name of the capture in the query --- -- typically useful info about the node: --- local type = node:type() -- type of the captured node @@ -830,8 +831,8 @@ end ---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. --- ----@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata): ---- capture id, capture node, metadata +---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table): +--- capture id, capture node, metadata, match function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() @@ -856,7 +857,7 @@ function Query:iter_captures(node, source, start, stop) self:apply_directives(match, match.pattern, source, metadata) end - return capture, captured_node, metadata + return capture, captured_node, metadata, match end return iter end -- cgit From 5c9033024f067a283a9030eab204f8ca6d685101 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 18 Mar 2024 22:47:51 +0100 Subject: vim-patch:9.1.0187: filetype: no support for Dafny files (#27918) Problem: Dafny files are not recognized. Solution: Recognize *.dfy files as filetype "dafny" (zeertzjq). Ref: https://dafny.org/ Ref: https://github.com/mlr-msft/vim-loves-dafny closes: vim/vim#14226 https://github.com/vim/vim/commit/4e334d0443f28f4e749dbef38d686d0dd19122de Co-authored-by: zeertzjq --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index fba76f93b2..614aa428e1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -335,6 +335,7 @@ local extension = { si = 'cuplsim', cyn = 'cynpp', cypher = 'cypher', + dfy = 'dafny', dart = 'dart', drt = 'dart', ds = 'datascript', -- cgit From aca2048bcd57937ea1c7b7f0325f25d5b82588db Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 18 Mar 2024 23:19:01 +0000 Subject: refactor(treesitter): redesign query iterating Problem: `TSNode:_rawquery()` is complicated, has known issues and the Lua and C code is awkwardly coupled (see logic with `active`). Solution: - Add `TSQueryCursor` and `TSQueryMatch` bindings. - Replace `TSNode:_rawquery()` with `TSQueryCursor:next_capture()` and `TSQueryCursor:next_match()` - Do more stuff in Lua - API for `Query:iter_captures()` and `Query:iter_matches()` remains the same. - `treesitter.c` no longer contains any logic related to predicates. - Add `match_limit` option to `iter_matches()`. Default is still 256. --- runtime/lua/vim/treesitter/_meta.lua | 44 ++++++---- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/query.lua | 125 +++++++++++++++++---------- 3 files changed, 109 insertions(+), 62 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 19d97d2820..e2768d4b06 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -34,22 +34,6 @@ error('Cannot require a meta file') ---@field byte_length fun(self: TSNode): integer local TSNode = {} ----@param query TSQuery ----@param captures true ----@param start? integer ----@param end_? integer ----@param opts? table ----@return fun(): integer, TSNode, vim.treesitter.query.TSMatch -function TSNode:_rawquery(query, captures, start, end_, opts) end - ----@param query TSQuery ----@param captures false ----@param start? integer ----@param end_? integer ----@param opts? table ----@return fun(): integer, vim.treesitter.query.TSMatch -function TSNode:_rawquery(query, captures, start, end_, opts) end - ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) ---@class TSParser: userdata @@ -90,3 +74,31 @@ vim._ts_parse_query = function(lang, query) end ---@param lang string ---@return TSParser vim._create_ts_parser = function(lang) end + +--- @class TSQueryMatch: userdata +--- @field captures fun(self: TSQueryMatch): table +local TSQueryMatch = {} + +--- @return integer match_id +--- @return integer pattern_index +function TSQueryMatch:info() end + +--- @class TSQueryCursor: userdata +--- @field remove_match fun(self: TSQueryCursor, id: integer) +local TSQueryCursor = {} + +--- @return integer capture +--- @return TSNode captured_node +--- @return TSQueryMatch match +function TSQueryCursor:next_capture() end + +--- @return TSQueryMatch match +function TSQueryCursor:next_match() end + +--- @param node TSNode +--- @param query TSQuery +--- @param start integer? +--- @param stop integer? +--- @param opts? { max_start_depth?: integer, match_limit?: integer} +--- @return TSQueryCursor +function vim._create_ts_querycursor(node, query, start, stop, opts) end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 6216d4e891..12b4cbc7b9 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -122,7 +122,7 @@ local parse = vim.func._memoize(hash_parse, function(node, buf, lang) end) --- @param buf integer ---- @param match vim.treesitter.query.TSMatch +--- @param match table --- @param query vim.treesitter.Query --- @param lang_context QueryLinterLanguageContext --- @param diagnostics vim.Diagnostic[] diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 30cd00c617..075fd0e99b 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -258,7 +258,7 @@ end) --- handling the "any" vs "all" semantics. They are called from the --- predicate_handlers table with the appropriate arguments for each predicate. local impl = { - --- @param match vim.treesitter.query.TSMatch + --- @param match table --- @param source integer|string --- @param predicate any[] --- @param any boolean @@ -293,7 +293,7 @@ local impl = { return not any end, - --- @param match vim.treesitter.query.TSMatch + --- @param match table --- @param source integer|string --- @param predicate any[] --- @param any boolean @@ -333,7 +333,7 @@ local impl = { end, }) - --- @param match vim.treesitter.query.TSMatch + --- @param match table --- @param source integer|string --- @param predicate any[] --- @param any boolean @@ -356,7 +356,7 @@ local impl = { end end)(), - --- @param match vim.treesitter.query.TSMatch + --- @param match table --- @param source integer|string --- @param predicate any[] --- @param any boolean @@ -383,13 +383,7 @@ local impl = { end, } ----@nodoc ----@class vim.treesitter.query.TSMatch ----@field pattern? integer ----@field active? boolean ----@field [integer] TSNode[] - ----@alias TSPredicate fun(match: vim.treesitter.query.TSMatch, pattern: integer, source: integer|string, predicate: any[]): boolean +---@alias TSPredicate fun(match: table, pattern: integer, source: integer|string, predicate: any[]): boolean -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) @@ -504,7 +498,7 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?'] ---@field [integer] vim.treesitter.query.TSMetadata ---@field [string] integer|string ----@alias TSDirective fun(match: vim.treesitter.query.TSMatch, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata) +---@alias TSDirective fun(match: table, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata) -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) @@ -726,13 +720,19 @@ local function is_directive(name) end ---@private ----@param match vim.treesitter.query.TSMatch ----@param pattern integer +---@param match TSQueryMatch ---@param source integer|string -function Query:match_preds(match, pattern, source) +function Query:match_preds(match, source) + local _, pattern = match:info() local preds = self.info.patterns[pattern] - for _, pred in pairs(preds or {}) do + if not preds then + return true + end + + local captures = match:captures() + + for _, pred in pairs(preds) do -- Here we only want to return if a predicate DOES NOT match, and -- continue on the other case. This way unknown predicates will not be considered, -- which allows some testing and easier user extensibility (#12173). @@ -754,7 +754,7 @@ function Query:match_preds(match, pattern, source) return false end - local pred_matches = handler(match, pattern, source, pred) + local pred_matches = handler(captures, pattern, source, pred) if not xor(is_not, pred_matches) then return false @@ -765,23 +765,33 @@ function Query:match_preds(match, pattern, source) end ---@private ----@param match vim.treesitter.query.TSMatch ----@param metadata vim.treesitter.query.TSMetadata -function Query:apply_directives(match, pattern, source, metadata) +---@param match TSQueryMatch +---@return vim.treesitter.query.TSMetadata metadata +function Query:apply_directives(match, source) + ---@type vim.treesitter.query.TSMetadata + local metadata = {} + local _, pattern = match:info() local preds = self.info.patterns[pattern] - for _, pred in pairs(preds or {}) do + if not preds then + return metadata + end + + local captures = match:captures() + + for _, pred in pairs(preds) do if is_directive(pred[1]) then local handler = directive_handlers[pred[1]] if not handler then error(string.format('No handler for %s', pred[1])) - return end - handler(match, pattern, source, pred, metadata) + handler(captures, pattern, source, pred, metadata) end end + + return metadata end --- Returns the start and stop value if set else the node's range. @@ -831,8 +841,10 @@ end ---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. --- ----@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table): +---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table?): --- capture id, capture node, metadata, match +--- +---@note Captures are only returned if the query pattern of a specific capture contained predicates. function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() @@ -840,24 +852,38 @@ function Query:iter_captures(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) - local raw_iter = node:_rawquery(self.query, true, start, stop) ---@type fun(): integer, TSNode, vim.treesitter.query.TSMatch + local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) + + local max_match_id = -1 + local function iter(end_line) - local capture, captured_node, match = raw_iter() + local capture, captured_node, match = cursor:next_capture() + + if not capture then + return + end + + local captures --- @type table? + local match_id, pattern_index = match:info() + local metadata = {} - if match ~= nil then - local active = self:match_preds(match, match.pattern, source) - match.active = active - if not active then + local preds = self.info.patterns[pattern_index] or {} + + if #preds > 0 and match_id > max_match_id then + captures = match:captures() + max_match_id = match_id + if not self:match_preds(match, source) then + cursor:remove_match(match_id) if end_line and captured_node:range() > end_line then return nil, captured_node, nil end return iter(end_line) -- tail call: try next match end - self:apply_directives(match, match.pattern, source, metadata) + metadata = self:apply_directives(match, source) end - return capture, captured_node, metadata, match + return capture, captured_node, metadata, captures end return iter end @@ -899,45 +925,54 @@ end ---@param opts? table Optional keyword arguments: --- - max_start_depth (integer) if non-zero, sets the maximum start depth --- for each match. This is used to prevent traversing too deep into a tree. +--- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256). --- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes. --- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is --- incorrect behavior. This option will eventually become the default and removed. --- ---@return (fun(): integer, table, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop, opts) - local all = opts and opts.all + opts = opts or {} + opts.match_limit = opts.match_limit or 256 + if type(source) == 'number' and source == 0 then source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) - local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@type fun(): integer, vim.treesitter.query.TSMatch + local cursor = vim._create_ts_querycursor(node, self.query, start, stop, opts) + local function iter() - local pattern, match = raw_iter() - local metadata = {} + local match = cursor:next_match() - if match ~= nil then - local active = self:match_preds(match, pattern, source) - if not active then - return iter() -- tail call: try next match - end + if not match then + return + end - self:apply_directives(match, pattern, source, metadata) + local match_id, pattern = match:info() + + if not self:match_preds(match, source) then + cursor:remove_match(match_id) + return iter() -- tail call: try next match end - if not all then + local metadata = self:apply_directives(match, source) + + local captures = match:captures() + + if not opts.all then -- Convert the match table into the old buggy version for backward -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all" -- option! local old_match = {} ---@type table - for k, v in pairs(match or {}) do + for k, v in pairs(captures or {}) do old_match[k] = v[#v] end return pattern, old_match, metadata end - return pattern, match, metadata + return pattern, captures, metadata end return iter end -- cgit From 4694ce68774dd63a63e39009b4a887ac08636987 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 19 Mar 2024 22:31:44 +0100 Subject: vim-patch:9.1.0188: filetype: no support for Vento files (#27935) Problem: Vento files are not recognized. Solution: Recognize *.vto files as filetype "vento" (wrapperup) Vento is a templating engine https://vento.js.org/ closes: vim/vim#14229 https://github.com/vim/vim/commit/9f26e5a9bcedb3caef26e9d77849ea37a3626bbf Co-authored-by: wrapperup --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 614aa428e1..af5636a32d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1063,6 +1063,7 @@ local extension = { vdmrt = 'vdmrt', vdmsl = 'vdmsl', vdm = 'vdmsl', + vto = 'vento', vr = 'vera', vri = 'vera', vrh = 'vera', -- cgit From 849d82b80b8584fe7918bffc6480f0ec0fda9b9b Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 23 Mar 2024 01:46:01 +0900 Subject: fix(lsp): handle stale bufnr on LspRequest autocmd trigger (#27981) continuation of https://github.com/neovim/neovim/pull/24013 --- runtime/lua/vim/lsp/client.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index d48be131f3..b06fab7319 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -693,7 +693,7 @@ function Client:_request(method, params, handler, bufnr) local request = { type = 'pending', bufnr = bufnr, method = method } self.requests[request_id] = request api.nvim_exec_autocmds('LspRequest', { - buffer = bufnr, + buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil, modeline = false, data = { client_id = self.id, request_id = request_id, request = request }, }) @@ -804,7 +804,7 @@ function Client:_cancel_request(id) if request and request.type == 'pending' then request.type = 'cancel' api.nvim_exec_autocmds('LspRequest', { - buffer = request.bufnr, + buffer = api.nvim_buf_is_valid(request.bufnr) and request.bufnr or nil, modeline = false, data = { client_id = self.id, request_id = id, request = request }, }) -- cgit From 881f5e59173a4f1b9a4cb16e425709e40d79d0e9 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 23 Mar 2024 16:33:53 +0800 Subject: vim-patch:9.1.0199: Not enough tests for the slice() function (#27991) Problem: Not enough tests for the slice() function. Solution: Test with multibyte chars, and in both Legacy and Vim9 script. Update docs to be clearer about how it treats composing chars. (zeertzjq) closes: vim/vim#14275 https://github.com/vim/vim/commit/ad38769030b5fa86aa0e8f1f0b4266690dfad4c9 --- runtime/lua/vim/_meta/vimfn.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index da251f89ad..caddd4dde2 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -8800,7 +8800,8 @@ function vim.fn.sinh(expr) end --- Similar to using a |slice| "expr[start : end]", but "end" is --- used exclusive. And for a string the indexes are used as --- character indexes instead of byte indexes. ---- Also, composing characters are not counted. +--- Also, composing characters are treated as a part of the +--- preceding base character. --- When {end} is omitted the slice continues to the last item. --- When {end} is -1 the last item is omitted. --- Returns an empty value if {start} or {end} are invalid. @@ -9208,8 +9209,8 @@ function vim.fn.strcharlen(string) end --- of byte index and length. --- When {skipcc} is omitted or zero, composing characters are --- counted separately. ---- When {skipcc} set to 1, Composing characters are ignored, ---- similar to |slice()|. +--- When {skipcc} set to 1, composing characters are treated as a +--- part of the preceding base character, similar to |slice()|. --- When a character index is used where a character does not --- exist it is omitted and counted as one character. For --- example: >vim @@ -9229,7 +9230,7 @@ function vim.fn.strcharpart(src, start, len, skipcc) end --- in String {string}. --- When {skipcc} is omitted or zero, composing characters are --- counted separately. ---- When {skipcc} set to 1, Composing characters are ignored. +--- When {skipcc} set to 1, composing characters are ignored. --- |strcharlen()| always does this. --- --- Returns zero on error. -- cgit From ca6dbf3558cec83f1d42a1e5f670c40c5893554e Mon Sep 17 00:00:00 2001 From: Calvin Bochulak Date: Sat, 23 Mar 2024 15:46:54 -0600 Subject: fix(vim.iter): use correct cmp function when truncating tail in `take` (#27998) --- runtime/lua/vim/iter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index a37b7f7858..2f2c21aee8 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -709,7 +709,8 @@ end ---@private function ListIter:take(n) local inc = self._head < self._tail and 1 or -1 - self._tail = math.min(self._tail, self._head + n * inc) + local cmp = self._head < self._tail and math.min or math.max + self._tail = cmp(self._tail, self._head + n * inc) return self end -- cgit From 3f238b39cfdf27657b2d9452c6ffd28f8209c95f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 21 Mar 2024 15:15:20 +0000 Subject: refactor(lsp): simplify client tracking - Remove: - uninitialized_clients - active_clients - all_buffer_active_clients - Add: - all_clients - Use `lsp.get_clients()` to get buffer clients. --- runtime/lua/vim/lsp.lua | 338 ++++++++++++++++++----------------------- runtime/lua/vim/lsp/client.lua | 2 +- 2 files changed, 152 insertions(+), 188 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index d5c376ba44..b302e1543a 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,7 +1,5 @@ local api = vim.api -local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local validate = vim.validate -local if_nil = vim.F.if_nil local lsp = vim._defer_require('vim.lsp', { _changetracking = ..., --- @module 'vim.lsp._changetracking' @@ -108,40 +106,7 @@ function lsp._buf_get_line_ending(bufnr) end -- Tracks all clients created via lsp.start_client -local active_clients = {} --- @type table -local all_buffer_active_clients = {} --- @type table> -local uninitialized_clients = {} --- @type table - ----@param bufnr? integer ----@param fn fun(client: vim.lsp.Client, client_id: integer, bufnr: integer) -local function for_each_buffer_client(bufnr, fn, restrict_client_ids) - validate({ - fn = { fn, 'f' }, - restrict_client_ids = { restrict_client_ids, 't', true }, - }) - bufnr = resolve_bufnr(bufnr) - local client_ids = all_buffer_active_clients[bufnr] - if not client_ids or tbl_isempty(client_ids) then - return - end - - if restrict_client_ids and #restrict_client_ids > 0 then - local filtered_client_ids = {} --- @type table - for client_id in pairs(client_ids) do - if vim.list_contains(restrict_client_ids, client_id) then - filtered_client_ids[client_id] = true - end - end - client_ids = filtered_client_ids - end - - for client_id in pairs(client_ids) do - local client = active_clients[client_id] - if client then - fn(client, client_id, bufnr) - end - end -end +local all_clients = {} --- @type table local client_errors_base = table.maxn(lsp.rpc.client_errors) local client_errors_offset = 0 @@ -156,7 +121,7 @@ end --- Can be used to look up the string from a the number or the number --- from the string. --- @nodoc -lsp.client_errors = tbl_extend( +lsp.client_errors = vim.tbl_extend( 'error', lsp.rpc.client_errors, client_error('BEFORE_INIT_CALLBACK_ERROR'), @@ -258,12 +223,10 @@ function lsp.start(config, opts) local bufnr = resolve_bufnr(opts.bufnr) - for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do - for _, client in pairs(clients) do - if reuse_client(client, config) then - lsp.buf_attach_client(bufnr, client.id) - return client.id - end + for _, client in pairs(all_clients) do + if reuse_client(client, config) then + lsp.buf_attach_client(bufnr, client.id) + return client.id end end @@ -385,17 +348,11 @@ end --- @param client vim.lsp.Client local function on_client_init(client) - local id = client.id - uninitialized_clients[id] = nil - -- Only assign after initialized. - active_clients[id] = client -- If we had been registered before we start, then send didOpen This can -- happen if we attach to buffers before initialize finishes or if -- someone restarts a client. - for bufnr, client_ids in pairs(all_buffer_active_clients) do - if client_ids[id] then - client.on_attach(bufnr) - end + for bufnr in pairs(client.attached_buffers) do + client:_on_attach(bufnr) end end @@ -403,28 +360,26 @@ end --- @param signal integer --- @param client_id integer local function on_client_exit(code, signal, client_id) - local client = active_clients[client_id] or uninitialized_clients[client_id] - - for bufnr, client_ids in pairs(all_buffer_active_clients) do - if client_ids[client_id] then - vim.schedule(function() - if client and client.attached_buffers[bufnr] then - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) - end + local client = all_clients[client_id] + + for bufnr in pairs(client.attached_buffers) do + vim.schedule(function() + if client and client.attached_buffers[bufnr] then + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id }, + }) + end - local namespace = vim.lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace, bufnr) + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace, bufnr) + client.attached_buffers[bufnr] = nil - client_ids[client_id] = nil - if vim.tbl_isempty(client_ids) then - reset_defaults(bufnr) - end - end) - end + if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then + reset_defaults(bufnr) + end + end) end local name = client.name or 'unknown' @@ -432,8 +387,7 @@ local function on_client_exit(code, signal, client_id) -- Schedule the deletion of the client object so that it exists in the execution of LspDetach -- autocommands vim.schedule(function() - active_clients[client_id] = nil - uninitialized_clients[client_id] = nil + all_clients[client_id] = nil -- Client can be absent if executable starts, but initialize fails -- init/attach won't have happened @@ -466,12 +420,12 @@ function lsp.start_client(config) end --- @diagnostic disable-next-line: invisible - table.insert(client._on_init_cbs, on_client_init) + table.insert(client._on_init_cbs, 1, on_client_init) + --- @diagnostic disable-next-line: invisible table.insert(client._on_exit_cbs, on_client_exit) - -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. - uninitialized_clients[client.id] = client + all_clients[client.id] = client client:initialize() @@ -495,7 +449,7 @@ local function text_document_did_change_handler( new_lastline ) -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached - if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then + if #lsp.get_clients({ bufnr = bufnr }) == 0 then return true end util.buf_versions[bufnr] = changedtick @@ -543,6 +497,80 @@ local function text_document_did_save_handler(bufnr) end end +--- @param bufnr integer +--- @param client_id integer +local function buf_attach(bufnr, client_id) + local uri = vim.uri_from_bufnr(bufnr) + local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) + local group = api.nvim_create_augroup(augroup, { clear = true }) + api.nvim_create_autocmd('BufWritePre', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/willSave', + callback = function(ctx) + for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do + local params = { + textDocument = { + uri = uri, + }, + reason = protocol.TextDocumentSaveReason.Manual, + } + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then + client.notify(ms.textDocument_willSave, params) + end + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then + local result, err = + client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) + if result and result.result then + util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) + elseif err then + log.error(vim.inspect(err)) + end + end + end + end, + }) + api.nvim_create_autocmd('BufWritePost', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/didSave handler', + callback = function(ctx) + text_document_did_save_handler(ctx.buf) + end, + }) + -- First time, so attach and set up stuff. + api.nvim_buf_attach(bufnr, false, { + on_lines = text_document_did_change_handler, + on_reload = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + client:_text_document_did_open_handler(bufnr) + end + end, + on_detach = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + end + for _, client in ipairs(all_clients) do + client.attached_buffers[bufnr] = nil + end + util.buf_versions[bufnr] = nil + end, + -- TODO if we know all of the potential clients ahead of time, then we + -- could conditionally set this. + -- utf_sizes = size_index > 1; + utf_sizes = true, + }) +end + --- Implements the `textDocument/did…` notifications required to track a buffer --- for any language server. --- @@ -561,92 +589,26 @@ function lsp.buf_attach_client(bufnr, client_id) log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) return false end - local buffer_client_ids = all_buffer_active_clients[bufnr] -- This is our first time attaching to this buffer. - if not buffer_client_ids then - buffer_client_ids = {} - all_buffer_active_clients[bufnr] = buffer_client_ids + if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then + buf_attach(bufnr, client_id) + end - local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) - local group = api.nvim_create_augroup(augroup, { clear = true }) - api.nvim_create_autocmd('BufWritePre', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/willSave', - callback = function(ctx) - for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do - local params = { - textDocument = { - uri = uri, - }, - reason = protocol.TextDocumentSaveReason.Manual, - } - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then - client.notify(ms.textDocument_willSave, params) - end - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then - local result, err = - client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) - if result and result.result then - util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) - elseif err then - log.error(vim.inspect(err)) - end - end - end - end, - }) - api.nvim_create_autocmd('BufWritePost', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/didSave handler', - callback = function(ctx) - text_document_did_save_handler(ctx.buf) - end, - }) - -- First time, so attach and set up stuff. - api.nvim_buf_attach(bufnr, false, { - on_lines = text_document_did_change_handler, - on_reload = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - client:_text_document_did_open_handler(bufnr) - end - end, - on_detach = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - client.attached_buffers[bufnr] = nil - end - util.buf_versions[bufnr] = nil - all_buffer_active_clients[bufnr] = nil - end, - -- TODO if we know all of the potential clients ahead of time, then we - -- could conditionally set this. - -- utf_sizes = size_index > 1; - utf_sizes = true, - }) + local client = lsp.get_client_by_id(client_id) + if not client then + return false end - if buffer_client_ids[client_id] then + if client.attached_buffers[bufnr] then return true end - -- This is our first time attaching this client to this buffer. - buffer_client_ids[client_id] = true - local client = active_clients[client_id] + client.attached_buffers[bufnr] = true + + -- This is our first time attaching this client to this buffer. -- Send didOpen for the client if it is initialized. If it isn't initialized -- then it will send didOpen on initialize. - if client then + if client.initialized then client:_on_attach(bufnr) end return true @@ -665,7 +627,7 @@ function lsp.buf_detach_client(bufnr, client_id) }) bufnr = resolve_bufnr(bufnr) - local client = lsp.get_client_by_id(client_id) + local client = all_clients[client_id] if not client or not client.attached_buffers[bufnr] then vim.notify( string.format( @@ -694,11 +656,6 @@ function lsp.buf_detach_client(bufnr, client_id) client.attached_buffers[bufnr] = nil util.buf_versions[bufnr] = nil - all_buffer_active_clients[bufnr][client_id] = nil - if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then - all_buffer_active_clients[bufnr] = nil - end - local namespace = lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) end @@ -708,7 +665,7 @@ end ---@param bufnr (integer) Buffer handle, or 0 for current ---@param client_id (integer) the client id function lsp.buf_is_attached(bufnr, client_id) - return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true + return lsp.get_clients({ bufnr = bufnr, id = client_id, _uninitialized = true })[1] ~= nil end --- Gets a client by id, or nil if the id is invalid. @@ -718,7 +675,7 @@ end --- ---@return (nil|vim.lsp.Client) client rpc object function lsp.get_client_by_id(client_id) - return active_clients[client_id] or uninitialized_clients[client_id] + return all_clients[client_id] end --- Returns list of buffers attached to client_id. @@ -726,7 +683,7 @@ end ---@param client_id integer client id ---@return integer[] buffers list of buffer ids function lsp.get_buffers_by_client_id(client_id) - local client = lsp.get_client_by_id(client_id) + local client = all_clients[client_id] return client and vim.tbl_keys(client.attached_buffers) or {} end @@ -742,17 +699,22 @@ end --- By default asks the server to shutdown, unless stop was requested --- already for this client, then force-shutdown is attempted. --- ----@param client_id integer|vim.lsp.Client id or |vim.lsp.Client| object, or list thereof ----@param force boolean|nil shutdown forcefully +---@param client_id integer|integer[]|vim.lsp.Client[] id, list of id's, or list of |vim.lsp.Client| objects +---@param force? boolean shutdown forcefully function lsp.stop_client(client_id, force) + --- @type integer[]|vim.lsp.Client[] local ids = type(client_id) == 'table' and client_id or { client_id } for _, id in ipairs(ids) do - if type(id) == 'table' and id.stop ~= nil then - id.stop(force) - elseif active_clients[id] then - active_clients[id].stop(force) - elseif uninitialized_clients[id] then - uninitialized_clients[id].stop(true) + if type(id) == 'table' then + if id.stop then + id.stop(force) + end + else + --- @cast id -vim.lsp.Client + local client = all_clients[id] + if client then + client.stop(force) + end end end end @@ -772,6 +734,9 @@ end --- --- Only return clients supporting the given method --- @field method? string +--- +--- Also return uninitialized clients. +--- @field package _uninitialized? boolean --- Get active clients. --- @@ -784,15 +749,16 @@ function lsp.get_clients(filter) local clients = {} --- @type vim.lsp.Client[] - local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {}) - or active_clients - for client_id in pairs(t) do - local client = active_clients[client_id] + local bufnr = filter.bufnr and resolve_bufnr(filter.bufnr) + + for _, client in pairs(all_clients) do if client and (filter.id == nil or client.id == filter.id) + and (filter.bufnr == nil or client.attached_buffers[bufnr]) and (filter.name == nil or client.name == filter.name) and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr })) + and (filter._uninitialized or client.initialized) then clients[#clients + 1] = client end @@ -810,15 +776,9 @@ end api.nvim_create_autocmd('VimLeavePre', { desc = 'vim.lsp: exit handler', callback = function() + local active_clients = lsp.get_clients() log.info('exit_handler', active_clients) - for _, client in pairs(uninitialized_clients) do - client.stop(true) - end - -- TODO handle v:dying differently? - if tbl_isempty(active_clients) then - return - end - for _, client in pairs(active_clients) do + for _, client in pairs(all_clients) do client.stop() end @@ -827,7 +787,7 @@ api.nvim_create_autocmd('VimLeavePre', { local send_kill = false for client_id, client in pairs(active_clients) do - local timeout = if_nil(client.flags.exit_timeout, false) + local timeout = client.flags.exit_timeout if timeout then send_kill = true timeouts[client_id] = timeout @@ -910,7 +870,7 @@ function lsp.buf_request(bufnr, method, params, handler) local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do - local client = active_clients[client_id] + local client = all_clients[client_id] client.cancel_request(request_id) end end @@ -1106,7 +1066,7 @@ end ---@return boolean stopped true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) assert(client_id, 'missing client_id param') - return active_clients[client_id] == nil and not uninitialized_clients[client_id] + return not all_clients[client_id] end --- Gets a map of client_id:client pairs for the given buffer, where each value @@ -1172,7 +1132,11 @@ function lsp.for_each_buffer_client(bufnr, fn) 'lsp.get_clients({ bufnr = bufnr }) with regular loop', '0.12' ) - return for_each_buffer_client(bufnr, fn) + bufnr = resolve_bufnr(bufnr) + + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do + fn(client, client.id, bufnr) + end end --- Function to manage overriding defaults for LSP handlers. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index b06fab7319..758d68e4b0 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -761,7 +761,7 @@ function Client:_request_sync(method, params, timeout_ms, bufnr) return request_result end ---- @private +--- @package --- Sends a notification to an LSP server. --- --- @param method string LSP method name. -- cgit From 934f38682afd5925df675485b96ac9a2d3b8dd57 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 25 Mar 2024 20:16:42 +0000 Subject: Revert "refactor(lsp): simplify client tracking" This reverts commit 3f238b39cfdf27657b2d9452c6ffd28f8209c95f. --- runtime/lua/vim/lsp.lua | 338 +++++++++++++++++++++++------------------ runtime/lua/vim/lsp/client.lua | 2 +- 2 files changed, 188 insertions(+), 152 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b302e1543a..d5c376ba44 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,5 +1,7 @@ local api = vim.api +local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local validate = vim.validate +local if_nil = vim.F.if_nil local lsp = vim._defer_require('vim.lsp', { _changetracking = ..., --- @module 'vim.lsp._changetracking' @@ -106,7 +108,40 @@ function lsp._buf_get_line_ending(bufnr) end -- Tracks all clients created via lsp.start_client -local all_clients = {} --- @type table +local active_clients = {} --- @type table +local all_buffer_active_clients = {} --- @type table> +local uninitialized_clients = {} --- @type table + +---@param bufnr? integer +---@param fn fun(client: vim.lsp.Client, client_id: integer, bufnr: integer) +local function for_each_buffer_client(bufnr, fn, restrict_client_ids) + validate({ + fn = { fn, 'f' }, + restrict_client_ids = { restrict_client_ids, 't', true }, + }) + bufnr = resolve_bufnr(bufnr) + local client_ids = all_buffer_active_clients[bufnr] + if not client_ids or tbl_isempty(client_ids) then + return + end + + if restrict_client_ids and #restrict_client_ids > 0 then + local filtered_client_ids = {} --- @type table + for client_id in pairs(client_ids) do + if vim.list_contains(restrict_client_ids, client_id) then + filtered_client_ids[client_id] = true + end + end + client_ids = filtered_client_ids + end + + for client_id in pairs(client_ids) do + local client = active_clients[client_id] + if client then + fn(client, client_id, bufnr) + end + end +end local client_errors_base = table.maxn(lsp.rpc.client_errors) local client_errors_offset = 0 @@ -121,7 +156,7 @@ end --- Can be used to look up the string from a the number or the number --- from the string. --- @nodoc -lsp.client_errors = vim.tbl_extend( +lsp.client_errors = tbl_extend( 'error', lsp.rpc.client_errors, client_error('BEFORE_INIT_CALLBACK_ERROR'), @@ -223,10 +258,12 @@ function lsp.start(config, opts) local bufnr = resolve_bufnr(opts.bufnr) - for _, client in pairs(all_clients) do - if reuse_client(client, config) then - lsp.buf_attach_client(bufnr, client.id) - return client.id + for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do + for _, client in pairs(clients) do + if reuse_client(client, config) then + lsp.buf_attach_client(bufnr, client.id) + return client.id + end end end @@ -348,11 +385,17 @@ end --- @param client vim.lsp.Client local function on_client_init(client) + local id = client.id + uninitialized_clients[id] = nil + -- Only assign after initialized. + active_clients[id] = client -- If we had been registered before we start, then send didOpen This can -- happen if we attach to buffers before initialize finishes or if -- someone restarts a client. - for bufnr in pairs(client.attached_buffers) do - client:_on_attach(bufnr) + for bufnr, client_ids in pairs(all_buffer_active_clients) do + if client_ids[id] then + client.on_attach(bufnr) + end end end @@ -360,26 +403,28 @@ end --- @param signal integer --- @param client_id integer local function on_client_exit(code, signal, client_id) - local client = all_clients[client_id] - - for bufnr in pairs(client.attached_buffers) do - vim.schedule(function() - if client and client.attached_buffers[bufnr] then - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) - end + local client = active_clients[client_id] or uninitialized_clients[client_id] + + for bufnr, client_ids in pairs(all_buffer_active_clients) do + if client_ids[client_id] then + vim.schedule(function() + if client and client.attached_buffers[bufnr] then + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id }, + }) + end - local namespace = vim.lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace, bufnr) - client.attached_buffers[bufnr] = nil + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace, bufnr) - if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then - reset_defaults(bufnr) - end - end) + client_ids[client_id] = nil + if vim.tbl_isempty(client_ids) then + reset_defaults(bufnr) + end + end) + end end local name = client.name or 'unknown' @@ -387,7 +432,8 @@ local function on_client_exit(code, signal, client_id) -- Schedule the deletion of the client object so that it exists in the execution of LspDetach -- autocommands vim.schedule(function() - all_clients[client_id] = nil + active_clients[client_id] = nil + uninitialized_clients[client_id] = nil -- Client can be absent if executable starts, but initialize fails -- init/attach won't have happened @@ -420,12 +466,12 @@ function lsp.start_client(config) end --- @diagnostic disable-next-line: invisible - table.insert(client._on_init_cbs, 1, on_client_init) - + table.insert(client._on_init_cbs, on_client_init) --- @diagnostic disable-next-line: invisible table.insert(client._on_exit_cbs, on_client_exit) - all_clients[client.id] = client + -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. + uninitialized_clients[client.id] = client client:initialize() @@ -449,7 +495,7 @@ local function text_document_did_change_handler( new_lastline ) -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached - if #lsp.get_clients({ bufnr = bufnr }) == 0 then + if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then return true end util.buf_versions[bufnr] = changedtick @@ -497,80 +543,6 @@ local function text_document_did_save_handler(bufnr) end end ---- @param bufnr integer ---- @param client_id integer -local function buf_attach(bufnr, client_id) - local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) - local group = api.nvim_create_augroup(augroup, { clear = true }) - api.nvim_create_autocmd('BufWritePre', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/willSave', - callback = function(ctx) - for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do - local params = { - textDocument = { - uri = uri, - }, - reason = protocol.TextDocumentSaveReason.Manual, - } - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then - client.notify(ms.textDocument_willSave, params) - end - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then - local result, err = - client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) - if result and result.result then - util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) - elseif err then - log.error(vim.inspect(err)) - end - end - end - end, - }) - api.nvim_create_autocmd('BufWritePost', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/didSave handler', - callback = function(ctx) - text_document_did_save_handler(ctx.buf) - end, - }) - -- First time, so attach and set up stuff. - api.nvim_buf_attach(bufnr, false, { - on_lines = text_document_did_change_handler, - on_reload = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - client:_text_document_did_open_handler(bufnr) - end - end, - on_detach = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - end - for _, client in ipairs(all_clients) do - client.attached_buffers[bufnr] = nil - end - util.buf_versions[bufnr] = nil - end, - -- TODO if we know all of the potential clients ahead of time, then we - -- could conditionally set this. - -- utf_sizes = size_index > 1; - utf_sizes = true, - }) -end - --- Implements the `textDocument/did…` notifications required to track a buffer --- for any language server. --- @@ -589,26 +561,92 @@ function lsp.buf_attach_client(bufnr, client_id) log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) return false end + local buffer_client_ids = all_buffer_active_clients[bufnr] -- This is our first time attaching to this buffer. - if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then - buf_attach(bufnr, client_id) - end + if not buffer_client_ids then + buffer_client_ids = {} + all_buffer_active_clients[bufnr] = buffer_client_ids - local client = lsp.get_client_by_id(client_id) - if not client then - return false + local uri = vim.uri_from_bufnr(bufnr) + local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) + local group = api.nvim_create_augroup(augroup, { clear = true }) + api.nvim_create_autocmd('BufWritePre', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/willSave', + callback = function(ctx) + for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do + local params = { + textDocument = { + uri = uri, + }, + reason = protocol.TextDocumentSaveReason.Manual, + } + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then + client.notify(ms.textDocument_willSave, params) + end + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then + local result, err = + client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) + if result and result.result then + util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) + elseif err then + log.error(vim.inspect(err)) + end + end + end + end, + }) + api.nvim_create_autocmd('BufWritePost', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/didSave handler', + callback = function(ctx) + text_document_did_save_handler(ctx.buf) + end, + }) + -- First time, so attach and set up stuff. + api.nvim_buf_attach(bufnr, false, { + on_lines = text_document_did_change_handler, + on_reload = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + client:_text_document_did_open_handler(bufnr) + end + end, + on_detach = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + client.attached_buffers[bufnr] = nil + end + util.buf_versions[bufnr] = nil + all_buffer_active_clients[bufnr] = nil + end, + -- TODO if we know all of the potential clients ahead of time, then we + -- could conditionally set this. + -- utf_sizes = size_index > 1; + utf_sizes = true, + }) end - if client.attached_buffers[bufnr] then + if buffer_client_ids[client_id] then return true end - - client.attached_buffers[bufnr] = true - -- This is our first time attaching this client to this buffer. + buffer_client_ids[client_id] = true + + local client = active_clients[client_id] -- Send didOpen for the client if it is initialized. If it isn't initialized -- then it will send didOpen on initialize. - if client.initialized then + if client then client:_on_attach(bufnr) end return true @@ -627,7 +665,7 @@ function lsp.buf_detach_client(bufnr, client_id) }) bufnr = resolve_bufnr(bufnr) - local client = all_clients[client_id] + local client = lsp.get_client_by_id(client_id) if not client or not client.attached_buffers[bufnr] then vim.notify( string.format( @@ -656,6 +694,11 @@ function lsp.buf_detach_client(bufnr, client_id) client.attached_buffers[bufnr] = nil util.buf_versions[bufnr] = nil + all_buffer_active_clients[bufnr][client_id] = nil + if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then + all_buffer_active_clients[bufnr] = nil + end + local namespace = lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) end @@ -665,7 +708,7 @@ end ---@param bufnr (integer) Buffer handle, or 0 for current ---@param client_id (integer) the client id function lsp.buf_is_attached(bufnr, client_id) - return lsp.get_clients({ bufnr = bufnr, id = client_id, _uninitialized = true })[1] ~= nil + return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true end --- Gets a client by id, or nil if the id is invalid. @@ -675,7 +718,7 @@ end --- ---@return (nil|vim.lsp.Client) client rpc object function lsp.get_client_by_id(client_id) - return all_clients[client_id] + return active_clients[client_id] or uninitialized_clients[client_id] end --- Returns list of buffers attached to client_id. @@ -683,7 +726,7 @@ end ---@param client_id integer client id ---@return integer[] buffers list of buffer ids function lsp.get_buffers_by_client_id(client_id) - local client = all_clients[client_id] + local client = lsp.get_client_by_id(client_id) return client and vim.tbl_keys(client.attached_buffers) or {} end @@ -699,22 +742,17 @@ end --- By default asks the server to shutdown, unless stop was requested --- already for this client, then force-shutdown is attempted. --- ----@param client_id integer|integer[]|vim.lsp.Client[] id, list of id's, or list of |vim.lsp.Client| objects ----@param force? boolean shutdown forcefully +---@param client_id integer|vim.lsp.Client id or |vim.lsp.Client| object, or list thereof +---@param force boolean|nil shutdown forcefully function lsp.stop_client(client_id, force) - --- @type integer[]|vim.lsp.Client[] local ids = type(client_id) == 'table' and client_id or { client_id } for _, id in ipairs(ids) do - if type(id) == 'table' then - if id.stop then - id.stop(force) - end - else - --- @cast id -vim.lsp.Client - local client = all_clients[id] - if client then - client.stop(force) - end + if type(id) == 'table' and id.stop ~= nil then + id.stop(force) + elseif active_clients[id] then + active_clients[id].stop(force) + elseif uninitialized_clients[id] then + uninitialized_clients[id].stop(true) end end end @@ -734,9 +772,6 @@ end --- --- Only return clients supporting the given method --- @field method? string ---- ---- Also return uninitialized clients. ---- @field package _uninitialized? boolean --- Get active clients. --- @@ -749,16 +784,15 @@ function lsp.get_clients(filter) local clients = {} --- @type vim.lsp.Client[] - local bufnr = filter.bufnr and resolve_bufnr(filter.bufnr) - - for _, client in pairs(all_clients) do + local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {}) + or active_clients + for client_id in pairs(t) do + local client = active_clients[client_id] if client and (filter.id == nil or client.id == filter.id) - and (filter.bufnr == nil or client.attached_buffers[bufnr]) and (filter.name == nil or client.name == filter.name) and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr })) - and (filter._uninitialized or client.initialized) then clients[#clients + 1] = client end @@ -776,9 +810,15 @@ end api.nvim_create_autocmd('VimLeavePre', { desc = 'vim.lsp: exit handler', callback = function() - local active_clients = lsp.get_clients() log.info('exit_handler', active_clients) - for _, client in pairs(all_clients) do + for _, client in pairs(uninitialized_clients) do + client.stop(true) + end + -- TODO handle v:dying differently? + if tbl_isempty(active_clients) then + return + end + for _, client in pairs(active_clients) do client.stop() end @@ -787,7 +827,7 @@ api.nvim_create_autocmd('VimLeavePre', { local send_kill = false for client_id, client in pairs(active_clients) do - local timeout = client.flags.exit_timeout + local timeout = if_nil(client.flags.exit_timeout, false) if timeout then send_kill = true timeouts[client_id] = timeout @@ -870,7 +910,7 @@ function lsp.buf_request(bufnr, method, params, handler) local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do - local client = all_clients[client_id] + local client = active_clients[client_id] client.cancel_request(request_id) end end @@ -1066,7 +1106,7 @@ end ---@return boolean stopped true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) assert(client_id, 'missing client_id param') - return not all_clients[client_id] + return active_clients[client_id] == nil and not uninitialized_clients[client_id] end --- Gets a map of client_id:client pairs for the given buffer, where each value @@ -1132,11 +1172,7 @@ function lsp.for_each_buffer_client(bufnr, fn) 'lsp.get_clients({ bufnr = bufnr }) with regular loop', '0.12' ) - bufnr = resolve_bufnr(bufnr) - - for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do - fn(client, client.id, bufnr) - end + return for_each_buffer_client(bufnr, fn) end --- Function to manage overriding defaults for LSP handlers. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 758d68e4b0..b06fab7319 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -761,7 +761,7 @@ function Client:_request_sync(method, params, timeout_ms, bufnr) return request_result end ---- @package +--- @private --- Sends a notification to an LSP server. --- --- @param method string LSP method name. -- cgit From 3fd8292aaf215a17c3803ed84bc3b9dfd4931294 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 25 Mar 2024 19:04:23 +0100 Subject: vim-patch:ab01adf7c65b runtime(doc): Update options.txt closes: vim/vim#14295 https://github.com/vim/vim/commit/ab01adf7c65b4ee350b402ab3ef1e7dfa5e074f1 Co-authored-by: Song-Tianxiang <149415622+Song-Tianxiang@users.noreply.github.com> --- runtime/lua/vim/_meta/options.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index ac366197cc..2fdedef1de 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6846,7 +6846,7 @@ vim.go.tpm = vim.go.tabpagemax --- appear wrong in many places. --- The value must be more than 0 and less than 10000. --- ---- There are four main ways to use tabs in Vim: +--- There are five main ways to use tabs in Vim: --- 1. Always keep 'tabstop' at 8, set 'softtabstop' and 'shiftwidth' to 4 --- (or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim --- will use a mix of tabs and spaces, but typing and will -- cgit From a7bbda121d035d050b449b4dc63bd6ae027e248d Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 25 Mar 2024 19:06:28 +0000 Subject: fix(test): typing --- runtime/lua/vim/lsp/client.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index b06fab7319..1259a2f3d1 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -225,7 +225,7 @@ local validate = vim.validate --- If {status} is `true`, the function returns {request_id} as the second --- result. You can use this with `client.cancel_request(request_id)` to cancel --- the request. ---- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer): boolean, integer? +--- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer?): boolean, integer? --- --- Sends a request to the server and synchronously waits for the response. --- This is a wrapper around {client.request} @@ -647,10 +647,10 @@ end --- checks for capabilities and handler availability. --- --- @param method string LSP method name. ---- @param params table|nil LSP request params. ---- @param handler lsp.Handler|nil Response |lsp-handler| for this method. ---- @param bufnr integer Buffer handle (0 for current). ---- @return boolean status, integer|nil request_id {status} is a bool indicating +--- @param params? table LSP request params. +--- @param handler? lsp.Handler Response |lsp-handler| for this method. +--- @param bufnr? integer Buffer handle (0 for current). +--- @return boolean status, integer? request_id {status} is a bool indicating --- whether the request was successful. If it is `false`, then it will --- always be `false` (the client has shutdown). If it was --- successful, then it will return {request_id} as the -- cgit From 00e71d3da3464df2b4c4f33bfd5fac6d88e7c867 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 21 Mar 2024 15:15:20 +0000 Subject: refactor(lsp): simplify client tracking - Remove: - uninitialized_clients - active_clients - all_buffer_active_clients - Add: - all_clients - Use `lsp.get_clients()` to get buffer clients. --- runtime/lua/vim/lsp.lua | 343 ++++++++++++++++++----------------------- runtime/lua/vim/lsp/client.lua | 14 +- 2 files changed, 160 insertions(+), 197 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index d5c376ba44..0c9de607fe 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1,7 +1,5 @@ local api = vim.api -local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend local validate = vim.validate -local if_nil = vim.F.if_nil local lsp = vim._defer_require('vim.lsp', { _changetracking = ..., --- @module 'vim.lsp._changetracking' @@ -108,40 +106,7 @@ function lsp._buf_get_line_ending(bufnr) end -- Tracks all clients created via lsp.start_client -local active_clients = {} --- @type table -local all_buffer_active_clients = {} --- @type table> -local uninitialized_clients = {} --- @type table - ----@param bufnr? integer ----@param fn fun(client: vim.lsp.Client, client_id: integer, bufnr: integer) -local function for_each_buffer_client(bufnr, fn, restrict_client_ids) - validate({ - fn = { fn, 'f' }, - restrict_client_ids = { restrict_client_ids, 't', true }, - }) - bufnr = resolve_bufnr(bufnr) - local client_ids = all_buffer_active_clients[bufnr] - if not client_ids or tbl_isempty(client_ids) then - return - end - - if restrict_client_ids and #restrict_client_ids > 0 then - local filtered_client_ids = {} --- @type table - for client_id in pairs(client_ids) do - if vim.list_contains(restrict_client_ids, client_id) then - filtered_client_ids[client_id] = true - end - end - client_ids = filtered_client_ids - end - - for client_id in pairs(client_ids) do - local client = active_clients[client_id] - if client then - fn(client, client_id, bufnr) - end - end -end +local all_clients = {} --- @type table local client_errors_base = table.maxn(lsp.rpc.client_errors) local client_errors_offset = 0 @@ -156,7 +121,7 @@ end --- Can be used to look up the string from a the number or the number --- from the string. --- @nodoc -lsp.client_errors = tbl_extend( +lsp.client_errors = vim.tbl_extend( 'error', lsp.rpc.client_errors, client_error('BEFORE_INIT_CALLBACK_ERROR'), @@ -258,12 +223,10 @@ function lsp.start(config, opts) local bufnr = resolve_bufnr(opts.bufnr) - for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do - for _, client in pairs(clients) do - if reuse_client(client, config) then - lsp.buf_attach_client(bufnr, client.id) - return client.id - end + for _, client in pairs(all_clients) do + if reuse_client(client, config) then + lsp.buf_attach_client(bufnr, client.id) + return client.id end end @@ -383,48 +346,30 @@ local function reset_defaults(bufnr) end) end ---- @param client vim.lsp.Client -local function on_client_init(client) - local id = client.id - uninitialized_clients[id] = nil - -- Only assign after initialized. - active_clients[id] = client - -- If we had been registered before we start, then send didOpen This can - -- happen if we attach to buffers before initialize finishes or if - -- someone restarts a client. - for bufnr, client_ids in pairs(all_buffer_active_clients) do - if client_ids[id] then - client.on_attach(bufnr) - end - end -end - --- @param code integer --- @param signal integer --- @param client_id integer local function on_client_exit(code, signal, client_id) - local client = active_clients[client_id] or uninitialized_clients[client_id] - - for bufnr, client_ids in pairs(all_buffer_active_clients) do - if client_ids[client_id] then - vim.schedule(function() - if client and client.attached_buffers[bufnr] then - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) - end + local client = all_clients[client_id] + + for bufnr in pairs(client.attached_buffers) do + vim.schedule(function() + if client and client.attached_buffers[bufnr] then + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client_id }, + }) + end - local namespace = vim.lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace, bufnr) + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace, bufnr) + client.attached_buffers[bufnr] = nil - client_ids[client_id] = nil - if vim.tbl_isempty(client_ids) then - reset_defaults(bufnr) - end - end) - end + if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then + reset_defaults(bufnr) + end + end) end local name = client.name or 'unknown' @@ -432,8 +377,7 @@ local function on_client_exit(code, signal, client_id) -- Schedule the deletion of the client object so that it exists in the execution of LspDetach -- autocommands vim.schedule(function() - active_clients[client_id] = nil - uninitialized_clients[client_id] = nil + all_clients[client_id] = nil -- Client can be absent if executable starts, but initialize fails -- init/attach won't have happened @@ -465,13 +409,10 @@ function lsp.start_client(config) return end - --- @diagnostic disable-next-line: invisible - table.insert(client._on_init_cbs, on_client_init) --- @diagnostic disable-next-line: invisible table.insert(client._on_exit_cbs, on_client_exit) - -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. - uninitialized_clients[client.id] = client + all_clients[client.id] = client client:initialize() @@ -495,7 +436,7 @@ local function text_document_did_change_handler( new_lastline ) -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached - if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then + if #lsp.get_clients({ bufnr = bufnr }) == 0 then return true end util.buf_versions[bufnr] = changedtick @@ -543,6 +484,80 @@ local function text_document_did_save_handler(bufnr) end end +--- @param bufnr integer +--- @param client_id integer +local function buf_attach(bufnr, client_id) + local uri = vim.uri_from_bufnr(bufnr) + local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) + local group = api.nvim_create_augroup(augroup, { clear = true }) + api.nvim_create_autocmd('BufWritePre', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/willSave', + callback = function(ctx) + for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do + local params = { + textDocument = { + uri = uri, + }, + reason = protocol.TextDocumentSaveReason.Manual, + } + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then + client.notify(ms.textDocument_willSave, params) + end + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then + local result, err = + client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) + if result and result.result then + util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) + elseif err then + log.error(vim.inspect(err)) + end + end + end + end, + }) + api.nvim_create_autocmd('BufWritePost', { + group = group, + buffer = bufnr, + desc = 'vim.lsp: textDocument/didSave handler', + callback = function(ctx) + text_document_did_save_handler(ctx.buf) + end, + }) + -- First time, so attach and set up stuff. + api.nvim_buf_attach(bufnr, false, { + on_lines = text_document_did_change_handler, + on_reload = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + client:_text_document_did_open_handler(bufnr) + end + end, + on_detach = function() + local params = { textDocument = { uri = uri } } + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + changetracking.reset_buf(client, bufnr) + if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then + client.notify(ms.textDocument_didClose, params) + end + end + for _, client in ipairs(all_clients) do + client.attached_buffers[bufnr] = nil + end + util.buf_versions[bufnr] = nil + end, + -- TODO if we know all of the potential clients ahead of time, then we + -- could conditionally set this. + -- utf_sizes = size_index > 1; + utf_sizes = true, + }) +end + --- Implements the `textDocument/did…` notifications required to track a buffer --- for any language server. --- @@ -561,92 +576,26 @@ function lsp.buf_attach_client(bufnr, client_id) log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) return false end - local buffer_client_ids = all_buffer_active_clients[bufnr] -- This is our first time attaching to this buffer. - if not buffer_client_ids then - buffer_client_ids = {} - all_buffer_active_clients[bufnr] = buffer_client_ids + if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then + buf_attach(bufnr, client_id) + end - local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) - local group = api.nvim_create_augroup(augroup, { clear = true }) - api.nvim_create_autocmd('BufWritePre', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/willSave', - callback = function(ctx) - for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do - local params = { - textDocument = { - uri = uri, - }, - reason = protocol.TextDocumentSaveReason.Manual, - } - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then - client.notify(ms.textDocument_willSave, params) - end - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then - local result, err = - client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf) - if result and result.result then - util.apply_text_edits(result.result, ctx.buf, client.offset_encoding) - elseif err then - log.error(vim.inspect(err)) - end - end - end - end, - }) - api.nvim_create_autocmd('BufWritePost', { - group = group, - buffer = bufnr, - desc = 'vim.lsp: textDocument/didSave handler', - callback = function(ctx) - text_document_did_save_handler(ctx.buf) - end, - }) - -- First time, so attach and set up stuff. - api.nvim_buf_attach(bufnr, false, { - on_lines = text_document_did_change_handler, - on_reload = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - client:_text_document_did_open_handler(bufnr) - end - end, - on_detach = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - changetracking.reset_buf(client, bufnr) - if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then - client.notify(ms.textDocument_didClose, params) - end - client.attached_buffers[bufnr] = nil - end - util.buf_versions[bufnr] = nil - all_buffer_active_clients[bufnr] = nil - end, - -- TODO if we know all of the potential clients ahead of time, then we - -- could conditionally set this. - -- utf_sizes = size_index > 1; - utf_sizes = true, - }) + local client = lsp.get_client_by_id(client_id) + if not client then + return false end - if buffer_client_ids[client_id] then + if client.attached_buffers[bufnr] then return true end - -- This is our first time attaching this client to this buffer. - buffer_client_ids[client_id] = true - local client = active_clients[client_id] + client.attached_buffers[bufnr] = true + + -- This is our first time attaching this client to this buffer. -- Send didOpen for the client if it is initialized. If it isn't initialized -- then it will send didOpen on initialize. - if client then + if client.initialized then client:_on_attach(bufnr) end return true @@ -665,7 +614,7 @@ function lsp.buf_detach_client(bufnr, client_id) }) bufnr = resolve_bufnr(bufnr) - local client = lsp.get_client_by_id(client_id) + local client = all_clients[client_id] if not client or not client.attached_buffers[bufnr] then vim.notify( string.format( @@ -694,11 +643,6 @@ function lsp.buf_detach_client(bufnr, client_id) client.attached_buffers[bufnr] = nil util.buf_versions[bufnr] = nil - all_buffer_active_clients[bufnr][client_id] = nil - if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then - all_buffer_active_clients[bufnr] = nil - end - local namespace = lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) end @@ -708,7 +652,7 @@ end ---@param bufnr (integer) Buffer handle, or 0 for current ---@param client_id (integer) the client id function lsp.buf_is_attached(bufnr, client_id) - return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true + return lsp.get_clients({ bufnr = bufnr, id = client_id, _uninitialized = true })[1] ~= nil end --- Gets a client by id, or nil if the id is invalid. @@ -718,7 +662,7 @@ end --- ---@return (nil|vim.lsp.Client) client rpc object function lsp.get_client_by_id(client_id) - return active_clients[client_id] or uninitialized_clients[client_id] + return all_clients[client_id] end --- Returns list of buffers attached to client_id. @@ -726,7 +670,7 @@ end ---@param client_id integer client id ---@return integer[] buffers list of buffer ids function lsp.get_buffers_by_client_id(client_id) - local client = lsp.get_client_by_id(client_id) + local client = all_clients[client_id] return client and vim.tbl_keys(client.attached_buffers) or {} end @@ -742,17 +686,22 @@ end --- By default asks the server to shutdown, unless stop was requested --- already for this client, then force-shutdown is attempted. --- ----@param client_id integer|vim.lsp.Client id or |vim.lsp.Client| object, or list thereof ----@param force boolean|nil shutdown forcefully +---@param client_id integer|integer[]|vim.lsp.Client[] id, list of id's, or list of |vim.lsp.Client| objects +---@param force? boolean shutdown forcefully function lsp.stop_client(client_id, force) + --- @type integer[]|vim.lsp.Client[] local ids = type(client_id) == 'table' and client_id or { client_id } for _, id in ipairs(ids) do - if type(id) == 'table' and id.stop ~= nil then - id.stop(force) - elseif active_clients[id] then - active_clients[id].stop(force) - elseif uninitialized_clients[id] then - uninitialized_clients[id].stop(true) + if type(id) == 'table' then + if id.stop then + id.stop(force) + end + else + --- @cast id -vim.lsp.Client + local client = all_clients[id] + if client then + client.stop(force) + end end end end @@ -772,6 +721,9 @@ end --- --- Only return clients supporting the given method --- @field method? string +--- +--- Also return uninitialized clients. +--- @field package _uninitialized? boolean --- Get active clients. --- @@ -784,15 +736,16 @@ function lsp.get_clients(filter) local clients = {} --- @type vim.lsp.Client[] - local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {}) - or active_clients - for client_id in pairs(t) do - local client = active_clients[client_id] + local bufnr = filter.bufnr and resolve_bufnr(filter.bufnr) + + for _, client in pairs(all_clients) do if client and (filter.id == nil or client.id == filter.id) + and (filter.bufnr == nil or client.attached_buffers[bufnr]) and (filter.name == nil or client.name == filter.name) and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr })) + and (filter._uninitialized or client.initialized) then clients[#clients + 1] = client end @@ -810,15 +763,9 @@ end api.nvim_create_autocmd('VimLeavePre', { desc = 'vim.lsp: exit handler', callback = function() + local active_clients = lsp.get_clients() log.info('exit_handler', active_clients) - for _, client in pairs(uninitialized_clients) do - client.stop(true) - end - -- TODO handle v:dying differently? - if tbl_isempty(active_clients) then - return - end - for _, client in pairs(active_clients) do + for _, client in pairs(all_clients) do client.stop() end @@ -827,7 +774,7 @@ api.nvim_create_autocmd('VimLeavePre', { local send_kill = false for client_id, client in pairs(active_clients) do - local timeout = if_nil(client.flags.exit_timeout, false) + local timeout = client.flags.exit_timeout if timeout then send_kill = true timeouts[client_id] = timeout @@ -910,7 +857,7 @@ function lsp.buf_request(bufnr, method, params, handler) local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do - local client = active_clients[client_id] + local client = all_clients[client_id] client.cancel_request(request_id) end end @@ -1106,7 +1053,7 @@ end ---@return boolean stopped true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) assert(client_id, 'missing client_id param') - return active_clients[client_id] == nil and not uninitialized_clients[client_id] + return not all_clients[client_id] end --- Gets a map of client_id:client pairs for the given buffer, where each value @@ -1172,7 +1119,11 @@ function lsp.for_each_buffer_client(bufnr, fn) 'lsp.get_clients({ bufnr = bufnr }) with regular loop', '0.12' ) - return for_each_buffer_client(bufnr, fn) + bufnr = resolve_bufnr(bufnr) + + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do + fn(client, client.id, bufnr) + end end --- Function to manage overriding defaults for LSP handlers. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 1259a2f3d1..303fc55982 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -185,6 +185,10 @@ local validate = vim.validate --- @field root_dir string --- --- @field attached_buffers table +--- +--- Buffers that should be attached to upon initialize() +--- @field package _buffers_to_attach table +--- --- @field private _log_prefix string --- --- Track this so that we can escalate automatically if we've already tried a @@ -608,8 +612,16 @@ function Client:initialize() self:_notify(ms.workspace_didChangeConfiguration, { settings = self.settings }) end + -- If server is being restarted, make sure to re-attach to any previously attached buffers. + -- Save which buffers before on_init in case new buffers are attached. + local reattach_bufs = vim.deepcopy(self.attached_buffers) + self:_run_callbacks(self._on_init_cbs, lsp.client_errors.ON_INIT_CALLBACK_ERROR, self, result) + for buf in pairs(reattach_bufs) do + self:_on_attach(buf) + end + log.info( self._log_prefix, 'server_capabilities', @@ -761,7 +773,7 @@ function Client:_request_sync(method, params, timeout_ms, bufnr) return request_result end ---- @private +--- @package --- Sends a notification to an LSP server. --- --- @param method string LSP method name. -- cgit From d6f406db4527deb693f0aef90316c25d846f5195 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 26 Mar 2024 13:31:37 +0800 Subject: fix(filetype): don't use fnamemodify() with :e for extension (#27976) Use pattern matching instead, as fnamemodify() with :e produces an empty string when the file name only has an extension, leading to differences in behavior from Vim. Related #16955 #27972 --- runtime/lua/vim/filetype.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index af5636a32d..a69391be18 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1472,7 +1472,6 @@ local filename = { ['bash.bashrc'] = detect.bash, bashrc = detect.bash, ['.bashrc'] = detect.bash, - ['.env'] = detect.sh, ['.kshrc'] = detect.ksh, ['.profile'] = detect.sh, ['/etc/profile'] = detect.sh, @@ -2387,7 +2386,9 @@ function M.match(args) end -- Next, check file extension - local ext = fn.fnamemodify(name, ':e') + -- Don't use fnamemodify() with :e modifier here, + -- as that's empty when there is only an extension. + local ext = name:match('%.([^.]-)$') or '' ft, on_detect = dispatch(extension[ext], path, bufnr) if ft then return ft, on_detect -- cgit From 3f3c7299a14ff0025487a416462d8c32d922e04a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 26 Mar 2024 18:30:17 +0800 Subject: docs: remove remaining mentions of hkmap (#28038) --- runtime/lua/vim/_meta/options.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 2fdedef1de..e8e56e86b2 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -11,10 +11,9 @@ vim.bo = vim.bo ---@field [integer] vim.wo vim.wo = vim.wo ---- Allow CTRL-_ in Insert and Command-line mode. This is default off, to ---- avoid that users that accidentally type CTRL-_ instead of SHIFT-_ get ---- into reverse Insert mode, and don't know how to get out. See ---- 'revins'. +--- Allow CTRL-_ in Insert mode. This is default off, to avoid that users +--- that accidentally type CTRL-_ instead of SHIFT-_ get into reverse +--- Insert mode, and don't know how to get out. See 'revins'. --- --- @type boolean vim.o.allowrevins = false -- cgit From fc6d713dd8066f3132f7234a94ac059ae6d596a7 Mon Sep 17 00:00:00 2001 From: Mayrom <32033791+nhruo123@users.noreply.github.com> Date: Wed, 27 Mar 2024 02:08:54 +0200 Subject: feat(diagnostic): add support for many namespaces filtering in GetOpts (#28045) --- runtime/lua/vim/diagnostic.lua | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index d5075d7d3d..57491d155d 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -682,6 +682,13 @@ local function get_diagnostics(bufnr, opts, clamp) opts = opts or {} local namespace = opts.namespace + + if type(namespace) == 'number' then + namespace = { namespace } + end + + ---@cast namespace integer[] + local diagnostics = {} -- Memoized results of buf_line_count per bufnr @@ -742,11 +749,15 @@ local function get_diagnostics(bufnr, opts, clamp) end elseif bufnr == nil then for b, t in pairs(diagnostic_cache) do - add_all_diags(b, t[namespace] or {}) + for _, iter_namespace in ipairs(namespace) do + add_all_diags(b, t[iter_namespace] or {}) + end end else bufnr = get_bufnr(bufnr) - add_all_diags(bufnr, diagnostic_cache[bufnr][namespace] or {}) + for _, iter_namespace in ipairs(namespace) do + add_all_diags(bufnr, diagnostic_cache[bufnr][iter_namespace] or {}) + end end if opts.severity then @@ -785,7 +796,7 @@ end --- @param search_forward boolean --- @param bufnr integer --- @param opts vim.diagnostic.GotoOpts ---- @param namespace integer +--- @param namespace integer[]|integer --- @return vim.Diagnostic? local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 @@ -1115,8 +1126,8 @@ end --- A table with the following keys: --- @class vim.diagnostic.GetOpts --- ---- Limit diagnostics to the given namespace. ---- @field namespace? integer +--- Limit diagnostics to one or more namespaces. +--- @field namespace? integer[]|integer --- --- Limit diagnostics to the given line number. --- @field lnum? integer -- cgit From 7d971500847089ec8ade926a7f84d6bb3a51c8b0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 25 Mar 2024 22:06:31 +0000 Subject: fix(treesitter): return correct match table in iter_captures() --- runtime/lua/vim/func.lua | 5 ++-- runtime/lua/vim/func/_memoize.lua | 8 ++++-- runtime/lua/vim/treesitter/highlighter.lua | 14 ++++++--- runtime/lua/vim/treesitter/query.lua | 46 +++++++++++++++--------------- 4 files changed, 42 insertions(+), 31 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/func.lua b/runtime/lua/vim/func.lua index 206d1bae95..f71659ffb4 100644 --- a/runtime/lua/vim/func.lua +++ b/runtime/lua/vim/func.lua @@ -32,10 +32,11 @@ local M = {} --- first n arguments passed to {fn}. --- --- @param fn F Function to memoize. +--- @param strong? boolean Do not use a weak table --- @return F # Memoized version of {fn} --- @nodoc -function M._memoize(hash, fn) - return require('vim.func._memoize')(hash, fn) +function M._memoize(hash, fn, strong) + return require('vim.func._memoize')(hash, fn, strong) end return M diff --git a/runtime/lua/vim/func/_memoize.lua b/runtime/lua/vim/func/_memoize.lua index 835bf64c93..65210351bf 100644 --- a/runtime/lua/vim/func/_memoize.lua +++ b/runtime/lua/vim/func/_memoize.lua @@ -36,15 +36,19 @@ end --- @generic F: function --- @param hash integer|string|fun(...): any --- @param fn F +--- @param strong? boolean --- @return F -return function(hash, fn) +return function(hash, fn, strong) vim.validate({ hash = { hash, { 'number', 'string', 'function' } }, fn = { fn, 'function' }, }) ---@type table> - local cache = setmetatable({}, { __mode = 'kv' }) + local cache = {} + if not strong then + setmetatable(cache, { __mode = 'kv' }) + end hash = resolve_hash(hash) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 1e6f128461..3f7e31212c 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range') local ns = api.nvim_create_namespace('treesitter/highlighter') ----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table +---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch ---@class (private) vim.treesitter.highlighter.Query ---@field private _query vim.treesitter.Query? @@ -243,7 +243,7 @@ function TSHighlighter:get_query(lang) return self._queries[lang] end ---- @param match table +--- @param match TSQueryMatch --- @param bufnr integer --- @param capture integer --- @param metadata vim.treesitter.query.TSMetadata @@ -256,13 +256,15 @@ local function get_url(match, bufnr, capture, metadata) return url end - if not match or not match[url] then + local captures = match:captures() + + if not captures[url] then return end -- Assume there is only one matching node. If there is more than one, take the URL -- from the first. - local other_node = match[url][1] + local other_node = captures[url][1] return vim.treesitter.get_node_text(other_node, bufnr, { metadata = metadata[url], @@ -296,6 +298,10 @@ local function on_line_impl(self, buf, line, is_spell_nav) end if state.iter == nil or state.next_row < line then + -- Mainly used to skip over folds + + -- TODO(lewis6991): Creating a new iterator loses the cached predicate results for query + -- matches. Move this logic inside iter_captures() so we can maintain the cache. state.iter = state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 075fd0e99b..e68acac929 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,5 +1,6 @@ local api = vim.api local language = require('vim.treesitter.language') +local memoize = vim.func._memoize local M = {} @@ -212,7 +213,7 @@ end ---@param query_name string Name of the query (e.g. "highlights") --- ---@return vim.treesitter.Query? : Parsed query. `nil` if no query files are found. -M.get = vim.func._memoize('concat-2', function(lang, query_name) +M.get = memoize('concat-2', function(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] end @@ -245,7 +246,7 @@ end) ---@return vim.treesitter.Query : Parsed query --- ---@see [vim.treesitter.query.get()] -M.parse = vim.func._memoize('concat-2', function(lang, query) +M.parse = memoize('concat-2', function(lang, query) language.add(lang) local ts_query = vim._ts_parse_query(lang, query) @@ -812,6 +813,12 @@ local function value_or_node_range(start, stop, node) return start, stop end +--- @param match TSQueryMatch +--- @return integer +local function match_id_hash(_, match) + return (match:info()) +end + --- Iterate over all captures from all matches inside {node} --- --- {source} is needed if the query contains predicates; then the caller @@ -841,7 +848,7 @@ end ---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. --- ----@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table?): +---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, TSQueryMatch): --- capture id, capture node, metadata, match --- ---@note Captures are only returned if the query pattern of a specific capture contained predicates. @@ -854,7 +861,8 @@ function Query:iter_captures(node, source, start, stop) local cursor = vim._create_ts_querycursor(node, self.query, start, stop, { match_limit = 256 }) - local max_match_id = -1 + local apply_directives = memoize(match_id_hash, self.apply_directives, true) + local match_preds = memoize(match_id_hash, self.match_preds, true) local function iter(end_line) local capture, captured_node, match = cursor:next_capture() @@ -863,27 +871,18 @@ function Query:iter_captures(node, source, start, stop) return end - local captures --- @type table? - local match_id, pattern_index = match:info() - - local metadata = {} - - local preds = self.info.patterns[pattern_index] or {} - - if #preds > 0 and match_id > max_match_id then - captures = match:captures() - max_match_id = match_id - if not self:match_preds(match, source) then - cursor:remove_match(match_id) - if end_line and captured_node:range() > end_line then - return nil, captured_node, nil - end - return iter(end_line) -- tail call: try next match + if not match_preds(self, match, source) then + local match_id = match:info() + cursor:remove_match(match_id) + if end_line and captured_node:range() > end_line then + return nil, captured_node, nil, nil end - - metadata = self:apply_directives(match, source) + return iter(end_line) -- tail call: try next match end - return capture, captured_node, metadata, captures + + local metadata = apply_directives(self, match, source) + + return capture, captured_node, metadata, match end return iter end @@ -972,6 +971,7 @@ function Query:iter_matches(node, source, start, stop, opts) return pattern, old_match, metadata end + -- TODO(lewis6991): create a new function that returns {match, metadata} return pattern, captures, metadata end return iter -- cgit From 1fcf84d46a6b4af16e2cbe74a17821af664d48e6 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 27 Mar 2024 12:46:31 +0100 Subject: vim-patch:9.1.0196: filetype: support for gnuplot files is lacking (#27972) Problem: filetype: support for gnuplot files is lacking Solution: Also detect *.gnuplot files (RobbiZ98) closes: vim/vim#14243 https://github.com/vim/vim/commit/3a6bd0c5c743bf69d2e8af4c8b3c6b2cb5f3631a Co-authored-by: RobbiZ98 <113035863+RobbiZ98@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a69391be18..cb5b8fec7a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -466,6 +466,7 @@ local extension = { glsl = 'glsl', gn = 'gn', gni = 'gn', + gnuplot = 'gnuplot', gpi = 'gnuplot', go = 'go', gp = 'gp', @@ -1304,7 +1305,6 @@ local filename = { ['.gnashpluginrc'] = 'gnash', gnashpluginrc = 'gnash', gnashrc = 'gnash', - ['.gnuplot'] = 'gnuplot', ['go.sum'] = 'gosum', ['go.work.sum'] = 'gosum', ['go.work'] = 'gowork', -- cgit From 4ee9e58056a9d17ce921d8cc6dfd6d3305a40f69 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 28 Mar 2024 07:39:36 +0800 Subject: feat(tui): query extended underline support using DECRQSS (#28052) --- runtime/lua/vim/_defaults.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 91baee1a1e..6223082622 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -384,13 +384,13 @@ if tty then -- attributes, so there should be no attributes in the list. local attrs = vim.split(decrqss, ';') if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then - return true + return false end -- The returned SGR sequence should begin with 48:2 local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') if not sgr then - return true + return false end -- The remaining elements of the SGR sequence should be the 3 colors @@ -422,7 +422,8 @@ if tty then if os.getenv('TMUX') then decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) end - io.stdout:write(string.format('\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + -- Reset attributes first, as other code may have set attributes. + io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) timer:start(1000, 0, function() -- Delete the autocommand if no response was received -- cgit From a89ce89742db600665b69e58d5e1bc3dbee9d57b Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Thu, 28 Mar 2024 02:32:32 +0100 Subject: docs: fix typos (#27868) Co-authored-by: ite-usagi <77563904+ite-usagi@users.noreply.github.com> Co-authored-by: v-sim <56476039+v-sim@users.noreply.github.com> Co-authored-by: Evgeni Chasnovski Co-authored-by: zeertzjq Co-authored-by: Quico Augustijn Co-authored-by: nhld Co-authored-by: francisco souza <108725+fsouza@users.noreply.github.com> --- runtime/lua/vim/_meta/api.lua | 2 +- runtime/lua/vim/_meta/builtin.lua | 2 +- 2 files changed, 2 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 ed8128769d..678d6d3500 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -669,7 +669,7 @@ function vim.api.nvim_buf_set_lines(buffer, start, end_, strict_indexing, replac --- @return boolean function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end ---- Sets the full file name for a buffer +--- Sets the full file name for a buffer, like `:file_f` --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param name string Buffer name diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index ef9821fa32..20b6d9dabe 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -115,7 +115,7 @@ function vim.stricmp(a, b) end --- Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not --- supplied, it defaults to false (use UTF-32). Returns the byte index. --- ---- Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. +--- Invalid UTF-8 and NUL is treated like in |vim.str_utfindex()|. --- An {index} in the middle of a UTF-16 sequence is rounded upwards to --- the end of that sequence. --- @param str string -- cgit From 4147302f4be3fe8a5dae3f20a336477cb5f507a4 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Tue, 26 Mar 2024 19:06:39 +0100 Subject: vim-patch:9.1.0211: page-wise scrolling does not support smooth-scrolling Problem: Page-wise scrolling with Ctrl-F/Ctrl-B implements it's own logic to change the topline and cursor. In doing so, skipcol is not handled properly for 'smoothscroll', and virtual lines. Solution: Re-use the logic from Ctrl-E/Ctrl-Y while staying backward compatible as much as possible. https://github.com/vim/vim/commit/b9f5b95b7bec2414a5a96010514702d99afea18e --- runtime/lua/vim/_meta/options.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e8e56e86b2..2d78013208 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6075,8 +6075,8 @@ vim.go.sta = vim.go.smarttab --- highlighted with `hl-NonText`. --- You may also want to add "lastline" to the 'display' option to show as --- much of the last line as possible. ---- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y ---- and scrolling with the mouse. +--- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y, +--- CTRL-B, CTRL-F and scrolling with the mouse. --- --- @type boolean vim.o.smoothscroll = false -- cgit From 2f638c0ac6275bebacd12671481642fa43d7ba10 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Thu, 28 Mar 2024 10:20:45 +0100 Subject: vim-patch:9.1.0215: Half-page scrolling does not support smooth-scrolling Problem: Page-wise scrolling with Ctrl-D/Ctrl-U implements it's own logic to change the topline and cursor. More logic than necessary for scrolling with Ctrl-F/Ctrl-B was removed in patch 9.1.0211. Solution: Re-use the logic from Ctrl-E/Ctrl-Y/Ctrl-F/Ctrl-B while staying backward compatible as much as possible. Restore some of the logic that determined how many lines will be scrolled (Luuk van Baal) https://github.com/vim/vim/commit/5a2e3ec9ac72b6e644fea4ebba7e632498296e2f --- runtime/lua/vim/_meta/options.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 2d78013208..adc42f9659 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6076,7 +6076,7 @@ vim.go.sta = vim.go.smarttab --- You may also want to add "lastline" to the 'display' option to show as --- much of the last line as possible. --- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y, ---- CTRL-B, CTRL-F and scrolling with the mouse. +--- CTRL-D, CTRL-U, CTRL-F, CTRL-B and scrolling with the mouse. --- --- @type boolean vim.o.smoothscroll = false @@ -7861,8 +7861,8 @@ vim.wo.winbl = vim.wo.winblend --- will scroll 'window' minus two lines, with a minimum of one. --- When 'window' is equal to 'lines' minus one CTRL-F and CTRL-B scroll --- in a much smarter way, taking care of wrapping lines. ---- When resizing the Vim window, the value is smaller than 1 or more than ---- or equal to 'lines' it will be set to 'lines' minus 1. +--- When resizing the Vim window, and the value is smaller than 1 or more +--- than or equal to 'lines' it will be set to 'lines' minus 1. --- Note: Do not confuse this with the height of the Vim window, use --- 'lines' for that. --- -- cgit From d223a7cbd2a204fffeb220fb18dfede8f4ef3e1f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 29 Mar 2024 17:49:25 +0800 Subject: vim-patch:9.1.0228: Two unrelated things are tested by a single test (#28093) Problem: Two unrelated things are tested by a single test. Solution: Split it into two, restoring the old Test_brace_single_line(). Add missing cleanup to some tests. (zeertzjq) closes: vim/vim#14323 https://github.com/vim/vim/commit/ad493ef3ea9ef7f2b0badcd2298883b5ab6e4ef4 --- runtime/lua/vim/_meta/options.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index adc42f9659..5e40851457 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -6075,8 +6075,7 @@ vim.go.sta = vim.go.smarttab --- highlighted with `hl-NonText`. --- You may also want to add "lastline" to the 'display' option to show as --- much of the last line as possible. ---- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y, ---- CTRL-D, CTRL-U, CTRL-F, CTRL-B and scrolling with the mouse. +--- NOTE: partly implemented, doesn't work yet for `gj` and `gk`. --- --- @type boolean vim.o.smoothscroll = false -- cgit From 38e38d1b401e38459842f0e4da431e3dd6c2e527 Mon Sep 17 00:00:00 2001 From: James Trew <66286082+jamestrew@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:23:01 -0400 Subject: fix(fs): allow backslash characters in unix paths Backslashes are valid characters in unix style paths. Fix the conversion of backslashes to forward slashes in several `vim.fs` functions when not on Windows. On Windows, backslashes will still be converted to forward slashes. --- runtime/lua/vim/fs.lua | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index f9fe122f01..b7718ac87a 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,6 +1,7 @@ local M = {} local iswin = vim.uv.os_uname().sysname == 'Windows_NT' +local os_sep = iswin and '\\' or '/' --- Iterate over all the parents of the given path. --- @@ -47,19 +48,23 @@ function M.dirname(file) return nil end vim.validate({ file = { file, 's' } }) - if iswin and file:match('^%w:[\\/]?$') then - return (file:gsub('\\', '/')) - elseif not file:match('[\\/]') then + if iswin then + file = file:gsub(os_sep, '/') --[[@as string]] + if file:match('^%w:/?$') then + return file + end + end + if not file:match('/') then return '.' elseif file == '/' or file:match('^/[^/]+$') then return '/' end ---@type string - local dir = file:match('[/\\]$') and file:sub(1, #file - 1) or file:match('^([/\\]?.+)[/\\]') + local dir = file:match('/$') and file:sub(1, #file - 1) or file:match('^(/?.+)/') if iswin and dir:match('^%w:$') then return dir .. '/' end - return (dir:gsub('\\', '/')) + return dir end --- Return the basename of the given path @@ -72,10 +77,13 @@ function M.basename(file) return nil end vim.validate({ file = { file, 's' } }) - if iswin and file:match('^%w:[\\/]?$') then - return '' + if iswin then + file = file:gsub(os_sep, '/') --[[@as string]] + if file:match('^%w:/?$') then + return '' + end end - return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) + return file:match('/$') and '' or (file:match('[^/]*$')) end --- Concatenate directories and/or file paths into a single path with normalization @@ -334,15 +342,16 @@ end --- @field expand_env boolean --- Normalize a path to a standard format. A tilde (~) character at the ---- beginning of the path is expanded to the user's home directory and any ---- backslash (\) characters are converted to forward slashes (/). Environment ---- variables are also expanded. +--- beginning of the path is expanded to the user's home directory and +--- environment variables are also expanded. +--- +--- On Windows, backslash (\) characters are converted to forward slashes (/). --- --- Examples: --- --- ```lua --- vim.fs.normalize('C:\\\\Users\\\\jdoe') ---- -- 'C:/Users/jdoe' +--- -- On Windows: 'C:/Users/jdoe' --- --- vim.fs.normalize('~/src/neovim') --- -- '/home/jdoe/src/neovim' @@ -364,7 +373,7 @@ function M.normalize(path, opts) if path:sub(1, 1) == '~' then local home = vim.uv.os_homedir() or '~' - if home:sub(-1) == '\\' or home:sub(-1) == '/' then + if home:sub(-1) == os_sep then home = home:sub(1, -2) end path = home .. path:sub(2) @@ -374,7 +383,7 @@ function M.normalize(path, opts) path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) end - path = path:gsub('\\', '/'):gsub('/+', '/') + path = path:gsub(os_sep, '/'):gsub('/+', '/') if iswin and path:match('^%w:/$') then return path end -- cgit From 2424c3e6967ef953222cf48564629ffe5812d415 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 29 Mar 2024 18:05:02 +0100 Subject: fix: support UNC paths in vim.fs.normalize Closes https://github.com/neovim/neovim/issues/27068. --- runtime/lua/vim/fs.lua | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index b7718ac87a..ad0d914ea2 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -371,7 +371,8 @@ function M.normalize(path, opts) expand_env = { opts.expand_env, { 'boolean' }, true }, }) - if path:sub(1, 1) == '~' then + -- Expand ~ to users home directory + if vim.startswith(path, '~') then local home = vim.uv.os_homedir() or '~' if home:sub(-1) == os_sep then home = home:sub(1, -2) @@ -379,15 +380,32 @@ function M.normalize(path, opts) path = home .. path:sub(2) end + -- Expand environment variables if `opts.expand_env` isn't `false` if opts.expand_env == nil or opts.expand_env then path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) end - path = path:gsub(os_sep, '/'):gsub('/+', '/') + -- Convert path separator to `/` + path = path:gsub(os_sep, '/') + + -- Don't modify leading double slash as those have implementation-defined behavior according to + -- POSIX. They are also valid UNC paths. Three or more leading slashes are however collapsed to + -- a single slash. + if vim.startswith(path, '//') and not vim.startswith(path, '///') then + path = '/' .. path:gsub('/+', '/') + else + path = path:gsub('/+', '/') + end + + -- Ensure last slash is not truncated from root drive on Windows if iswin and path:match('^%w:/$') then return path end - return (path:gsub('(.)/$', '%1')) + + -- Remove trailing slashes + path = path:gsub('(.)/$', '%1') + + return path end return M -- cgit From e1ff2c51cad755d0ddc04a23df23e317d77023ed Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 31 Mar 2024 11:20:05 +0800 Subject: feat(lua): pass keys before mapping to vim.on_key() callback (#28098) Keys before mapping (i.e. typed keys) are passed as the second argument. --- runtime/lua/vim/_editor.lua | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index f527fc194c..18f6cfa4ba 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -654,11 +654,14 @@ local on_key_cbs = {} --- @type table --- ---@note {fn} will be removed on error. ---@note {fn} will not be cleared by |nvim_buf_clear_namespace()| ----@note {fn} will receive the keys after mappings have been evaluated --- ----@param fn fun(key: string)? Function invoked on every key press. |i_CTRL-V| ---- Passing in nil when {ns_id} is specified removes the ---- callback associated with namespace {ns_id}. +---@param fn fun(key: string, typed: string)? +--- Function invoked on every key press. |i_CTRL-V| +--- {key} is the key after mappings have been applied, and +--- {typed} is the key(s) before mappings are applied, which +--- may be empty if {key} is produced by non-typed keys. +--- When {fn} is nil and {ns_id} is specified, the callback +--- associated with namespace {ns_id} is removed. ---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a --- new |nvim_create_namespace()| id. --- @@ -684,11 +687,11 @@ end --- Executes the on_key callbacks. ---@private -function vim._on_key(char) +function vim._on_key(buf, typed_buf) local failed_ns_ids = {} local failed_messages = {} for k, v in pairs(on_key_cbs) do - local ok, err_msg = pcall(v, char) + local ok, err_msg = pcall(v, buf, typed_buf) if not ok then vim.on_key(nil, k) table.insert(failed_ns_ids, k) -- cgit From 01691c5447d9222a0e84f77b7043251580de7dee Mon Sep 17 00:00:00 2001 From: Marcin Szamotulski Date: Sun, 31 Mar 2024 20:43:34 +0200 Subject: fix(lsp): abort callHierarchy on no result (#28102) The `callHierarchy` function should warn the user when `textDocument/prepareCallHierarchy` didn't resolve an entity and return, rather than calling the `callHierarchy/{incoming,outgoing}Calls` method with an empty object - which is encoded as an empty list (which doesn't respect language server specification for the `callHierarchy/incomingCalls` call). --- runtime/lua/vim/lsp/buf.lua | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 50121f30b2..43f52b8116 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -461,7 +461,14 @@ local function call_hierarchy(method) vim.notify(err.message, vim.log.levels.WARN) return end + if not result then + vim.notify('No item resolved', vim.log.levels.WARN) + return + end local call_hierarchy_item = pick_call_hierarchy_item(result) + if not call_hierarchy_item then + return + end local client = vim.lsp.get_client_by_id(ctx.client_id) if client then client.request(method, { item = call_hierarchy_item }, nil, ctx.bufnr) -- cgit From c24dcb1bea1fcd7384ccb09bd3bd1d25db51f890 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 18:57:50 +0200 Subject: vim-patch:9.1.0234: filetype: support for Intel HEX files is lacking Problem: filetype: support for Intel HEX files is lacking Solution: Add more file extensions that are typical for Intel HEX files (Wu, Zhenyu) Reference: https://en.wikipedia.org/wiki/Intel_HEX closes: vim/vim#14355 https://github.com/vim/vim/commit/e523dd9803ed62ea0657af8c85ab7bdfe80f4c53 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index cb5b8fec7a..13a3953e8a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -504,7 +504,16 @@ local extension = { vc = 'hercules', heex = 'heex', hex = 'hex', + ['a43'] = 'hex', + ['a90'] = 'hex', ['h32'] = 'hex', + ['h80'] = 'hex', + ['h86'] = 'hex', + ihex = 'hex', + ihe = 'hex', + ihx = 'hex', + int = 'hex', + mcs = 'hex', hjson = 'hjson', m3u = 'hlsplaylist', m3u8 = 'hlsplaylist', -- cgit From 96d77b2051a660e79d2d57c33d6c2b448d996838 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:03:13 +0200 Subject: vim-patch:9.1.0235: filetype: supertux files are not recognized Problem: filetype: supertux files are not recognized Solution: Supertux uses lisp to store hotkeys in config and game stage information, so add a pattern for supertux files. (Wu, Zhenyu) Reference: https://github.com/SuperTux/supertux/wiki/S-Expression closes: vim/vim#14356 https://github.com/vim/vim/commit/4ff83b904ea579e51a0da5d2c6c3873ccef4ac0e Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 13a3953e8a..8237674ef3 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -620,6 +620,7 @@ local extension = { el = 'lisp', lsp = 'lisp', asd = 'lisp', + stsg = 'lisp', lt = 'lite', lite = 'lite', livemd = 'livebook', @@ -1761,6 +1762,7 @@ local pattern = { ['.*/etc/.*limits%.conf'] = 'limits', ['.*/etc/limits'] = 'limits', ['.*/etc/.*limits%.d/.*%.conf'] = 'limits', + ['.*/supertux2/config'] = 'lisp', ['.*/LiteStep/.*/.*%.rc'] = 'litestep', ['.*/etc/login%.access'] = 'loginaccess', ['.*/etc/login%.defs'] = 'logindefs', -- cgit From 2e0233d003710c91d513cce5074f6f4b9970e5ee Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:06:13 +0200 Subject: vim-patch:9.1.0236: filetype: texlua files are not recognized Problem: filetype: texlua files are not recognized Solution: Add '*.tlu' pattern for texlua files (Wu, Zhenyu) Reference: https://github.com/TeX-Live/texdoc/tree/master/script closes: vim/vim#14357 https://github.com/vim/vim/commit/a75f4791b147db60a1d2744bb5ab7326e0c0edc0 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 8237674ef3..16bfd2f912 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -637,6 +637,7 @@ local extension = { nse = 'lua', rockspec = 'lua', lua = 'lua', + tlu = 'lua', luau = 'luau', lrc = 'lyrics', m = detect.m, -- cgit From f29ba3c46cc72b6281927fbcc9a5101dab0badd2 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:07:24 +0200 Subject: vim-patch:9.1.0237: filetype: mplstyle files are not recognized Problem: filetype: mplstyle files are not recognized Solution: Detect '*.mplstyle' files as yaml (Wu, Zhenyu) closes: vim/vim#14358 https://github.com/vim/vim/commit/0fd560d46a1b83edba032300ab1f6119d4dca7b5 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 16bfd2f912..c788ae4413 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1141,6 +1141,7 @@ local extension = { yml = 'yaml', yaml = 'yaml', eyaml = 'yaml', + mplstyle = 'yaml', yang = 'yang', yuck = 'yuck', z8a = 'z8a', -- cgit From ce69056e956ba0957714ef649062828743545c6c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:10:01 +0200 Subject: vim-patch:9.1.0238: filetype: jupyterlab and sublime config are not recognized Problem: filetype: jupyterlab and sublime config are not recognized Solution: Detect jupyterlab and sublime config files as json (Wu, Zhenyu) closes: vim/vim#14359 https://github.com/vim/vim/commit/75c607dff7c9e02766ae0fd3588e08da00394d0f Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c788ae4413..7ab01402cc 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -576,6 +576,10 @@ local extension = { geojson = 'json', webmanifest = 'json', ipynb = 'json', + ['jupyterlab-settings'] = 'json', + ['sublime-project'] = 'json', + ['sublime-settings'] = 'json', + ['sublime-workspace'] = 'json', ['json-patch'] = 'json', json5 = 'json5', jsonc = 'jsonc', -- cgit From acac56360fd0bc94a00b5fc34ae55ebb7158eb98 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:12:30 +0200 Subject: vim-patch:9.1.0239: filetype: gnuplot history files are not recognised Problem: filetype: gnuplot history files are not recognised Solution: detect .gnuplot_history files as gnuplot (Wu, Zhenyu) closes: vim/vim#14360 https://github.com/vim/vim/commit/8e47eb31cc8b29a223f1706730b8f4a5598d59ce Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 7ab01402cc..5b5c9e0af5 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1321,6 +1321,7 @@ local filename = { ['.gnashpluginrc'] = 'gnash', gnashpluginrc = 'gnash', gnashrc = 'gnash', + ['.gnuplot_history'] = 'gnuplot', ['go.sum'] = 'gosum', ['go.work.sum'] = 'gosum', ['go.work'] = 'gowork', -- cgit From a978e831582aeb1de18d42a522a84b2c38b69b9f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:16:13 +0200 Subject: vim-patch:9.1.0240: filetype: some python tools config files are not recognized Problem: filetype: some python tools config files are not recognized Solution: Detect config files for setuptools, pudb, coverage as dosini (Wu, Zhenyu) closes: vim/vim#14361 https://github.com/vim/vim/commit/665220a17b830a271f0c7ef07211d25cd1c7b389 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5b5c9e0af5..07ce1d8b83 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1281,6 +1281,9 @@ local filename = { npmrc = 'dosini', ['/etc/yum.conf'] = 'dosini', ['.npmrc'] = 'dosini', + ['setup.cfg'] = 'dosini', + ['pudb.cfg'] = 'dosini', + ['.coveragerc'] = 'dosini', ['/etc/pacman.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', -- cgit From afc7a5611eef3c07154c34a60d3c2513e8afcdb7 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:18:10 +0200 Subject: vim-patch:9.1.0241: filetype: mysql history files are not recognized Problem: filetype: mysql history files are not recognized Solution: Detect .mysql_history as mysql (Wu, Zhenyu) closes: vim/vim#14362 https://github.com/vim/vim/commit/6b285c8cfd74e1da49fe17dca2ea9989bd9dae49 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 07ce1d8b83..9e60877381 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1411,6 +1411,7 @@ local filename = { ['mplayer.conf'] = 'mplayerconf', mrxvtrc = 'mrxvtrc', ['.mrxvtrc'] = 'mrxvtrc', + ['.mysql_history'] = 'mysql', ['/etc/nanorc'] = 'nanorc', Neomuttrc = 'neomuttrc', ['.netrc'] = 'netrc', -- cgit From 5cdbb22c348f196f59d55535cc4bfee5dc72d83c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:20:16 +0200 Subject: vim-patch:9.1.0242: filetype: octave history files are not recognized Problem: filetype: octave history files are not recognized Solution: Detect octave/history files as octave (Wu, Zhenyu) closes: vim/vim#14363 https://github.com/vim/vim/commit/be71ac694f623e162f1ecb7a182bdf2fd281591f Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 9e60877381..ba5c0add73 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1912,6 +1912,7 @@ local pattern = { ['.*%.[1-9]'] = detect.nroff, ['.*%.ml%.cppo'] = 'ocaml', ['.*%.mli%.cppo'] = 'ocaml', + ['.*/octave/history'] = 'octave', ['.*%.opam%.template'] = 'opam', ['.*/openvpn/.*/.*%.conf'] = 'openvpn', ['.*%.[Oo][Pp][Ll]'] = 'opl', -- cgit From 1a0b27965c13cf0f2d405bed341621ec8cdf3d70 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:22:20 +0200 Subject: vim-patch:9.1.0243: filetype: netrw history file is not recognized Problem: filetype: netrw history file is not recognized Solution: Detect .netrwhist as vim files (Wu, Zhenyu) closes: vim/vim#14364 https://github.com/vim/vim/commit/abbb4a4f7032de9e223293182402af5e2867e372 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index ba5c0add73..c721d387b9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1542,6 +1542,7 @@ local filename = { vgrindefs = 'vgrindefs', ['.exrc'] = 'vim', ['_exrc'] = 'vim', + ['.netrwhist'] = 'vim', ['_viminfo'] = 'viminfo', ['.viminfo'] = 'viminfo', ['.wgetrc'] = 'wget', -- cgit From 239101e32afbbaef679b1c310bbbee65c57141a2 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:25:07 +0200 Subject: vim-patch:9.1.0244: filetype: bash history files are not recognized Problem: filetype: bash history files are not recognized Solution: detect .bash-history and .bash_history files as bash (Wu, Zhenyu) closes: vim/vim#14365 https://github.com/vim/vim/commit/84ce55001af80d89245c4dd051c6f809389df62f Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c721d387b9..a1ce5f1edf 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1953,6 +1953,7 @@ local pattern = { ['.*/etc/serial%.conf'] = 'setserial', ['.*/etc/udev/cdsymlinks%.conf'] = 'sh', ['%.bash[_%-]aliases'] = detect.bash, + ['%.bash[_%-]history'] = detect.bash, ['%.bash[_%-]logout'] = detect.bash, ['%.bash[_%-]profile'] = detect.bash, ['%.kshrc.*'] = detect.ksh, -- cgit From fa863c17b27f92f840b88b58c88e6479f70ac3c6 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:44:40 +0200 Subject: vim-patch:9.1.0245: filetype: zsh theme, history and zunit files are not recognized Problem: filetype: zsh theme, history and zunit files are not recognized. Solution: Detect '.zsh_history', '*.zsh-theme' and '*.zunit' as zsh (Wu, Zhenyu) closes: vim/vim#14366 https://github.com/vim/vim/commit/a55a22a1a30f8f37633ed78ea25a393d11e250f2 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a1ce5f1edf..6dc0391404 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1155,6 +1155,8 @@ local extension = { zut = 'zimbutempl', zs = 'zserio', zsh = 'zsh', + zunit = 'zsh', + ['zsh-theme'] = 'zsh', vala = 'vala', web = detect.web, pl = detect.pl, @@ -1573,6 +1575,7 @@ local filename = { ['.zshrc'] = 'zsh', ['.zprofile'] = 'zsh', ['.zcompdump'] = 'zsh', + ['.zsh_history'] = 'zsh', ['.zshenv'] = 'zsh', ['.zfbfmarks'] = 'zsh', -- END FILENAME -- cgit From 09869c3745f2341ed8d37a06a1cad91c570947af Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:46:48 +0200 Subject: vim-patch:9.1.0246: filetype: fontconfig files are not recognized Problem: filetype: fontconfig files are not recognized Solution: detect 'fonts.conf' as xml (Wu, Zhenyu) closes: vim/vim#14367 https://github.com/vim/vim/commit/a2c27b01dc344b16849721bd934779c627665364 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 6dc0391404..1d6dc73879 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1566,6 +1566,7 @@ local filename = { fglrxrc = 'xml', ['/etc/blkid.tab'] = 'xml', ['/etc/blkid.tab.old'] = 'xml', + ['fonts.conf'] = 'xml', ['.clangd'] = 'yaml', ['.clang-format'] = 'yaml', ['.clang-tidy'] = 'yaml', -- cgit From de1a54dfe1f18a855d32124786181a91be157aca Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:50:45 +0200 Subject: vim-patch:9.1.0247: filetype: bundle config files are not recognized Problem: filetype: bundle config files are not recognized Solution: Detect '*/.bundle/config' as yaml (Wu, Zhenyu) closes: vim/vim#14368 https://github.com/vim/vim/commit/3f6fa93b3b7d8e0bd30eddbbf4ae273c14d4455b Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1d6dc73879..f3aae468c7 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2052,6 +2052,7 @@ local pattern = { -- Increase priority to run before the pattern below ['XF86Config%-4.*'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }), ['XF86Config.*'] = starsetf(detect.xfree86_v3), + ['.*/%.bundle/config'] = 'yaml', ['%.zcompdump.*'] = starsetf('zsh'), -- .zlog* and zlog* ['%.?zlog.*'] = starsetf('zsh'), -- cgit From 19ee281809b37d6de34125e60c568c7c3133ebad Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:53:05 +0200 Subject: vim-patch:9.1.0248: filetype: yarn lock files are not recognized Problem: filetype: yarn lock files are not recognized Solution: Detect 'yarn.lock' files as yaml (Wu, Zhenyu) closes: vim/vim#14369 https://github.com/vim/vim/commit/3b497aa2470ff613fed79569bc8589dae8dc3190 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index f3aae468c7..ff56bfbac0 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1570,6 +1570,7 @@ local filename = { ['.clangd'] = 'yaml', ['.clang-format'] = 'yaml', ['.clang-tidy'] = 'yaml', + ['yarn.lock'] = 'yaml', ['/etc/zprofile'] = 'zsh', ['.zlogin'] = 'zsh', ['.zlogout'] = 'zsh', -- cgit From 000431820e1e0886be99e68c2667633f1e4b18c6 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:56:43 +0200 Subject: vim-patch:9.1.0249: filetype: rock_manifest and config.ld files are not recognized Problem: filetype: rock_manifest and config.ld files are not recognized Solution: Detect 'rock_manifest' and 'config.ld' as lua (Wu, Zhenyu) closes: vim/vim#14370 https://github.com/vim/vim/commit/a917bd58bde0e1fca2affedc6fc0c15cb6b5e9f2 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index ff56bfbac0..3fc208c7a4 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1389,6 +1389,8 @@ local filename = { ['.lsl'] = detect.lsl, ['.busted'] = 'lua', ['.luacheckrc'] = 'lua', + ['config.ld'] = 'lua', + ['rock_manifest'] = 'lua', ['lynx.cfg'] = 'lynx', ['m3overrides'] = 'm3build', ['m3makefile'] = 'm3build', -- cgit From 2e97ae26647cb52e028d59a6e4e57d28c93e6283 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 31 Mar 2024 19:58:49 +0200 Subject: vim-patch:9.1.0250: filetype: ldscripts cannot be recognized Problem: filetype: ldscripts cannot be recognized Solution: Detect '*/ldscripts/*' as ld (Wu, Zhenyu) closes: vim/vim#14371 https://github.com/vim/vim/commit/4c7098b00a5edfb25b24fe3210866a4f30a2d78f Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 3fc208c7a4..86374b26e5 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1775,6 +1775,7 @@ local pattern = { ['.*%.[Ss][Uu][Bb]'] = 'krl', ['lilo%.conf.*'] = starsetf('lilo'), ['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'), + ['.*/ldscripts/.*'] = 'ld', ['.*lftp/rc'] = 'lftp', ['.*/%.libao'] = 'libao', ['.*/etc/libao%.conf'] = 'libao', -- cgit From 381806729db1016106b02d866c4f4f64f76a351f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 1 Apr 2024 18:43:30 +0800 Subject: vim-patch:9.0.1643: filetype detection fails if file name ends in many '~' (#28141) Problem: Filetype detection fails if file name ends in many '~'. Solution: Strip multiple '~' at the same time. (closes vim/vim#12553) https://github.com/vim/vim/commit/c12e4eecbb26cedca96e0810d3501043356eebaa In Nvim this already works as Lua filetype detection isn't subject to such a small recursion limit as autocommands, but it still makes sense to avoid unnecessary recursion. Co-authored-by: Bram Moolenaar --- runtime/lua/vim/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 86374b26e5..190071bbc7 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2064,7 +2064,7 @@ local pattern = { ['%.?zsh.*'] = starsetf('zsh'), -- Ignored extension ['.*~'] = function(path, bufnr) - local short = path:gsub('~$', '', 1) + local short = path:gsub('~+$', '', 1) if path ~= short and short ~= '' then return M.match({ buf = bufnr, filename = fn.fnameescape(short) }) end -- cgit From 6cfca21bac6bb39b50cba1c23ffb2b69e2d94df8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 2 Apr 2024 10:54:40 +0200 Subject: feat(treesitter): add `@injection.filename` Problem: Injecting languages for file redirects (e.g., in bash) is not possible. Solution: Add `@injection.filename` capture that is piped through `vim.filetype.match({ filename = node_text })`; the resulting filetype (if not `nil`) is then resolved as a language (either directly or through the list maintained via `vim.treesitter.language.register()`). Note: `@injection.filename` is a non-standard capture introduced by Helix; having two editors implement it makes it likely to be upstreamed. --- runtime/lua/vim/treesitter/languagetree.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 3b5d8953c9..8f65cb57c3 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -809,6 +809,10 @@ function LanguageTree:_get_injection(match, metadata) if name == 'injection.language' then local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) lang = resolve_lang(text) + elseif name == 'injection.filename' then + local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) + local ft = vim.filetype.match({ filename = text }) + lang = ft and resolve_lang(ft) elseif name == 'injection.content' then ranges = get_node_ranges(node, self._source, metadata[id], include_children) end -- cgit From d9235efa76229708586d3c9db3dcbac46127ca0a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 2 Apr 2024 11:56:29 +0100 Subject: refactor(lsp): move workspace folder logic into the client - Changed `reuse_client` to check workspace folders in addition to root_dir. --- runtime/lua/vim/lsp.lua | 28 ++++++++++++++++++++++----- runtime/lua/vim/lsp/buf.lua | 40 +++++---------------------------------- runtime/lua/vim/lsp/client.lua | 43 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 41 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0c9de607fe..eb604caacd 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -164,6 +164,28 @@ local function once(fn) end end +--- @param client vim.lsp.Client +--- @param config vim.lsp.ClientConfig +--- @return boolean +local function reuse_client_default(client, config) + if client.name ~= config.name then + return false + end + + if config.root_dir then + for _, dir in ipairs(client.workspace_folders or {}) do + -- note: do not need to check client.root_dir since that should be client.workspace_folders[1] + if config.root_dir == dir.name then + return true + end + end + end + + -- TODO(lewis6991): also check config.workspace_folders + + return false +end + --- @class vim.lsp.start.Opts --- @inlinedoc --- @@ -216,11 +238,7 @@ end --- @return integer? client_id function lsp.start(config, opts) opts = opts or {} - local reuse_client = opts.reuse_client - or function(client, conf) - return client.root_dir == conf.root_dir and client.name == conf.name - end - + local reuse_client = opts.reuse_client or reuse_client_default local bufnr = resolve_bufnr(opts.bufnr) for _, client in pairs(all_clients) do diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 43f52b8116..17cc698b76 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -521,28 +521,9 @@ function M.add_workspace_folder(workspace_folder) print(workspace_folder, ' is not a valid directory') return end - local new_workspace = { - uri = vim.uri_from_fname(workspace_folder), - name = workspace_folder, - } - local params = { event = { added = { new_workspace }, removed = {} } } - local bufnr = vim.api.nvim_get_current_buf() + local bufnr = api.nvim_get_current_buf() for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do - local found = false - for _, folder in pairs(client.workspace_folders or {}) do - if folder.name == workspace_folder then - found = true - print(workspace_folder, 'is already part of this workspace') - break - end - end - if not found then - client.notify(ms.workspace_didChangeWorkspaceFolders, params) - if not client.workspace_folders then - client.workspace_folders = {} - end - table.insert(client.workspace_folders, new_workspace) - end + client:_add_workspace_folder(workspace_folder) end end @@ -554,23 +535,12 @@ function M.remove_workspace_folder(workspace_folder) workspace_folder = workspace_folder or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h')) api.nvim_command('redraw') - if not (workspace_folder and #workspace_folder > 0) then + if not workspace_folder or #workspace_folder == 0 then return end - local workspace = { - uri = vim.uri_from_fname(workspace_folder), - name = workspace_folder, - } - local params = { event = { added = {}, removed = { workspace } } } - local bufnr = vim.api.nvim_get_current_buf() + local bufnr = api.nvim_get_current_buf() for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do - for idx, folder in pairs(client.workspace_folders) do - if folder.name == workspace_folder then - client.notify(ms.workspace_didChangeWorkspaceFolders, params) - client.workspace_folders[idx] = nil - return - end - end + client:_remove_workspace_folder(workspace_folder) end print(workspace_folder, 'is not currently part of the workspace') end diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 303fc55982..f73f97b8cd 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -420,7 +420,7 @@ local function get_workspace_folders(workspace_folders, root_dir) return { { uri = vim.uri_from_fname(root_dir), - name = string.format('%s', root_dir), + name = root_dir, }, } end @@ -1065,4 +1065,45 @@ function Client:_on_exit(code, signal) ) end +--- @package +--- Add a directory to the workspace folders. +--- @param dir string? +function Client:_add_workspace_folder(dir) + for _, folder in pairs(self.workspace_folders or {}) do + if folder.name == dir then + print(dir, 'is already part of this workspace') + return + end + end + + local wf = assert(get_workspace_folders(nil, dir)) + + self:_notify(ms.workspace_didChangeWorkspaceFolders, { + event = { added = wf, removed = {} }, + }) + + if not self.workspace_folders then + self.workspace_folders = {} + end + vim.list_extend(self.workspace_folders, wf) +end + +--- @package +--- Remove a directory to the workspace folders. +--- @param dir string? +function Client:_remove_workspace_folder(dir) + local wf = assert(get_workspace_folders(nil, dir)) + + self:_notify(ms.workspace_didChangeWorkspaceFolders, { + event = { added = {}, removed = wf }, + }) + + for idx, folder in pairs(self.workspace_folders) do + if folder.name == dir then + table.remove(self.workspace_folders, idx) + break + end + end +end + return Client -- cgit From e74cd1d9ffa935914905c6fc666c5bbf0cba36b7 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 2 Apr 2024 23:04:11 +0200 Subject: vim-patch:9.1.0253: filetype: typespec files are not recognized Problem: filetype: typespec files are not recognized Solution: Detect '*.tsp' files as typespec (Hilmar Wiegand) Specs is at https://typespec.io/ closes: vim/vim#14392 https://github.com/vim/vim/commit/6c9f4f98f1cda3793406724a260cd651210a5d0d Co-authored-by: Hilmar Wiegand --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 190071bbc7..1198a9972f 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1052,6 +1052,7 @@ local extension = { mts = 'typescript', cts = 'typescript', tsx = 'typescriptreact', + tsp = 'typespec', uc = 'uc', uit = 'uil', uil = 'uil', -- cgit From a500c5f808ccf0b678c935f00e0af4503a5bd724 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 5 Apr 2024 18:04:45 +0800 Subject: vim-patch:8.1.0815: dialog for file changed outside of Vim not tested (#28184) Problem: Dialog for file changed outside of Vim not tested. Solution: Add a test. Move FileChangedShell test. Add 'L' flag to feedkeys(). https://github.com/vim/vim/commit/5e66b42aae7c67a3ef67617d4bd43052ac2b73ce Co-authored-by: Bram Moolenaar --- runtime/lua/vim/_meta/vimfn.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index caddd4dde2..887c4b3cbe 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1959,6 +1959,7 @@ function vim.fn.extendnew(expr1, expr2, expr3) end --- 't' Handle keys as if typed; otherwise they are handled as --- if coming from a mapping. This matters for undo, --- opening folds, etc. +--- 'L' Lowlevel input. Other flags are not used. --- 'i' Insert the string instead of appending (see above). --- 'x' Execute commands until typeahead is empty. This is --- similar to using ":normal!". You can call feedkeys() -- cgit From 9711370c26453f3a966b9306111939b144248b41 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 5 Apr 2024 18:08:54 +0800 Subject: feat(defaults): add :Inspect to right-click menu (#28181) Ref #21393 - Move default user commands to _defaults.lua as that now contains all kinds of defaults rather than just default mappings and menus. - Remove the :aunmenu as there are no menus when _defaults.lua is run. --- runtime/lua/vim/_defaults.lua | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 6223082622..0798ca8d16 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -1,3 +1,31 @@ +--- Default user commands +do + vim.api.nvim_create_user_command('Inspect', function(cmd) + if cmd.bang then + vim.print(vim.inspect_pos()) + else + vim.show_pos() + end + end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true }) + + vim.api.nvim_create_user_command('InspectTree', function(cmd) + if cmd.mods ~= '' or cmd.count ~= 0 then + local count = cmd.count ~= 0 and cmd.count or '' + local new = cmd.mods ~= '' and 'new' or 'vnew' + + vim.treesitter.inspect_tree({ + command = ('%s %s%s'):format(cmd.mods, count, new), + }) + else + vim.treesitter.inspect_tree() + end + end, { desc = 'Inspect treesitter language tree for buffer', count = true }) + + vim.api.nvim_create_user_command('EditQuery', function(cmd) + vim.treesitter.query.edit(cmd.fargs[1]) + end, { desc = 'Edit treesitter query', nargs = '?' }) +end + --- Default mappings do --- Default maps for * and # in visual mode. @@ -93,7 +121,6 @@ do --- Right click popup menu -- TODO VimScript, no l10n vim.cmd([[ - aunmenu * vnoremenu PopUp.Cut "+x vnoremenu PopUp.Copy "+y anoremenu PopUp.Paste "+gP @@ -102,6 +129,7 @@ do nnoremenu PopUp.Select\ All ggVG vnoremenu PopUp.Select\ All gg0oG$ inoremenu PopUp.Select\ All VG + anoremenu PopUp.Inspect Inspect anoremenu PopUp.-1- anoremenu PopUp.How-to\ disable\ mouse help disable-mouse ]]) -- cgit From 9af355964306977eab836ca5324ed70d685c0c8e Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 5 Apr 2024 13:24:39 +0200 Subject: feat(lsp): set workDoneToken in initialize request (#28182) Problem: Some servers don't report progress during initialize unless the client sets the `workDoneToken` See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initiatingWorkDoneProgress In particular: > There is no specific client capability signaling whether a client will > send a progress token per request. The reason for this is that this is > in many clients not a static aspect and might even change for every > request instance for the same request type. So the capability is signal > on every request instance by the presence of a workDoneToken property. And: > Servers can also initiate progress reporting using the > window/workDoneProgress/create request. This is useful if the server > needs to report progress outside of a request (for example the server > needs to re-index a database). The token can then be used to report > progress using the same notifications used as for client initiated > progress. So far progress report functionality was relying entirely on the latter. Solution: Set a `workDoneToken` Closes https://github.com/neovim/neovim/issues/27938 --- runtime/lua/vim/lsp/client.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index f73f97b8cd..09064b9510 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -579,6 +579,7 @@ function Client:initialize() initializationOptions = config.init_options, capabilities = self.capabilities, trace = self._trace, + workDoneToken = '1', } self:_run_callbacks( -- cgit From 73de98256cf3932dca156fbfd0c82c1cc10d487e Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 4 Apr 2024 18:10:12 +0300 Subject: feat(comment): add built-in commenting Design - Enable commenting support only through `gc` mappings for simplicity. No ability to configure, no Lua module, no user commands. Yet. - Overall implementation is a simplified version of 'mini.comment' module of 'echasnovski/mini.nvim' adapted to be a better suit for core. It basically means reducing code paths which use only specific fixed set of plugin config. All used options are default except `pad_comment_parts = false`. This means that 'commentstring' option is used as is without forcing single space inner padding. As 'tpope/vim-commentary' was considered for inclusion earlier, here is a quick summary of how this commit differs from it: - **User-facing features**. Both implement similar user-facing mappings. This commit does not include `gcu` which is essentially a `gcgc`. There are no commands, events, or configuration in this commit. - **Size**. Both have reasonably comparable number of lines of code, while this commit has more comments in tricky areas. - **Maintainability**. This commit has (purely subjectively) better readability, tests, and Lua types. - **Configurability**. This commit has no user configuration, while 'vim-commentary' has some (partially as a counter-measure to possibly modifying 'commentstring' option). - **Extra features**: - This commit supports tree-sitter by computing `'commentstring'` option under cursor, which can matter in presence of tree-sitter injected languages. - This commit comments blank lines while 'tpope/vim-commentary' does not. At the same time, blank lines are not taken into account when deciding the toggle action. - This commit has much better speed on larger chunks of lines (like above 1000). This is thanks to using `nvim_buf_set_lines()` to set all new lines at once, and not with `vim.fn.setline()`. --- runtime/lua/vim/_comment.lua | 266 ++++++++++++++++++++++++++++++++++++++++++ runtime/lua/vim/_defaults.lua | 18 +++ 2 files changed, 284 insertions(+) create mode 100644 runtime/lua/vim/_comment.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua new file mode 100644 index 0000000000..e9cd662c9d --- /dev/null +++ b/runtime/lua/vim/_comment.lua @@ -0,0 +1,266 @@ +---@nodoc +---@class vim._comment.Parts +---@field left string Left part of comment +---@field right string Right part of comment + +--- Get 'commentstring' at cursor +---@param ref_position integer[] +---@return string +local function get_commentstring(ref_position) + local buf_cs = vim.bo.commentstring + + local has_ts_parser, ts_parser = pcall(vim.treesitter.get_parser) + if not has_ts_parser then + return buf_cs + end + + -- Try to get 'commentstring' associated with local tree-sitter language. + -- This is useful for injected languages (like markdown with code blocks). + local row, col = ref_position[1] - 1, ref_position[2] + local ref_range = { row, col, row, col + 1 } + + -- - Get 'commentstring' from the deepest LanguageTree which both contains + -- reference range and has valid 'commentstring' (meaning it has at least + -- one associated 'filetype' with valid 'commentstring'). + -- In simple cases using `parser:language_for_range()` would be enough, but + -- it fails for languages without valid 'commentstring' (like 'comment'). + local ts_cs, res_level = nil, 0 + + ---@param lang_tree vim.treesitter.LanguageTree + local function traverse(lang_tree, level) + if not lang_tree:contains(ref_range) then + return + end + + local lang = lang_tree:lang() + local filetypes = vim.treesitter.language.get_filetypes(lang) + for _, ft in ipairs(filetypes) do + local cur_cs = vim.filetype.get_option(ft, 'commentstring') + if cur_cs ~= '' and level > res_level then + ts_cs = cur_cs + end + end + + for _, child_lang_tree in pairs(lang_tree:children()) do + traverse(child_lang_tree, level + 1) + end + end + traverse(ts_parser, 1) + + return ts_cs or buf_cs +end + +--- Compute comment parts from 'commentstring' +---@param ref_position integer[] +---@return vim._comment.Parts +local function get_comment_parts(ref_position) + local cs = get_commentstring(ref_position) + + if cs == nil or cs == '' then + vim.api.nvim_echo({ { "Option 'commentstring' is empty.", 'WarningMsg' } }, true, {}) + return { left = '', right = '' } + end + + if not (type(cs) == 'string' and cs:find('%%s') ~= nil) then + error(vim.inspect(cs) .. " is not a valid 'commentstring'.") + end + + -- Structure of 'commentstring': <%s> + local left, right = cs:match('^(.-)%%s(.-)$') + return { left = left, right = right } +end + +--- Make a function that checks if a line is commented +---@param parts vim._comment.Parts +---@return fun(line: string): boolean +local function make_comment_check(parts) + local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) + + -- Commented line has the following structure: + -- + local nonblank_regex = '^%s-' .. l_esc .. '.*' .. r_esc .. '%s-$' + + -- Commented blank line can have any amoung of whitespace around parts + local blank_regex = '^%s-' .. vim.trim(l_esc) .. '%s*' .. vim.trim(r_esc) .. '%s-$' + + return function(line) + return line:find(nonblank_regex) ~= nil or line:find(blank_regex) ~= nil + end +end + +--- Compute comment-related information about lines +---@param lines string[] +---@param parts vim._comment.Parts +---@return string indent +---@return boolean is_commented +local function get_lines_info(lines, parts) + local comment_check = make_comment_check(parts) + + local is_commented = true + local indent_width = math.huge + ---@type string + local indent + + for _, l in ipairs(lines) do + -- Update lines indent: minimum of all indents except blank lines + local _, indent_width_cur, indent_cur = l:find('^(%s*)') + + -- Ignore blank lines completely when making a decision + if indent_width_cur < l:len() then + -- NOTE: Copying actual indent instead of recreating it with `indent_width` + -- allows to handle both tabs and spaces + if indent_width_cur < indent_width then + ---@diagnostic disable-next-line:cast-local-type + indent_width, indent = indent_width_cur, indent_cur + end + + -- Update comment info: commented if every non-blank line is commented + if is_commented then + is_commented = comment_check(l) + end + end + end + + -- `indent` can still be `nil` in case all `lines` are empty + return indent or '', is_commented +end + +--- Compute whether a string is blank +---@param x string +---@return boolean is_blank +local function is_blank(x) + return x:find('^%s*$') ~= nil +end + +--- Make a function which comments a line +---@param parts vim._comment.Parts +---@param indent string +---@return fun(line: string): string +local function make_comment_function(parts, indent) + local prefix, nonindent_start, suffix = indent .. parts.left, indent:len() + 1, parts.right + local blank_comment = indent .. vim.trim(parts.left) .. vim.trim(parts.right) + + return function(line) + if is_blank(line) then + return blank_comment + end + return prefix .. line:sub(nonindent_start) .. suffix + end +end + +--- Make a function which uncomments a line +---@param parts vim._comment.Parts +---@return fun(line: string): string +local function make_uncomment_function(parts) + local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) + local nonblank_regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$' + local blank_regex = '^(%s*)' .. vim.trim(l_esc) .. '(%s*)' .. vim.trim(r_esc) .. '(%s-)$' + + return function(line) + -- Try both non-blank and blank regexes + local indent, new_line, trail = line:match(nonblank_regex) + if new_line == nil then + indent, new_line, trail = line:match(blank_regex) + end + + -- Return original if line is not commented + if new_line == nil then + return line + end + + -- Prevent trailing whitespace + if is_blank(new_line) then + indent, trail = '', '' + end + + return indent .. new_line .. trail + end +end + +--- Comment/uncomment buffer range +---@param line_start integer +---@param line_end integer +---@param ref_position? integer[] +local function toggle_lines(line_start, line_end, ref_position) + ref_position = ref_position or { line_start, 0 } + local parts = get_comment_parts(ref_position) + local lines = vim.api.nvim_buf_get_lines(0, line_start - 1, line_end, false) + local indent, is_comment = get_lines_info(lines, parts) + + local f = is_comment and make_uncomment_function(parts) or make_comment_function(parts, indent) + + -- Direct `nvim_buf_set_lines()` essentially removes both regular and + -- extended marks (squashes to empty range at either side of the region) + -- inside region. Use 'lockmarks' to preserve regular marks. + -- Preserving extmarks is not a universally good thing to do: + -- - Good for non-highlighting in text area extmarks (like showing signs). + -- - Debatable for highlighting in text area (like LSP semantic tokens). + -- Mostly because it causes flicker as highlighting is preserved during + -- comment toggling. + package.loaded['vim._comment']._lines = vim.tbl_map(f, lines) + local lua_cmd = string.format( + 'vim.api.nvim_buf_set_lines(0, %d, %d, false, package.loaded["vim._comment"]._lines)', + line_start - 1, + line_end + ) + vim.cmd.lua({ lua_cmd, mods = { lockmarks = true } }) + package.loaded['vim._comment']._lines = nil +end + +--- Operator which toggles user-supplied range of lines +---@param mode string? +---|"'line'" +---|"'char'" +---|"'block'" +local function operator(mode) + -- Used without arguments as part of expression mapping. Otherwise it is + -- called as 'operatorfunc'. + if mode == nil then + vim.o.operatorfunc = "v:lua.require'vim._comment'.operator" + return 'g@' + end + + -- Compute target range + local mark_from, mark_to = "'[", "']" + local lnum_from, col_from = vim.fn.line(mark_from), vim.fn.col(mark_from) + local lnum_to, col_to = vim.fn.line(mark_to), vim.fn.col(mark_to) + + -- Do nothing if "from" mark is after "to" (like in empty textobject) + if (lnum_from > lnum_to) or (lnum_from == lnum_to and col_from > col_to) then + return + end + + -- NOTE: use cursor position as reference for possibly computing local + -- tree-sitter-based 'commentstring'. Recompute every time for a proper + -- dot-repeat. In Visual and sometimes Normal mode it uses start position. + toggle_lines(lnum_from, lnum_to, vim.api.nvim_win_get_cursor(0)) + return '' +end + +--- Select contiguous commented lines at cursor +local function textobject() + local lnum_cur = vim.fn.line('.') + local parts = get_comment_parts({ lnum_cur, vim.fn.col('.') }) + local comment_check = make_comment_check(parts) + + if not comment_check(vim.fn.getline(lnum_cur)) then + return + end + + -- Compute commented range + local lnum_from = lnum_cur + while (lnum_from >= 2) and comment_check(vim.fn.getline(lnum_from - 1)) do + lnum_from = lnum_from - 1 + end + + local lnum_to = lnum_cur + local n_lines = vim.api.nvim_buf_line_count(0) + while (lnum_to <= n_lines - 1) and comment_check(vim.fn.getline(lnum_to + 1)) do + lnum_to = lnum_to + 1 + end + + -- Select range linewise for operator to act upon + vim.cmd('normal! ' .. lnum_from .. 'GV' .. lnum_to .. 'G') +end + +return { operator = operator, textobject = textobject, toggle_lines = toggle_lines } diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 0798ca8d16..90d05f67a5 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -114,6 +114,24 @@ do do_open(table.concat(vim.iter(lines):map(vim.trim):totable())) end, { desc = gx_desc }) end + + --- Default maps for built-in commenting + do + local operator_rhs = function() + return require('vim._comment').operator() + end + vim.keymap.set({ 'n', 'x' }, 'gc', operator_rhs, { expr = true, desc = 'Toggle comment' }) + + local line_rhs = function() + return require('vim._comment').operator() .. '_' + end + vim.keymap.set('n', 'gcc', line_rhs, { expr = true, desc = 'Toggle comment line' }) + + local textobject_rhs = function() + require('vim._comment').textobject() + end + vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) + end end --- Default menus -- cgit From 0443f06b71c7a1563657765b35f7f3869300edc7 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 5 Apr 2024 18:07:36 +0200 Subject: docs: don't mention executable() can return -1 This cannot happen for neovim. --- runtime/lua/vim/_meta/vimfn.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 887c4b3cbe..c114c3b212 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -1598,11 +1598,10 @@ function vim.fn.eventhandler() end --- The result is a Number: --- 1 exists --- 0 does not exist ---- -1 not implemented on this system --- |exepath()| can be used to get the full path of an executable. --- --- @param expr any ---- @return 0|1|-1 +--- @return 0|1 function vim.fn.executable(expr) end --- Execute {command} and capture its output. -- cgit From 7560aee59547c3b050c3ee9988dd0e4d36d00144 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 6 Apr 2024 21:11:57 +0800 Subject: vim-patch:9.1.0266: filetype: earthfile files are not recognized (#28207) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: filetype: earthfile files are not recognized Solution: Detect 'Earthfile' as earthfile (Gaëtan Lehmann) closes: vim/vim#14408 https://github.com/vim/vim/commit/28e5e7c48483254604506dbce5eb61396ff65808 Co-authored-by: Gaëtan Lehmann --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1198a9972f..9b1fd80b82 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1293,6 +1293,7 @@ local filename = { jbuild = 'dune', ['dune-workspace'] = 'dune', ['dune-project'] = 'dune', + Earthfile = 'earthfile', ['.editorconfig'] = 'editorconfig', ['elinks.conf'] = 'elinks', ['mix.lock'] = 'elixir', -- cgit From 9dd112dd4821a325a6c1c8d952a537f42f9c728c Mon Sep 17 00:00:00 2001 From: dundargoc Date: Tue, 2 Apr 2024 12:36:13 +0200 Subject: refactor: remove fn_bool It's better to use vim.fn directly instead of creating minor abstractions like fn_bool. --- runtime/lua/vim/health.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index f6f7abef8f..9e9c557ad3 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -386,7 +386,7 @@ local path2name = function(path) path = path:gsub('^.*/lua/', '') -- Remove the filename (health.lua) - path = vim.fn.fnamemodify(path, ':h') + path = vim.fs.dirname(path) -- Change slashes to dots path = path:gsub('/', '.') @@ -497,11 +497,4 @@ function M._check(mods, plugin_names) vim.print('') end -local fn_bool = function(key) - return function(...) - return vim.fn[key](...) == 1 - end -end -M.executable = fn_bool('executable') - return M -- cgit From d32cbef59551a1808caea2ddaeac323fdc18d6b6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 8 Apr 2024 06:11:31 +0800 Subject: vim-patch:9cd9e759ab1e (#28224) runtime(doc): Normalise builtin-function optional parameter formatting These should generally be formatted as func([{arg}]) and referenced as {arg} in the description. closes: vim/vim#14438 https://github.com/vim/vim/commit/9cd9e759ab1e6e6adb24a23648eed41e4d94d522 Co-authored-by: Doug Kearns --- runtime/lua/vim/_meta/vimfn.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index c114c3b212..e3c7495cf9 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2703,14 +2703,14 @@ function vim.fn.getcellwidths() end function vim.fn.getchangelist(buf) end --- Get a single character from the user or input stream. ---- If [expr] is omitted, wait until a character is available. ---- If [expr] is 0, only get a character when one is available. +--- If {expr} is omitted, wait until a character is available. +--- If {expr} is 0, only get a character when one is available. --- Return zero otherwise. ---- If [expr] is 1, only check if a character is available, it is +--- If {expr} is 1, only check if a character is available, it is --- not consumed. Return zero if no character available. --- If you prefer always getting a string use |getcharstr()|. --- ---- Without [expr] and when [expr] is 0 a whole character or +--- Without {expr} and when {expr} is 0 a whole character or --- special key is returned. If it is a single character, the --- result is a Number. Use |nr2char()| to convert it to a String. --- Otherwise a String is returned with the encoded character. @@ -2720,11 +2720,11 @@ function vim.fn.getchangelist(buf) end --- also a String when a modifier (shift, control, alt) was used --- that is not included in the character. --- ---- When [expr] is 0 and Esc is typed, there will be a short delay +--- When {expr} is 0 and Esc is typed, there will be a short delay --- while Vim waits to see if this is the start of an escape --- sequence. --- ---- When [expr] is 1 only the first byte is returned. For a +--- When {expr} is 1 only the first byte is returned. For a --- one-byte character it is the character itself as a number. --- Use nr2char() to convert it to a String. --- @@ -2828,10 +2828,10 @@ function vim.fn.getcharsearch() end --- Get a single character from the user or input stream as a --- string. ---- If [expr] is omitted, wait until a character is available. ---- If [expr] is 0 or false, only get a character when one is +--- If {expr} is omitted, wait until a character is available. +--- If {expr} is 0 or false, only get a character when one is --- available. Return an empty string otherwise. ---- If [expr] is 1 or true, only check if a character is +--- If {expr} is 1 or true, only check if a character is --- available, it is not consumed. Return an empty string --- if no character is available. --- Otherwise this works like |getchar()|, except that a number @@ -5993,7 +5993,7 @@ function vim.fn.min(expr) end function vim.fn.mkdir(name, flags, prot) end --- Return a string that indicates the current mode. ---- If [expr] is supplied and it evaluates to a non-zero Number or +--- If {expr} is supplied and it evaluates to a non-zero Number or --- a non-empty String (|non-zero-arg|), then the full mode is --- returned, otherwise only the first letter is returned. --- Also see |state()|. -- cgit From 541c2d3816cdef9aba7f55f74908fa07d0cfa3ec Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 7 Apr 2024 18:39:14 +0200 Subject: vim-patch:9.1.0273: filetype: keymap files are not recognized Problem: filetype: keymap files are not recognized Solution: Detect '*.keymap' files as Device Tree Files (0xadk) closes: vim/vim#14434 https://github.com/vim/vim/commit/b78753db5fac879a76da3519101e815451d0d455 Co-authored-by: 0xadk <0xadk@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 9b1fd80b82..083ad9f24f 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -367,6 +367,7 @@ local extension = { dtsi = 'dts', dtso = 'dts', its = 'dts', + keymap = 'dts', dylan = 'dylan', intr = 'dylanintr', lid = 'dylanlid', -- cgit From bef4ad6507a53db8570909de954a538b50b97a30 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 8 Apr 2024 22:39:30 +0200 Subject: vim-patch:9.1.0279: filetype: roc files are not recognized Problem: filetype: roc files are not recognized Solution: Detect '*.roc' files as roc filetype, add a basic filetype plugin (nat-418) closes: vim/vim#14416 https://github.com/vim/vim/commit/196b6678c5483217ea5bc7d047b02c915615dae6 Co-authored-by: nat-418 <93013864+nat-418@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 083ad9f24f..1cbefc842f 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -887,6 +887,7 @@ local extension = { Snw = 'rnoweb', robot = 'robot', resource = 'robot', + roc = 'roc', ron = 'ron', rsc = 'routeros', x = 'rpcgen', -- cgit From 2857cde07026f6583b68fa650b7bd40151d79521 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 8 Apr 2024 22:41:20 +0200 Subject: vim-patch:9.1.0275: filetype: R history files are not recognized Problem: filetype: R history files are not recognized Solution: Detect '.Rhistory' files as r filetype (Wu, Zhenyu) closes: vim/vim#14440 https://github.com/vim/vim/commit/fc21b6437ce91368c2d53437177083b8bc375720 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1cbefc842f..d0e31df3a1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1474,6 +1474,7 @@ local filename = { ['.pythonrc'] = 'python', SConstruct = 'python', qmldir = 'qmldir', + ['.Rhistory'] = 'r', ['.Rprofile'] = 'r', Rprofile = 'r', ['Rprofile.site'] = 'r', -- cgit From cbe982bbd5a09b1337e5b3ffcbd15a34cec2db78 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 8 Apr 2024 22:51:09 +0200 Subject: vim-patch:9.1.0278: filetype: zathurarc files not recognized Problem: filetype: zathurarc files not recognized Solution: Detect '.zathurarc' files as zathurarc filetype, add zathurarc filetype (Wu, Zhenyu) closes: vim/vim#14380 https://github.com/vim/vim/commit/72d81a66edd835aeff3f539ccd0f97afb1ebd63c Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d0e31df3a1..9f1d237884 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1578,6 +1578,7 @@ local filename = { ['.clang-format'] = 'yaml', ['.clang-tidy'] = 'yaml', ['yarn.lock'] = 'yaml', + zathurarc = 'zathurarc', ['/etc/zprofile'] = 'zsh', ['.zlogin'] = 'zsh', ['.zlogout'] = 'zsh', -- cgit From 41521658b1dca302a4c7125753694b59a317c0f9 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 8 Apr 2024 22:42:54 +0200 Subject: vim-patch:9.1.0276: No pandoc syntax support Problem: No pandoc syntax support Solution: Add pandoc syntax and compiler plugins (Wu, Zhenyu, Konfekt) closes: vim/vim#14389 https://github.com/vim/vim/commit/7005b7ee7f282b24378c2a844366cb8616cad5d7 Co-authored-by: Wu, Zhenyu Co-authored-by: Konfekt --- runtime/lua/vim/filetype.lua | 16 ++++++++++------ runtime/lua/vim/filetype/detect.lua | 5 +++++ 2 files changed, 15 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 9f1d237884..bbe1e2c89c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -662,12 +662,12 @@ local extension = { mws = 'maple', mpl = 'maple', mv = 'maple', - mkdn = 'markdown', - md = 'markdown', - mdwn = 'markdown', - mkd = 'markdown', - markdown = 'markdown', - mdown = 'markdown', + mkdn = detect.markdown, + md = detect.markdown, + mdwn = detect.markdown, + mkd = detect.markdown, + markdown = detect.markdown, + mdown = detect.markdown, mhtml = 'mason', comp = 'mason', mason = 'mason', @@ -762,6 +762,10 @@ local extension = { ora = 'ora', org = 'org', org_archive = 'org', + pandoc = 'pandoc', + pdk = 'pandoc', + pd = 'pandoc', + pdc = 'pandoc', pxsl = 'papp', papp = 'papp', pxml = 'papp', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 3db4f2bcdc..ca2c53b75d 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -889,6 +889,11 @@ local function m4(contents) end end +--- @type vim.filetype.mapfn +function M.markdown(_, _) + return vim.g.filetype_md or 'markdown' +end + --- Rely on the file to start with a comment. --- MS message text files use ';', Sendmail files use '#' or 'dnl' --- @type vim.filetype.mapfn -- cgit From b95b6ed9753da8f157d3ba34408c4c2e0de9d744 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Wed, 10 Apr 2024 18:23:47 +0800 Subject: fix(lsp): empty commands should not be considered executable (#28216) According to the LSP specification, the CodeLens.command is optional but the CodeLens.command.command is not optional, which means the correct representation of a display-only code lens is indeed one with a command with a title to display and an empty string as command. --- runtime/lua/vim/lsp/codelens.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index d2557ca9d7..90ca83cd59 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -79,7 +79,7 @@ function M.run() local lenses_by_client = lens_cache_by_buf[bufnr] or {} for client, lenses in pairs(lenses_by_client) do for _, lens in pairs(lenses) do - if lens.range.start.line == (line - 1) then + if lens.range.start.line == (line - 1) and lens.command and lens.command.command ~= '' then table.insert(options, { client = client, lens = lens }) end end -- cgit From 1dacf2ecee36e2abd490df2ad5f9c24fa73205b7 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Wed, 10 Apr 2024 18:27:37 +0800 Subject: fix(lsp): prevent code-lens refresh from becoming a permanent no-op (#28228) To avoid repeatedly requesting a buffer multiple times before a request is completed, the current implementation puts the requested buffer into the active_refreshes table before requesting. But since we only remove the buffer from active_refreshes in the lsp-handler of textDocument/codeLens, this will cause if the user sends a request that cannot trigger lsp-handler (for example, if there is an LSP server attached to the current buffer, and especially when the user creates an autocmd which performs vim.lsp.codelens.refresh after the BufEnter event is triggered like in the document example), this buffer will be put into active_refreshes, and there is no way to remove it, which will result in all subsequent vim.lsp.codelens.refresh not requesting textDocument/codeLens. --- runtime/lua/vim/lsp/codelens.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 90ca83cd59..a2a0c15b93 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -231,7 +231,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback) countdown() else assert(client) - client.request('codeLens/resolve', lens, function(_, result) + client.request(ms.codeLens_resolve, lens, function(_, result) if api.nvim_buf_is_loaded(bufnr) and result and result.command then lens.command = result.command -- Eager display to have some sort of incremental feedback @@ -306,7 +306,11 @@ function M.refresh(opts) textDocument = util.make_text_document_params(buf), } active_refreshes[buf] = true - vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens) + + local request_ids = vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens) + if vim.tbl_isempty(request_ids) then + active_refreshes[buf] = nil + end end end end -- cgit From 00e6651880c32a9878797eeeaef7018c3d5d99b7 Mon Sep 17 00:00:00 2001 From: altermo <107814000+altermo@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:52:51 +0200 Subject: fix(treesitter): use tree range instead of tree root node range --- runtime/lua/vim/treesitter/languagetree.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 8f65cb57c3..990debc77b 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -1100,7 +1100,14 @@ end ---@param range Range ---@return boolean local function tree_contains(tree, range) - return Range.contains({ tree:root():range() }, range) + local tree_ranges = tree:included_ranges(false) + + return Range.contains({ + tree_ranges[1][1], + tree_ranges[1][2], + tree_ranges[#tree_ranges][3], + tree_ranges[#tree_ranges][4], + }, range) end --- Determines whether {range} is contained in the |LanguageTree|. -- cgit From 8cca7871555e15f53de3dc4325c8d0203a78622a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 09:16:13 +0800 Subject: test: macros in Visual mode without default mappings (#28288) --- runtime/lua/vim/_defaults.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 90d05f67a5..2f02c2b389 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -93,6 +93,7 @@ do "':normal! @'.getcharstr().''", { silent = true, expr = true, desc = ':help v_@-default' } ) + --- Map |gx| to call |vim.ui.open| on the identifier under the cursor do local function do_open(uri) -- cgit From 5aee5879703eeaf65896059b70b41988d3af6dca Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:02:25 +0800 Subject: vim-patch:9.1.0289: filetype: some TeX files are not recognized (#28291) Problem: filetype: some TeX files are not recognized Solution: Add more patterns for TeX files and inspect a few more files for being TeX files (Wu, Zhenyu) closes: vim/vim#14456 https://github.com/vim/vim/commit/61ee833a504ae73bc6b3e2527a81582263f02afd Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 26 ++++++++++++++++++++++++-- runtime/lua/vim/filetype/detect.lua | 7 ++++++- 2 files changed, 30 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bbe1e2c89c..06761fefe9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -252,7 +252,7 @@ local extension = { capnp = 'capnp', cdc = 'cdc', cdl = 'cdl', - toc = 'cdrtoc', + toc = detect_line1('\\contentsline', 'tex', 'cdrtoc'), cfc = 'cf', cfm = 'cf', cfi = 'cf', @@ -631,7 +631,7 @@ local extension = { livemd = 'livebook', lgt = 'logtalk', lotos = 'lotos', - lot = 'lotos', + lot = detect_line1('\\contentsline', 'tex', 'lotos'), lout = 'lout', lou = 'lout', ulpc = 'lpc', @@ -1035,6 +1035,27 @@ local extension = { bbl = 'tex', latex = 'tex', sty = 'tex', + pgf = 'tex', + nlo = 'tex', + nls = 'tex', + out = 'tex', + thm = 'tex', + eps_tex = 'tex', + pygtex = 'tex', + pygstyle = 'tex', + clo = 'tex', + aux = 'tex', + brf = 'tex', + ind = 'tex', + lof = 'tex', + loe = 'tex', + nav = 'tex', + vrb = 'tex', + ins = 'tex', + tikz = 'tex', + bbx = 'tex', + cbx = 'tex', + beamer = 'tex', cls = detect.cls, texi = 'texinfo', txi = 'texinfo', @@ -2023,6 +2044,7 @@ local pattern = { ['.*termcap.*'] = starsetf(function(path, bufnr) return require('vim.filetype.detect').printcap('term') end), + ['.*/tex/latex/.*%.cfg'] = 'tex', ['.*%.t%.html'] = 'tilde', ['%.?tmux.*%.conf'] = 'tmux', ['%.?tmux.*%.conf.*'] = { 'tmux', { priority = -1 } }, diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index ca2c53b75d..c48984d151 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -458,6 +458,9 @@ end --- @type vim.filetype.mapfn function M.def(_, bufnr) + if getline(bufnr, 1):find('%%%%') then + return 'tex' + end if vim.g.filetype_def == 'modula2' or is_modula2(bufnr) then return modula2(bufnr) end @@ -738,7 +741,9 @@ end --- @type vim.filetype.mapfn function M.inp(_, bufnr) - if getline(bufnr, 1):find('^%*') then + if getline(bufnr, 1):find('%%%%') then + return 'tex' + elseif getline(bufnr, 1):find('^%*') then return 'abaqus' else for _, line in ipairs(getlines(bufnr, 1, 500)) do -- cgit From da3059b00f96979691a420835f47d53fbef8db05 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:03:45 +0800 Subject: vim-patch:9.1.0290: filetype: xilinx files are not recognized (#28295) Problem: filetype: xilinx files are not recognized Solution: Add a few xilinx specific file patterns, inspect lpr files for being xml/pascal (Wu, Zhenyu) closes: vim/vim#14454 https://github.com/vim/vim/commit/614691ceefb2b2470cd9097013ffc140f81d6a71 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 06761fefe9..d7d34b625d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -582,6 +582,9 @@ local extension = { ['sublime-settings'] = 'json', ['sublime-workspace'] = 'json', ['json-patch'] = 'json', + bd = 'json', + bda = 'json', + xci = 'json', json5 = 'json5', jsonc = 'jsonc', jsonl = 'jsonl', @@ -770,7 +773,7 @@ local extension = { papp = 'papp', pxml = 'papp', pas = 'pascal', - lpr = 'pascal', + lpr = detect_line1('<%?xml', 'xml', 'pascal'), dpr = 'pascal', pbtxt = 'pbtxt', g = 'pccts', @@ -1156,6 +1159,10 @@ local extension = { csproj = 'xml', wpl = 'xml', xmi = 'xml', + xpr = 'xml', + xpfm = 'xml', + spfm = 'xml', + bxml = 'xml', xpm = detect_line1('XPM2', 'xpm2', 'xpm'), xpm2 = 'xpm2', xqy = 'xquery', -- cgit From a93a045e63f96ba0b2b7f005818ddf16c2640038 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:05:11 +0800 Subject: vim-patch:9.1.0302: filetype: blueprint files are not recognized (#28292) Problem: filetype: blueprint files are not recognized Solution: Detect '*.bp' files as blueprint files, add a minimal filetype plugin (Bruno Belanyi) See: https://source.android.com/docs/setup/build closes: vim/vim#14488 https://github.com/vim/vim/commit/6be7ef5bc734ce6045d6f919f1a8559a3fa7f2fd Co-authored-by: Bruno BELANYI --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d7d34b625d..7de2a95c6b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -236,6 +236,7 @@ local extension = { bbclass = 'bitbake', bl = 'blank', blp = 'blueprint', + bp = 'bp', bsd = 'bsdl', bsdl = 'bsdl', bst = 'bst', -- cgit From e4fb3e200768763fbff8668c19b2011d6c8309aa Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:06:21 +0800 Subject: vim-patch:9.1.0303: filetype: some protocol buffer files not recognized (#28293) Problem: filetype: some protocol buffer files not recognized Solution: Detect '*.textproto', '*.textpb', '*.txtpb' as pbtxt files (Bruno Belanyi) See: https://protobuf.dev/reference/protobuf/textformat-spec/#text-format-files closes: vim/vim#14463 https://github.com/vim/vim/commit/e54a8e7c73bbfba0c77e928f27fb3a9bffd2e8fd Co-authored-by: Bruno BELANYI --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 7de2a95c6b..d2c6d87774 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -776,6 +776,9 @@ local extension = { pas = 'pascal', lpr = detect_line1('<%?xml', 'xml', 'pascal'), dpr = 'pascal', + txtpb = 'pbtxt', + textproto = 'pbtxt', + textpb = 'pbtxt', pbtxt = 'pbtxt', g = 'pccts', pcmk = 'pcmk', -- cgit From 611cc7de4373bc2777705d9b977b3ae1bdd2ea90 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:07:24 +0800 Subject: vim-patch:9.1.0304: filetype: cgdb config file is not recognized (#28294) Problem: filetype: cgdb config file is not recognized Solution: Detect cgdbrc files as cgdbrc filetype (Wu, Zhenyu) closes: vim/vim#14458 https://github.com/vim/vim/commit/1492fe69037586b6c625d42205d77dd38ba51640 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d2c6d87774..c0c64be94b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1289,6 +1289,7 @@ local filename = { ['/etc/default/cdrdao'] = 'cdrdaoconf', ['/etc/defaults/cdrdao'] = 'cdrdaoconf', ['cfengine.conf'] = 'cfengine', + cgdbrc = 'cgdbrc', ['CMakeLists.txt'] = 'cmake', ['.alias'] = detect.csh, ['.cshrc'] = detect.csh, -- cgit From 07c5969aa90e7a275f6606f25f22b848152f75d8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:12:43 +0800 Subject: vim-patch:9.1.0291: filetype: libreoffice config files are not recognized Problem: filetype: libreoffice config files are not recognized Solution: Detect Libreoffice config fils as xml/dosini (Wu, Zhenyu) closes: vim/vim#14453 https://github.com/vim/vim/commit/73c89bcf79df280b8698f77374afabd9494dc741 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c0c64be94b..06804559dd 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1167,6 +1167,10 @@ local extension = { xpfm = 'xml', spfm = 'xml', bxml = 'xml', + xcu = 'xml', + xlb = 'xml', + xlc = 'xml', + xba = 'xml', xpm = detect_line1('XPM2', 'xpm2', 'xpm'), xpm2 = 'xpm2', xqy = 'xquery', @@ -1326,6 +1330,8 @@ local filename = { ['setup.cfg'] = 'dosini', ['pudb.cfg'] = 'dosini', ['.coveragerc'] = 'dosini', + ['psprint.conf'] = 'dosini', + sofficerc = 'dosini', ['/etc/pacman.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', -- cgit From b0ded426807f56cdd5331b8dcb9094d90917c89c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:14:55 +0800 Subject: vim-patch:9.1.0292: filetype: XDG mimeapps.list file is not recognized Problem: filetype: XDG mimeapps.list file is not recognized Solution: Detect mimeapps.list as dosini filetype (Wu, Zhenyu) Refer: https://wiki.archlinux.org/title/XDG_MIME_Applications#Format closes: vim/vim#14451 https://github.com/vim/vim/commit/efd752ec384980135c36b9fb673574e64c270c90 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 06804559dd..bccdbd1ba4 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1332,6 +1332,7 @@ local filename = { ['.coveragerc'] = 'dosini', ['psprint.conf'] = 'dosini', sofficerc = 'dosini', + ['mimeapps.list'] = 'dosini', ['/etc/pacman.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', -- cgit From 60ced890f38c8f717c9dae92a9bf2819f6b00ef8 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:15:54 +0800 Subject: vim-patch:9.1.0293: filetype: lxqt config files are not recognized Problem: filetype: lxqt config files are not recognized Solution: Detect {lxqt,screengrab}/*.conf files as dosini, fix failing filetype test for */tex/latex/**.cfg (Wu, Zhenyu) closes: vim/vim#14450 https://github.com/vim/vim/commit/41208884b8c1a73b42ddb6c1e5f008dae6aa0a83 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bccdbd1ba4..6dec2e6d16 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1735,6 +1735,8 @@ local pattern = { ['.*/%.aws/credentials'] = 'confini', ['.*/etc/pacman%.conf'] = 'confini', ['.*/etc/yum%.conf'] = 'dosini', + ['.*/lxqt/.*%.conf'] = 'dosini', + ['.*/screengrab/.*%.conf'] = 'dosini', ['.*lvs'] = 'dracula', ['.*lpe'] = 'dracula', ['.*/dtrace/.*%.d'] = 'dtrace', -- cgit From 7334e9055b279023014d0955f2f80a9a3f8a723b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:17:54 +0800 Subject: vim-patch:9.1.0295: filetype: pip config files are not recognized Problem: filetype: pip config files are not recognized Solution: detect pip.conf as dosini filetype (Wu, Zhenyu) closes: vim/vim#14448 https://github.com/vim/vim/commit/d2b95b8446233e0021a8c0cd672f8fae748e3955 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 6dec2e6d16..873e797e33 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1327,6 +1327,7 @@ local filename = { npmrc = 'dosini', ['/etc/yum.conf'] = 'dosini', ['.npmrc'] = 'dosini', + ['pip.conf'] = 'dosini', ['setup.cfg'] = 'dosini', ['pudb.cfg'] = 'dosini', ['.coveragerc'] = 'dosini', -- cgit From be7d8ff0e44a5364adee1c6313cdd375267a26f3 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:20:35 +0800 Subject: vim-patch:9.1.0306: filetype: x11vnc config file is not recognized Problem: filetype: x11vnc config file is not recognized Solution: Detect '.x11vncrc' as conf filetype (Wu, Zhenyu) See: https://linux.die.net/man/1/x11vnc closes: vim/vim#14511 https://github.com/vim/vim/commit/58ce78ad438deefec54fd6206166ca2794cd6efe Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 873e797e33..d8b818866c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1302,6 +1302,7 @@ local filename = { ['csh.login'] = detect.csh, ['csh.logout'] = detect.csh, ['auto.master'] = 'conf', + ['.x11vncrc'] = 'conf', ['configure.in'] = 'config', ['configure.ac'] = 'config', crontab = 'crontab', -- cgit From 8c112a823535eeb32f4490b2fff33d1cc36de004 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 17:21:37 +0800 Subject: vim-patch:9.1.0307: filetype: texdoc config files is not recognized Problem: filetype: texdoc config files is not recognized Solution: Detect 'texdoc.cnf' as conf filetype (Wu, Zhenyu) See: https://github.com/TeX-Live/texdoc/blob/master/texdoc.cnf closes: vim/vim#14507 https://github.com/vim/vim/commit/7fdbd1bb58192650bec067a0f224c1fa971c6782 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d8b818866c..b1d02f900d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1302,6 +1302,7 @@ local filename = { ['csh.login'] = detect.csh, ['csh.logout'] = detect.csh, ['auto.master'] = 'conf', + ['texdoc.cnf'] = 'conf', ['.x11vncrc'] = 'conf', ['configure.in'] = 'config', ['configure.ac'] = 'config', -- cgit From a629978cb684daa768e702de948be819a22dcb4d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 12 Apr 2024 18:08:54 +0800 Subject: vim-patch:9.1.0305: filetype: some history files are not recognized (#28300) Problem: filetype: some history files are not recognized Solution: Add some history patterns to filetype.vim (Wu, Zhenyu) closes: vim/vim#14513 https://github.com/vim/vim/commit/da70feabeab77581e48a7ca9c8d5f950c1c2814e Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index b1d02f900d..215be7fdc2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1295,6 +1295,7 @@ local filename = { ['cfengine.conf'] = 'cfengine', cgdbrc = 'cgdbrc', ['CMakeLists.txt'] = 'cmake', + ['.cling_history'] = 'cpp', ['.alias'] = detect.csh, ['.cshrc'] = detect.csh, ['.login'] = detect.csh, @@ -1410,6 +1411,7 @@ local filename = { ['ipf.conf'] = 'ipfilter', ['ipf6.conf'] = 'ipfilter', ['ipf.rules'] = 'ipfilter', + ['.node_repl_history'] = 'javascript', ['Pipfile.lock'] = 'json', ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', @@ -1440,12 +1442,14 @@ local filename = { ['.lsl'] = detect.lsl, ['.busted'] = 'lua', ['.luacheckrc'] = 'lua', + ['.lua_history'] = 'lua', ['config.ld'] = 'lua', ['rock_manifest'] = 'lua', ['lynx.cfg'] = 'lynx', ['m3overrides'] = 'm3build', ['m3makefile'] = 'm3build', ['cm3.cfg'] = 'm3quake', + ['.m4_history'] = 'm4', ['.followup'] = 'mail', ['.article'] = 'mail', ['.letter'] = 'mail', @@ -1519,6 +1523,8 @@ local filename = { ['MANIFEST.in'] = 'pymanifest', ['.pythonstartup'] = 'python', ['.pythonrc'] = 'python', + ['.python_history'] = 'python', + ['.jline-jython.history'] = 'python', SConstruct = 'python', qmldir = 'qmldir', ['.Rhistory'] = 'r', @@ -1537,6 +1543,8 @@ local filename = { Puppetfile = 'ruby', ['.irbrc'] = 'ruby', irbrc = 'ruby', + ['.irb_history'] = 'ruby', + irb_history = 'ruby', Vagrantfile = 'ruby', ['smb.conf'] = 'samba', screenrc = 'screen', @@ -1546,6 +1554,7 @@ local filename = { ['/etc/services'] = 'services', ['/etc/serial.conf'] = 'setserial', ['/etc/udev/cdsymlinks.conf'] = 'sh', + ['.ash_history'] = 'sh', ['bash.bashrc'] = detect.bash, bashrc = detect.bash, ['.bashrc'] = detect.bash, @@ -1562,6 +1571,7 @@ local filename = { ['/etc/slp.spi'] = 'slpspi', ['.slrnrc'] = 'slrnrc', ['sendmail.cf'] = 'sm', + ['.sqlite_history'] = 'sql', ['squid.conf'] = 'squid', ['ssh_config'] = 'sshconfig', ['sshd_config'] = 'sshdconfig', @@ -1574,7 +1584,10 @@ local filename = { ['undo.data'] = 'taskdata', ['.tclshrc'] = 'tcl', ['.wishrc'] = 'tcl', + ['.tclsh-history'] = 'tcl', ['tclsh.rc'] = 'tcl', + ['.xsctcmdhistory'] = 'tcl', + ['.xsdbcmdhistory'] = 'tcl', ['texmf.cnf'] = 'texmf', COPYING = 'text', README = 'text', @@ -1592,6 +1605,7 @@ local filename = { ['/.cargo/credentials'] = 'toml', ['Cargo.lock'] = 'toml', ['trustees.conf'] = 'trustees', + ['.ts_node_repl_history'] = 'typescript', ['/etc/udev/udev.conf'] = 'udevconf', ['/etc/updatedb.conf'] = 'updatedb', ['fdrupstream.log'] = 'upstreamlog', -- cgit From 66220d164a40791a5131d4660e6ffbee431070d5 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Mon, 8 Apr 2024 10:57:37 +0200 Subject: revert: "feat(health): fold successful healthchecks #22866" This reverts commit 4382d2ed564b80944345785d780cf1b19fb23ba8. The story for this feature was left in an incomplete state. It was never the intention to unilaterally fold all information, only the ones that did not contain relevant information. This feature does more harm than good in its incomplete state. --- runtime/lua/vim/health.lua | 37 +------------------------------------ 1 file changed, 1 insertion(+), 36 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 9e9c557ad3..18d20d4b40 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -2,42 +2,7 @@ local M = {} local s_output = {} ---@type string[] ---- Returns the fold text of the current healthcheck section -function M.foldtext() - local foldtext = vim.fn.foldtext() - - if vim.bo.filetype ~= 'checkhealth' then - return foldtext - end - - if vim.b.failedchecks == nil then - vim.b.failedchecks = vim.empty_dict() - end - - if vim.b.failedchecks[foldtext] == nil then - local warning = '- WARNING ' - local warninglen = string.len(warning) - local err = '- ERROR ' - local errlen = string.len(err) - local failedchecks = vim.b.failedchecks - failedchecks[foldtext] = false - - local foldcontent = vim.api.nvim_buf_get_lines(0, vim.v.foldstart - 1, vim.v.foldend, false) - for _, line in ipairs(foldcontent) do - if string.sub(line, 1, warninglen) == warning or string.sub(line, 1, errlen) == err then - failedchecks[foldtext] = true - break - end - end - - vim.b.failedchecks = failedchecks - end - - return vim.b.failedchecks[foldtext] and '+WE' .. foldtext:sub(4) or foldtext -end - ---- @param path string path to search for the healthcheck ---- @return string[] { name, func, type } representing a healthcheck +-- From a path return a list [{name}, {func}, {type}] representing a healthcheck local function filepath_to_healthcheck(path) path = vim.fs.normalize(path) local name --- @type string -- cgit From 355c149ba02a0d18d8dab9ede553a56507153c84 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 13 Apr 2024 09:59:53 +0800 Subject: vim-patch:9.1.0311: filetype: Some config files are not recognized (#28311) Problem: Some config files are not recognized Solution: Add some patterns for chktex, ripgreprc and ctags config files. See: https://www.nongnu.org/chktex/ See: https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md#configuration-file See: https://docs.ctags.io/en/latest/option-file.html#order-of-loading-option-files closes: vim/vim#14506 https://github.com/vim/vim/commit/a1dcd76ce791b5b8bd093765a99b71aa163300a5 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 215be7fdc2..5f7bcce713 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -283,6 +283,7 @@ local extension = { cbl = 'cobol', atg = 'coco', recipe = 'conaryrecipe', + ctags = 'conf', hook = function(path, bufnr) return M._getline(bufnr, 1) == '[Trigger]' and 'confini' or nil end, @@ -1305,6 +1306,9 @@ local filename = { ['auto.master'] = 'conf', ['texdoc.cnf'] = 'conf', ['.x11vncrc'] = 'conf', + ['.chktexrc'] = 'conf', + ['.ripgreprc'] = 'conf', + ripgreprc = 'conf', ['configure.in'] = 'config', ['configure.ac'] = 'config', crontab = 'crontab', -- cgit From 328a2373514a880ea80bed53d63de76e2dea2a9c Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 13 Apr 2024 20:41:59 +0900 Subject: fix(defaults): auto-close terminal for &shell with args (#28276) Problem: The `:terminal` auto-close logic does not support `&shell` that has arguments, e.g., `/bin/bash -O globstar`. Solution: Join `argv` and match `&shell`. This is not perfect since `&shell` may contain irregular spaces and quotes, but it seems to be good enough. --- runtime/lua/vim/_defaults.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 2f02c2b389..2afcc185e5 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -175,7 +175,7 @@ do end local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel) local argv = info.argv or {} - if #argv == 1 and argv[1] == vim.o.shell then + if table.concat(argv, ' ') == vim.o.shell then vim.api.nvim_buf_delete(args.buf, { force = true }) end end, -- cgit From 420f130223314805eac356d03eab98ff94961686 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 Apr 2024 18:33:44 +0200 Subject: vim-patch:9.1.0315: filetype: a few more dosini files are not recognized Problem: filetype: a few more dosini files are not recognized Solution: Detect wakatime, reply config files, flatpak, nfs config files and a few more python tools as dosini (or toml) (Wu, Zhenyu) Refer: - https://packaging.python.org/en/latest/specifications/pypirc/ - https://jorisroovers.com/gitlint/latest/configuration/ - https://pylint.pycqa.org/en/latest/user_guide/usage/run.html#command-line-options - https://docs.bpython-interpreter.org/en/latest/configuration.html - https://mypy.readthedocs.io/en/stable/config_file.html#the-mypy-configuration-file - https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file - https://github.com/wakatime/wakatime-cli?tab=readme-ov-file#usage - https://metacpan.org/dist/Reply/view/bin/reply#-cfg-~/.replyrc close: vim/vim#14512 https://github.com/vim/vim/commit/0881329d129866fa49444e7fb6e622e54285a8ff Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5f7bcce713..583839e28d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1338,9 +1338,18 @@ local filename = { ['setup.cfg'] = 'dosini', ['pudb.cfg'] = 'dosini', ['.coveragerc'] = 'dosini', + ['.pypirc'] = 'dosini', + ['.pylintrc'] = 'dosini', + ['pylintrc'] = 'dosini', + ['.replyrc'] = 'dosini', + ['.gitlint'] = 'dosini', + ['.oelint.cfg'] = 'dosini', ['psprint.conf'] = 'dosini', sofficerc = 'dosini', ['mimeapps.list'] = 'dosini', + ['.wakatime.cfg'] = 'dosini', + ['nfs.conf'] = 'dosini', + ['nfsmount.conf'] = 'dosini', ['/etc/pacman.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', @@ -1608,6 +1617,8 @@ local filename = { ['Gopkg.lock'] = 'toml', ['/.cargo/credentials'] = 'toml', ['Cargo.lock'] = 'toml', + ['.black'] = 'toml', + black = detect_line1('tool%.black', 'toml', nil), ['trustees.conf'] = 'trustees', ['.ts_node_repl_history'] = 'typescript', ['/etc/udev/udev.conf'] = 'udevconf', @@ -1758,6 +1769,9 @@ local pattern = { ['.*/etc/yum%.conf'] = 'dosini', ['.*/lxqt/.*%.conf'] = 'dosini', ['.*/screengrab/.*%.conf'] = 'dosini', + ['.*/bpython/config'] = 'dosini', + ['.*/mypy/config'] = 'dosini', + ['.*/flatpak/repo/config'] = 'dosini', ['.*lvs'] = 'dracula', ['.*lpe'] = 'dracula', ['.*/dtrace/.*%.d'] = 'dtrace', -- cgit From 737091d234b510ca71d991936ccf96c07d93a167 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 Apr 2024 18:42:10 +0200 Subject: vim-patch:9.1.0316: filetype: some sh and confini files not recognized Problem: filetype: some sh and confini files not recognized Solution: Detect neofetch, '.xprofile', XDG-User-Dirs files, paru and makepkg config files (Wu, Zhenyu) See: - https://github.com/dylanaraps/neofetch/wiki/Customizing-Info#config-file-location - https://www.freedesktop.org/wiki/Software/xdg-user-dirs/ closes: vim/vim#14505 https://github.com/vim/vim/commit/5a9f7e6750727f81d0638e7ce0ee6bcb01742570 Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 583839e28d..76250626da 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1350,7 +1350,8 @@ local filename = { ['.wakatime.cfg'] = 'dosini', ['nfs.conf'] = 'dosini', ['nfsmount.conf'] = 'dosini', - ['/etc/pacman.conf'] = 'confini', + ['pacman.conf'] = 'confini', + ['paru.conf'] = 'confini', ['mpv.conf'] = 'confini', dune = 'dune', jbuild = 'dune', @@ -1568,6 +1569,11 @@ local filename = { ['/etc/serial.conf'] = 'setserial', ['/etc/udev/cdsymlinks.conf'] = 'sh', ['.ash_history'] = 'sh', + ['makepkg.conf'] = 'sh', + ['.makepkg.conf'] = 'sh', + ['user-dirs.dirs'] = 'sh', + ['user-dirs.defaults'] = 'sh', + ['.xprofile'] = 'sh', ['bash.bashrc'] = detect.bash, bashrc = detect.bash, ['.bashrc'] = detect.bash, @@ -1765,7 +1771,6 @@ local pattern = { ['php%.ini%-.*'] = 'dosini', ['.*/%.aws/config'] = 'confini', ['.*/%.aws/credentials'] = 'confini', - ['.*/etc/pacman%.conf'] = 'confini', ['.*/etc/yum%.conf'] = 'dosini', ['.*/lxqt/.*%.conf'] = 'dosini', ['.*/screengrab/.*%.conf'] = 'dosini', @@ -2047,6 +2052,7 @@ local pattern = { ['.*/etc/services'] = 'services', ['.*/etc/serial%.conf'] = 'setserial', ['.*/etc/udev/cdsymlinks%.conf'] = 'sh', + ['.*/neofetch/config%.conf'] = 'sh', ['%.bash[_%-]aliases'] = detect.bash, ['%.bash[_%-]history'] = detect.bash, ['%.bash[_%-]logout'] = detect.bash, -- cgit From 391200f19810512b09cf3e66f78f755f3825044f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 Apr 2024 18:47:51 +0200 Subject: vim-patch:9.1.0317: filetype: matplotlibrc files are not recognized Problem: filetype: matplotlibrc files are not recognized Solution: Detect 'matplotlibrc' file as yaml filetype (Wu, Zhenyu) See: https://matplotlib.org/stable/users/explain/customizing.html#the-matplotlibrc-file closes: vim/vim#14501 https://github.com/vim/vim/commit/55d4f3c006689945599589a90036923b1752754f Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 76250626da..0e06cf69e6 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1660,6 +1660,7 @@ local filename = { ['.clang-format'] = 'yaml', ['.clang-tidy'] = 'yaml', ['yarn.lock'] = 'yaml', + matplotlibrc = 'yaml', zathurarc = 'zathurarc', ['/etc/zprofile'] = 'zsh', ['.zlogin'] = 'zsh', -- cgit From 4ca6e08327496ed5efc3465c42464319302ba22a Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 Apr 2024 18:49:36 +0200 Subject: vim-patch:9.1.0318: filetype: translate shell config files are not recognized Problem: filetype: translate shell config files are not recognized Solution: Detect 'init.trans', 'translate-shell' and '.trans' files as clojure (Wu, Zhenyu) See: https://github.com/soimort/translate-shell/wiki/Configuration closes: vim/vim#14499 https://github.com/vim/vim/commit/4b5cd7257ee99384940d5210cf50298ff925924e Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 0e06cf69e6..d23f4147e9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1295,6 +1295,8 @@ local filename = { ['/etc/defaults/cdrdao'] = 'cdrdaoconf', ['cfengine.conf'] = 'cfengine', cgdbrc = 'cgdbrc', + ['init.trans'] = 'clojure', + ['.trans'] = 'clojure', ['CMakeLists.txt'] = 'cmake', ['.cling_history'] = 'cpp', ['.alias'] = detect.csh, @@ -1738,6 +1740,7 @@ local pattern = { }, ['[cC]hange[lL]og.*'] = starsetf(detect.changelog), ['.*%.%.ch'] = 'chill', + ['.*/etc/translate%-shell'] = 'clojure', ['.*%.cmake%.in'] = 'cmake', -- */cmus/rc and */.cmus/rc ['.*/%.?cmus/rc'] = 'cmusrc', -- cgit From aa1d0ac095dcc08b6069d716222739e83cbe051f Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 15 Apr 2024 03:43:33 +0800 Subject: fix(defaults): only repeat macro for each selected line if linewise (#28289) As mentioned in #28287, repeating a macro for each selected line doesn't really make sense in non-linewise Visual mode. Fix #28287 --- runtime/lua/vim/_defaults.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 2afcc185e5..533ebbc7c3 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -78,19 +78,20 @@ do --- See |&-default| vim.keymap.set('n', '&', ':&&', { desc = ':help &-default' }) - --- Use Q in visual mode to execute a macro on each line of the selection. #21422 + --- Use Q in Visual mode to execute a macro on each line of the selection. #21422 + --- This only make sense in linewise Visual mode. #28287 --- --- Applies to @x and includes @@ too. vim.keymap.set( 'x', 'Q', - ':normal! @=reg_recorded()', - { silent = true, desc = ':help v_Q-default' } + "mode() == 'V' ? ':normal! @=reg_recorded()' : 'Q'", + { silent = true, expr = true, desc = ':help v_Q-default' } ) vim.keymap.set( 'x', '@', - "':normal! @'.getcharstr().''", + "mode() == 'V' ? ':normal! @'.getcharstr().'' : '@'", { silent = true, expr = true, desc = ':help v_@-default' } ) -- cgit From 7180ef690180cf92d1d49811820c46dd60e4d1c6 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Mon, 15 Apr 2024 00:10:16 +0100 Subject: feat(api)!: nvim_open_win: noautocmd blocks all autocmds #28192 Problem: noautocmd is confusing; despite its name, it doesn't block all autocommands (instead it blocks only those related to setting the buffer), and is commonly used by plugins to open windows while producing minimal side-effects. Solution: be consistent and block all autocommands when noautocmd is set. This includes WinNew (again), plus autocommands from entering the window (if enter is set) like WinEnter, WinLeave, TabEnter, .etc. See the discussion at https://github.com/neovim/neovim/pull/14659#issuecomment-2040029517 for more information. Remove win_set_buf's noautocmd argument, as it's no longer needed. NOTE: pum_create_float_preview sets noautocmd for win_set_buf, but all its callers already use block_autocmds. Despite that, pum_create_float_preview doesn't actually properly handle autocommands (it has no checks for whether those from win_enter or nvim_create_buf free the window). For now, ensure autocommands are blocked within it for correctness (in case it's ever called outside of a block_autocmds context; the function seems to have been refactored in #26739 anyway). --- runtime/lua/vim/_meta/api.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 678d6d3500..f56c256da6 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -1718,9 +1718,8 @@ function vim.api.nvim_open_term(buffer, opts) end --- • footer_pos: Footer position. Must be set with `footer` --- option. Value can be one of "left", "center", or "right". --- Default is `"left"`. ---- • noautocmd: If true then autocommands triggered from ---- setting the `buffer` to display are blocked (e.g: ---- `BufEnter`, `BufLeave`, `BufWinEnter`). +--- • noautocmd: If true then all autocommands are blocked for +--- the duration of the call. --- • fixed: If true when anchor is NW or SW, the float window --- would be kept fixed even if the window would be truncated. --- • hide: If true the floating window will be hidden. -- cgit From 43f8d7e3ef2db733f01f2d4c03214cd90c5a09be Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 15 Apr 2024 07:11:39 +0800 Subject: vim-patch:9.1.0329: String interpolation fails for Dict type (#28335) Problem: String interpolation fails for Dict type Solution: Support Dict data type properly, also support :put =Dict (without having to convert it to string() first) (Yegappan Lakshmanan) fixes: vim/vim#14529 closes: vim/vim#14541 https://github.com/vim/vim/commit/f01493c55062c01b1cdf9b1e946577f4d1bdddf3 Co-authored-by: Yegappan Lakshmanan --- runtime/lua/vim/_meta/vimfn.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index e3c7495cf9..d010673d24 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -9353,10 +9353,10 @@ function vim.fn.stridx(haystack, needle, start) end --- for infinite and NaN floating-point values representations --- which use |str2float()|. Strings are also dumped literally, --- only single quote is escaped, which does not allow using YAML ---- for parsing back binary strings. |eval()| should always work for ---- strings and floats though and this is the only official ---- method, use |msgpackdump()| or |json_encode()| if you need to ---- share data with other application. +--- for parsing back binary strings. |eval()| should always work +--- for strings and floats though, and this is the only official +--- method. Use |msgpackdump()| or |json_encode()| if you need to +--- share data with other applications. --- --- @param expr any --- @return string -- cgit From 57adf8c6e01d9395eb52fe03571c535571efdc4b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 15 Apr 2024 04:33:09 -0700 Subject: fix(vim.ui): open() may wait indefinitely #28325 Problem: vim.ui.open "locks up" Nvim if the spawned process does not terminate. #27986 Solution: - Change `vim.ui.open()`: - Do not call `wait()`. - Return a `SystemObj`. The caller can decide if it wants to `wait()`. - Change `gx` to `wait()` only a short time. - Allows `gx` to show a message if the command fails, without the risk of waiting forever. --- runtime/lua/vim/_defaults.lua | 13 +++++++++++-- runtime/lua/vim/_editor.lua | 1 + runtime/lua/vim/_system.lua | 3 +++ runtime/lua/vim/lsp/handlers.lua | 3 ++- runtime/lua/vim/ui.lua | 17 ++++++++--------- 5 files changed, 25 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 533ebbc7c3..5ada64a358 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -95,10 +95,19 @@ do { silent = true, expr = true, desc = ':help v_@-default' } ) - --- Map |gx| to call |vim.ui.open| on the identifier under the cursor + --- Map |gx| to call |vim.ui.open| on the at cursor. do local function do_open(uri) - local _, err = vim.ui.open(uri) + local cmd, err = vim.ui.open(uri) + local rv = cmd and cmd:wait(1000) or nil + if cmd and rv and rv.code ~= 0 then + err = ('vim.ui.open: command %s (%d): %s'):format( + (rv.code == 124 and 'timeout' or 'failed'), + rv.code, + vim.inspect(cmd.cmd) + ) + end + if err then vim.notify(err, vim.log.levels.ERROR) end diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 18f6cfa4ba..a40b298a37 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -121,6 +121,7 @@ vim.log = { --- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait(). --- --- @return vim.SystemObj Object with the fields: +--- - cmd (string[]) Command name and args --- - pid (integer) Process ID --- - wait (fun(timeout: integer|nil): SystemCompleted) Wait for the process to complete. Upon --- timeout the process is sent the KILL signal (9) and the exit code is set to 124. Cannot diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua index e97a5fc6c3..d603971495 100644 --- a/runtime/lua/vim/_system.lua +++ b/runtime/lua/vim/_system.lua @@ -18,6 +18,7 @@ local uv = vim.uv --- @field stderr? string --- @class vim.SystemState +--- @field cmd string[] --- @field handle? uv.uv_process_t --- @field timer? uv.uv_timer_t --- @field pid? integer @@ -56,6 +57,7 @@ local function close_handles(state) end --- @class vim.SystemObj +--- @field cmd string[] --- @field pid integer --- @field private _state vim.SystemState --- @field wait fun(self: vim.SystemObj, timeout?: integer): vim.SystemCompleted @@ -68,6 +70,7 @@ local SystemObj = {} --- @return vim.SystemObj local function new_systemobj(state) return setmetatable({ + cmd = state.cmd, pid = state.pid, _state = state, }, { __index = SystemObj }) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index daf4fec8d2..1c5291e7fd 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -615,7 +615,8 @@ M[ms.window_showDocument] = function(_, result, ctx, _) if result.external then -- TODO(lvimuser): ask the user for confirmation - local ret, err = vim.ui.open(uri) + local cmd, err = vim.ui.open(uri) + local ret = cmd and cmd:wait(2000) or nil if ret == nil or ret.code ~= 0 then return { diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index b0e7ca1a35..b6695734a3 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -114,14 +114,19 @@ end --- Examples: --- --- ```lua +--- -- Asynchronous. --- vim.ui.open("https://neovim.io/") --- vim.ui.open("~/path/to/file") ---- vim.ui.open("$VIMRUNTIME") +--- -- Synchronous (wait until the process exits). +--- local cmd, err = vim.ui.open("$VIMRUNTIME") +--- if cmd then +--- cmd:wait() +--- end --- ``` --- ---@param path string Path or URL to open --- ----@return vim.SystemCompleted|nil # Command result, or nil if not found. +---@return vim.SystemObj|nil # Command object, or nil if not found. ---@return string|nil # Error message on failure --- ---@see |vim.system()| @@ -152,13 +157,7 @@ function M.open(path) return nil, 'vim.ui.open: no handler found (tried: explorer.exe, xdg-open)' end - local rv = vim.system(cmd, { text = true, detach = true }):wait() - if rv.code ~= 0 then - local msg = ('vim.ui.open: command failed (%d): %s'):format(rv.code, vim.inspect(cmd)) - return rv, msg - end - - return rv, nil + return vim.system(cmd, { text = true, detach = true }), nil end return M -- cgit From 26765e8461c1ba1e9a351632212cf89900221781 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 8 Apr 2024 00:41:41 +0200 Subject: feat(diagnostic): is_enabled, enable(…, enable:boolean) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: `vim.diagnostic.is_disabled` and `vim.diagnostic.disable` are unnecessary and inconsistent with the "toggle" pattern (established starting with `vim.lsp.inlay_hint`, see https://github.com/neovim/neovim/pull/25512#pullrequestreview-1676750276 As a reminder, the rationale is: - we always need `enable()` - we always end up needing `is_enabled()` - "toggle" can be achieved via `enable(not is_enabled())` - therefore, - `toggle()` and `disable()` are redundant - `is_disabled()` is a needless inconsistency Solution: - Introduce `vim.diagnostic.is_enabled`, and `vim.diagnostic.enable(…, enable:boolean)` - Note: Future improvement would be to add an `enable()` overload `enable(enable:boolean, opts: table)`. - Deprecate `vim.diagnostic.is_disabled`, `vim.diagnostic.disable` --- runtime/lua/vim/diagnostic.lua | 132 +++++++++++++++++++++++------------------ 1 file changed, 73 insertions(+), 59 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 57491d155d..26374aa055 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1492,7 +1492,7 @@ end --- diagnostics, use |vim.diagnostic.reset()|. --- --- To hide diagnostics and prevent them from re-displaying, use ---- |vim.diagnostic.disable()|. +--- |vim.diagnostic.enable()|. --- ---@param namespace integer? Diagnostic namespace. When omitted, hide --- diagnostics from all namespaces. @@ -1517,25 +1517,35 @@ function M.hide(namespace, bufnr) end end ---- Check whether diagnostics are disabled in a given buffer. +--- Check whether diagnostics are enabled. --- ----@param bufnr integer? Buffer number, or 0 for current buffer. ----@param namespace integer? Diagnostic namespace. When omitted, checks if ---- all diagnostics are disabled in {bufnr}. ---- Otherwise, only checks if diagnostics from ---- {namespace} are disabled. ----@return boolean -function M.is_disabled(bufnr, namespace) +--- @param bufnr integer? Buffer number, or 0 for current buffer. +--- @param namespace integer? Diagnostic namespace, or `nil` for all diagnostics in {bufnr}. +--- @return boolean +--- @since 12 +function M.is_enabled(bufnr, namespace) bufnr = get_bufnr(bufnr) if namespace and M.get_namespace(namespace).disabled then - return true + return false end if type(diagnostic_disabled[bufnr]) == 'table' then - return diagnostic_disabled[bufnr][namespace] + return not diagnostic_disabled[bufnr][namespace] end - return diagnostic_disabled[bufnr] ~= nil + return diagnostic_disabled[bufnr] == nil +end + +--- @deprecated use `vim.diagnostic.is_enabled()` +function M.is_disabled(bufnr, namespace) + vim.deprecate( + 'Defining diagnostic signs with :sign-define or sign_define()', + 'vim.diagnostic.is_enabled()', + '0.12', + nil, + false + ) + return not M.is_enabled(bufnr, namespace) end --- Display diagnostics for the given namespace and buffer. @@ -1923,71 +1933,75 @@ function M.setloclist(opts) set_list(true, opts) end ---- Disable diagnostics in the given buffer. ---- ----@param bufnr integer? Buffer number, or 0 for current buffer. When ---- omitted, disable diagnostics in all buffers. ----@param namespace integer? Only disable diagnostics for the given namespace. +--- @deprecated use `vim.diagnostic.enabled(…, false)` function M.disable(bufnr, namespace) - vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } }) - if bufnr == nil then - if namespace == nil then - -- Disable everything (including as yet non-existing buffers and - -- namespaces) by setting diagnostic_disabled to an empty table and set - -- its metatable to always return true. This metatable is removed - -- in enable() - diagnostic_disabled = setmetatable({}, { - __index = function() - return true - end, - }) - else - local ns = M.get_namespace(namespace) - ns.disabled = true - end - else - bufnr = get_bufnr(bufnr) - if namespace == nil then - diagnostic_disabled[bufnr] = true - else - if type(diagnostic_disabled[bufnr]) ~= 'table' then - diagnostic_disabled[bufnr] = {} - end - diagnostic_disabled[bufnr][namespace] = true - end - end - - M.hide(namespace, bufnr) + vim.deprecate( + 'vim.diagnostic.disable()', + 'vim.diagnostic.enabled(…, false)', + '0.12', + nil, + false + ) + M.enable(bufnr, namespace, false) end ---- Enable diagnostics in the given buffer. +--- Enables or disables diagnostics. --- ----@param bufnr integer? Buffer number, or 0 for current buffer. When ---- omitted, enable diagnostics in all buffers. ----@param namespace integer? Only enable diagnostics for the given namespace. -function M.enable(bufnr, namespace) - vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } }) +--- To "toggle", pass the inverse of `is_enabled()`: +--- +--- ```lua +--- vim.diagnostic.enable(0, not vim.diagnostic.is_enabled()) +--- ``` +--- +--- @param bufnr integer? Buffer number, or 0 for current buffer, or `nil` for all buffers. +--- @param namespace integer? Only for the given namespace, or `nil` for all. +--- @param enable (boolean|nil) true/nil to enable, false to disable +function M.enable(bufnr, namespace, enable) + vim.validate({ + bufnr = { bufnr, 'n', true }, + namespace = { namespace, 'n', true }, + enable = { enable, 'b', true }, + }) + enable = enable == nil and true or enable if bufnr == nil then if namespace == nil then - -- Enable everything by setting diagnostic_disabled to an empty table - diagnostic_disabled = {} + diagnostic_disabled = ( + enable + -- Enable everything by setting diagnostic_disabled to an empty table. + and {} + -- Disable everything (including as yet non-existing buffers and namespaces) by setting + -- diagnostic_disabled to an empty table and set its metatable to always return true. + or setmetatable({}, { + __index = function() + return true + end, + }) + ) else local ns = M.get_namespace(namespace) - ns.disabled = false + ns.disabled = not enable end else bufnr = get_bufnr(bufnr) if namespace == nil then - diagnostic_disabled[bufnr] = nil + diagnostic_disabled[bufnr] = (not enable) and true or nil else if type(diagnostic_disabled[bufnr]) ~= 'table' then - return + if enable then + return + else + diagnostic_disabled[bufnr] = {} + end end - diagnostic_disabled[bufnr][namespace] = nil + diagnostic_disabled[bufnr][namespace] = (not enable) and true or nil end end - M.show(namespace, bufnr) + if enable then + M.show(namespace, bufnr) + else + M.hide(namespace, bufnr) + end end --- Parse a diagnostic from a string. -- cgit From 5ed9916a28b9a09d2e5629f3e4e5b0e81403ded7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 15 Apr 2024 18:35:59 +0200 Subject: feat(diagnostic): enable(…, opts) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: vim.diagnostic.enable() does not match the signature of vim.lsp.inlay_hint.enable() Solution: - Change the signature so that the first 2 args are (bufnr, enable). - Introduce a 3rd `opts` arg. - Currently it only supports `opts.ns_id`. --- runtime/lua/vim/diagnostic.lua | 67 ++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 26374aa055..4fcbfa7507 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -239,6 +239,9 @@ local M = {} --- whole line the sign is placed in. --- @field linehl? table +--- @class vim.diagnostic.Filter : vim.diagnostic.Opts +--- @field ns_id? integer Namespace + --- @nodoc --- @enum vim.diagnostic.Severity M.severity = { @@ -1538,13 +1541,7 @@ end --- @deprecated use `vim.diagnostic.is_enabled()` function M.is_disabled(bufnr, namespace) - vim.deprecate( - 'Defining diagnostic signs with :sign-define or sign_define()', - 'vim.diagnostic.is_enabled()', - '0.12', - nil, - false - ) + vim.deprecate('vim.diagnostic.is_disabled()', 'vim.diagnostic.is_enabled()', '0.12', nil, false) return not M.is_enabled(bufnr, namespace) end @@ -1591,7 +1588,7 @@ function M.show(namespace, bufnr, diagnostics, opts) return end - if M.is_disabled(bufnr, namespace) then + if not M.is_enabled(bufnr, namespace) then return end @@ -1942,7 +1939,7 @@ function M.disable(bufnr, namespace) nil, false ) - M.enable(bufnr, namespace, false) + M.enable(bufnr, false, { ns_id = namespace }) end --- Enables or disables diagnostics. @@ -1954,17 +1951,49 @@ end --- ``` --- --- @param bufnr integer? Buffer number, or 0 for current buffer, or `nil` for all buffers. ---- @param namespace integer? Only for the given namespace, or `nil` for all. --- @param enable (boolean|nil) true/nil to enable, false to disable -function M.enable(bufnr, namespace, enable) +--- @param opts vim.diagnostic.Filter? Filter by these opts, or `nil` for all. Only `ns_id` is +--- supported, currently. +function M.enable(bufnr, enable, opts) + opts = opts or {} + if type(enable) == 'number' then + -- Legacy signature. + vim.deprecate( + 'vim.diagnostic.enable(buf:number, namespace)', + 'vim.diagnostic.enable(buf:number, enable:boolean, opts)', + '0.12', + nil, + false + ) + opts.ns_id = enable + enable = true + end vim.validate({ bufnr = { bufnr, 'n', true }, - namespace = { namespace, 'n', true }, - enable = { enable, 'b', true }, + enable = { + enable, + function(o) + return o == nil or type(o) == 'boolean' or type(o) == 'number' + end, + 'boolean or number (deprecated)', + }, + opts = { + opts, + function(o) + return o == nil + or ( + type(o) == 'table' + -- TODO(justinmk): support other `vim.diagnostic.Filter` fields. + and (vim.tbl_isempty(o) or vim.deep_equal(vim.tbl_keys(o), { 'ns_id' })) + ) + end, + 'vim.diagnostic.Filter table (only ns_id is supported currently)', + }, }) enable = enable == nil and true or enable + if bufnr == nil then - if namespace == nil then + if opts.ns_id == nil then diagnostic_disabled = ( enable -- Enable everything by setting diagnostic_disabled to an empty table. @@ -1978,12 +2007,12 @@ function M.enable(bufnr, namespace, enable) }) ) else - local ns = M.get_namespace(namespace) + local ns = M.get_namespace(opts.ns_id) ns.disabled = not enable end else bufnr = get_bufnr(bufnr) - if namespace == nil then + if opts.ns_id == nil then diagnostic_disabled[bufnr] = (not enable) and true or nil else if type(diagnostic_disabled[bufnr]) ~= 'table' then @@ -1993,14 +2022,14 @@ function M.enable(bufnr, namespace, enable) diagnostic_disabled[bufnr] = {} end end - diagnostic_disabled[bufnr][namespace] = (not enable) and true or nil + diagnostic_disabled[bufnr][opts.ns_id] = (not enable) and true or nil end end if enable then - M.show(namespace, bufnr) + M.show(opts.ns_id, bufnr) else - M.hide(namespace, bufnr) + M.hide(opts.ns_id, bufnr) end end -- cgit From 6717cc5b416a94862bfa44b677066d60584cee0b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 08:16:15 +0800 Subject: vim-patch:9.1.0322: filetype: some mail tools not recognized Problem: filetype: some mail tools not recognized Solution: Detect '.mbsncrc' as conf, '.msmtprc' as msmtp and '.notmuch-config' as ini filetype (Shane-XB-Qian) closes: vim/vim#14533 https://github.com/vim/vim/commit/a7a9a476cf388f89286216188b8c8ae10702d9e2 Co-authored-by: shane.xb.qian --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d23f4147e9..ed0ae6abfd 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1311,6 +1311,7 @@ local filename = { ['.chktexrc'] = 'conf', ['.ripgreprc'] = 'conf', ripgreprc = 'conf', + ['.mbsyncrc'] = 'conf', ['configure.in'] = 'config', ['configure.ac'] = 'config', crontab = 'crontab', @@ -1352,6 +1353,7 @@ local filename = { ['.wakatime.cfg'] = 'dosini', ['nfs.conf'] = 'dosini', ['nfsmount.conf'] = 'dosini', + ['.notmuch-config'] = 'dosini', ['pacman.conf'] = 'confini', ['paru.conf'] = 'confini', ['mpv.conf'] = 'confini', @@ -1486,6 +1488,7 @@ local filename = { ['mplayer.conf'] = 'mplayerconf', mrxvtrc = 'mrxvtrc', ['.mrxvtrc'] = 'mrxvtrc', + ['.msmtprc'] = 'msmtp', ['.mysql_history'] = 'mysql', ['/etc/nanorc'] = 'nanorc', Neomuttrc = 'neomuttrc', -- cgit From fcf17c88590b4fb471821d69b1e30796129bebc1 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 08:19:42 +0800 Subject: vim-patch:9.1.0323: filetype: cabal config files may not be recognized Problem: filetype: cabal config files may not be recognized Solution: Change filetype pattern to '*/{,.}cabal/config' (Wu Zhenyu) closes: vim/vim#14498 https://github.com/vim/vim/commit/799dedec0e959d7a18df8a06d497770706d1627c Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index ed0ae6abfd..d622566d55 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1723,9 +1723,8 @@ local pattern = { ['.*%.blade%.php'] = 'blade', ['bzr_log%..*'] = 'bzr', ['.*enlightenment/.*%.cfg'] = 'c', - ['${HOME}/cabal%.config'] = 'cabalconfig', - ['${HOME}/%.config/cabal/config'] = 'cabalconfig', - ['${XDG_CONFIG_HOME}/cabal/config'] = 'cabalconfig', + ['.*/%.cabal/config'] = 'cabalconfig', + ['.*/cabal/config'] = 'cabalconfig', ['cabal%.project%..*'] = starsetf('cabalproject'), ['.*/%.calendar/.*'] = starsetf('calendar'), ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'), -- cgit From ca2f24cbbd52f1f6b73d0e5fc19b11bade606e45 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 08:22:51 +0800 Subject: vim-patch:9.1.0324: filetype: some json files are not recognized Problem: filetype: some json files are not recognized Solution: Detect '.jscsrc' and '.vsconfig' as jsonc filetype (Wu, Zhenyu) See: - https://github.com/microsoft/PowerToys/blob/main/.vsconfig - https://jscs-dev.github.io/ closes: vim/vim#14452 https://github.com/vim/vim/commit/c59a8648b2d8b3e17f12cd45f74a31b1aa385d2d Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d622566d55..8b800cf859 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1437,10 +1437,12 @@ local filename = { ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', ['.hintrc'] = 'jsonc', + ['.jscsrc'] = 'jsonc', ['.jsfmtrc'] = 'jsonc', ['.jshintrc'] = 'jsonc', ['.luaurc'] = 'jsonc', ['.swrc'] = 'jsonc', + ['.vsconfig'] = 'jsonc', ['.justfile'] = 'just', Kconfig = 'kconfig', ['Kconfig.debug'] = 'kconfig', -- cgit From 07661009c59e1cf70270d16ecb5f61f5c360d56a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 09:33:16 +0800 Subject: vim-patch:9.1.0325: CMakeCache.txt files not recognized (#28359) vim-patch:9.1.0325: filetype: CMakeCache.txt files not recognized Problem: filetype: CMakeCache.txt files not recognized Solution: Detect 'CMakeCache.txt' files as cmakecache filetype, include basic syntax script for cmakecache (Wu, Zhenyu, @bfrg) closes: vim/vim#14384 https://github.com/vim/vim/commit/62c09e032c6b2d49fffac726300d142381924b98 Co-authored-by: Wu, Zhenyu Co-authored-by: bfrg --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 8b800cf859..d687d55fcf 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1298,6 +1298,7 @@ local filename = { ['init.trans'] = 'clojure', ['.trans'] = 'clojure', ['CMakeLists.txt'] = 'cmake', + ['CMakeCache.txt'] = 'cmakecache', ['.cling_history'] = 'cpp', ['.alias'] = detect.csh, ['.cshrc'] = detect.csh, -- cgit From fb7ffac69fe4a32abfb4abfe5cf36213da17904d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Tue, 16 Apr 2024 09:33:33 +0800 Subject: vim-patch:9.1.0326: filetype: some requirements files are not recognized (#28360) Problem: filetype: some requirements files are not recognized Solution: Detect '*-requirements.txt', 'constraints.txt', 'requirements.in', 'requirements/*.txt' and 'requires/*.txt' as requirements filetype, include pip compiler, include requirements filetype and syntax plugin (Wu, Zhenyu, @raimon49) closes: vim/vim#14379 https://github.com/vim/vim/commit/f9f5424d3e75bbdb35aa48fa6f9241d9479b35e8 Co-authored-by: Wu, Zhenyu Co-authored-by: raimon --- runtime/lua/vim/filetype.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d687d55fcf..c6ad89320b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1559,6 +1559,8 @@ local filename = { ['.inputrc'] = 'readline', ['.reminders'] = 'remind', ['requirements.txt'] = 'requirements', + ['constraints.txt'] = 'requirements', + ['requirements.in'] = 'requirements', ['resolv.conf'] = 'resolv', ['robots.txt'] = 'robots', Gemfile = 'ruby', @@ -2052,6 +2054,9 @@ local pattern = { ['.*/queries/.*%.scm'] = 'query', -- treesitter queries (Neovim only) ['.*,v'] = 'rcs', ['%.reminders.*'] = starsetf('remind'), + ['.*%-requirements%.txt'] = 'requirements', + ['requirements/.*%.txt'] = 'requirements', + ['requires/.*%.txt'] = 'requirements', ['[rR]akefile.*'] = starsetf('ruby'), ['[rR]antfile'] = 'ruby', ['[rR]akefile'] = 'ruby', -- cgit From fe4583127f0aaf631b05ad3dff7ebb0126314cf2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 16 Apr 2024 07:31:43 -0700 Subject: fix: vim.validate() order is not deterministic #28377 Problem: The order of the validation performed by vim.validate() is unpredictable. - harder to write reliable tests. - confusing UX because validation result might return different errors randomly. Solution: Iterate the input using `vim.spairs()`. Future: Ideally, the caller could provide an "ordered dict". --- runtime/lua/vim/shared.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index a9eebf36da..eb51c244ef 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -578,7 +578,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) - vim.validate({ t = { t, 't' } }) + assert(type(t) == 'table', ('expected table, got %s'):format(type(t))) --- @cast t table -- collect the keys @@ -795,7 +795,7 @@ do return false, string.format('opt: expected table, got %s', type(opt)) end - for param_name, spec in pairs(opt) do + for param_name, spec in vim.spairs(opt) do if type(spec) ~= 'table' then return false, string.format('opt[%s]: expected table, got %s', param_name, type(spec)) end @@ -851,7 +851,8 @@ do return true end - --- Validates a parameter specification (types and values). + --- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric + --- order, until the first failure. --- --- Usage example: --- -- cgit From 20b38677c22b0ff19ea54396c7718b5a8f410ed4 Mon Sep 17 00:00:00 2001 From: Luna Saphie Mittelbach Date: Sun, 14 Apr 2024 12:54:10 +0200 Subject: feat(defaults): use ripgrep (rg) for 'grepprg' if available --- runtime/lua/vim/_defaults.lua | 8 ++++++++ runtime/lua/vim/_meta/options.lua | 12 ++++++++++++ 2 files changed, 20 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 5ada64a358..68ad95b725 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -496,3 +496,11 @@ if tty then end end end + +--- Default 'grepprg' to ripgrep if available. +if vim.fn.executable('rg') == 1 then + -- Match :grep default, otherwise rg searches cwd by default + -- Use -uuu to make ripgrep not do its default filtering + vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul') + vim.o.grepformat = '%f:%l:%c:%m' +end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 5e40851457..e10eb305b1 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2625,6 +2625,8 @@ vim.go.gd = vim.go.gdefault --- This is a scanf-like string that uses the same format as the --- 'errorformat' option: see `errorformat`. --- +--- If ripgrep ('grepprg') is available, this option defaults to `%f:%l:%c:%m`. +--- --- @type string vim.o.grepformat = "%f:%l:%m,%f:%l%m,%f %l%m" vim.o.gfm = vim.o.grepformat @@ -2649,6 +2651,16 @@ vim.go.gfm = vim.go.grepformat --- apply equally to 'grepprg'. --- This option cannot be set from a `modeline` or in the `sandbox`, for --- security reasons. +--- This option defaults to: +--- - `rg --vimgrep -uuu $* ...` if ripgrep is available (`:checkhealth`), +--- - `grep -n $* /dev/null` on Unix, +--- - `findstr /n $* nul` on Windows. +--- Ripgrep can perform additional filtering such as using .gitignore rules +--- and skipping hidden or binary files. This is disabled by default (see the -u option) +--- to more closely match the behaviour of standard grep. +--- You can make ripgrep match Vim's case handling using the +--- -i/--ignore-case and -S/--smart-case options. +--- An `OptionSet` autocmd can be used to set it up to match automatically. --- --- @type string vim.o.grepprg = "grep -n $* /dev/null" -- cgit From 8e5c48b08dad54706500e353c58ffb91f2684dd3 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Wed, 17 Apr 2024 01:13:44 +0600 Subject: feat(lua): vim.fs.normalize() resolves ".", ".." #28203 Problem: `vim.fs.normalize` does not resolve `.` and `..` components. This makes no sense as the entire point of normalization is to remove redundancy from the path. The path normalization functions in several other languages (Java, Python, C++, etc.) also resolve `.` and `..` components. Reference: - Python: https://docs.python.org/3/library/os.path.html#os.path.normpath - Java: https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html#normalize-- - C++: https://en.cppreference.com/w/cpp/filesystem/path/lexically_normal Solution: Resolve "." and ".." in `vim.fs.normalize`. Before: "~/foo/bar/../baz/./" => "~/foo/bar/../baz/." After: "~/foo/bar/../baz/./" => "~/foo/baz" --- runtime/lua/vim/fs.lua | 195 +++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 166 insertions(+), 29 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index ad0d914ea2..65ad58c720 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -334,30 +334,147 @@ function M.find(names, opts) return matches end +--- Split a Windows path into a prefix and a body, such that the body can be processed like a POSIX +--- path. The path must use forward slashes as path separator. +--- +--- Does not check if the path is a valid Windows path. Invalid paths will give invalid results. +--- +--- Examples: +--- - `//./C:/foo/bar` -> `//./C:`, `/foo/bar` +--- - `//?/UNC/server/share/foo/bar` -> `//?/UNC/server/share`, `/foo/bar` +--- - `//./system07/C$/foo/bar` -> `//./system07`, `/C$/foo/bar` +--- - `C:/foo/bar` -> `C:`, `/foo/bar` +--- - `C:foo/bar` -> `C:`, `foo/bar` +--- +--- @param path string Path to split. +--- @return string, string, boolean : prefix, body, whether path is invalid. +local function split_windows_path(path) + local prefix = '' + + --- Match pattern. If there is a match, move the matched pattern from the path to the prefix. + --- Returns the matched pattern. + --- + --- @param pattern string Pattern to match. + --- @return string|nil Matched pattern + local function match_to_prefix(pattern) + local match = path:match(pattern) + + if match then + prefix = prefix .. match --[[ @as string ]] + path = path:sub(#match + 1) + end + + return match + end + + local function process_unc_path() + return match_to_prefix('[^/]+/+[^/]+/+') + end + + if match_to_prefix('^//[?.]/') then + -- Device paths + local device = match_to_prefix('[^/]+/+') + + -- Return early if device pattern doesn't match, or if device is UNC and it's not a valid path + if not device or (device:match('^UNC/+$') and not process_unc_path()) then + return prefix, path, false + end + elseif match_to_prefix('^//') then + -- Process UNC path, return early if it's invalid + if not process_unc_path() then + return prefix, path, false + end + elseif path:match('^%w:') then + -- Drive paths + prefix, path = path:sub(1, 2), path:sub(3) + end + + -- If there are slashes at the end of the prefix, move them to the start of the body. This is to + -- ensure that the body is treated as an absolute path. For paths like C:foo/bar, there are no + -- slashes at the end of the prefix, so it will be treated as a relative path, as it should be. + local trailing_slash = prefix:match('/+$') + + if trailing_slash then + prefix = prefix:sub(1, -1 - #trailing_slash) + path = trailing_slash .. path --[[ @as string ]] + end + + return prefix, path, true +end + +--- Resolve `.` and `..` components in a POSIX-style path. This also removes extraneous slashes. +--- `..` is not resolved if the path is relative and resolving it requires the path to be absolute. +--- If a relative path resolves to the current directory, an empty string is returned. +--- +--- @see M.normalize() +--- @param path string Path to resolve. +--- @return string Resolved path. +local function path_resolve_dot(path) + local is_path_absolute = vim.startswith(path, '/') + -- Split the path into components and process them + local path_components = vim.split(path, '/') + local new_path_components = {} + + for _, component in ipairs(path_components) do + if component == '.' or component == '' then -- luacheck: ignore 542 + -- Skip `.` components and empty components + elseif component == '..' then + if #new_path_components > 0 and new_path_components[#new_path_components] ~= '..' then + -- For `..`, remove the last component if we're still inside the current directory, except + -- when the last component is `..` itself + table.remove(new_path_components) + elseif is_path_absolute then -- luacheck: ignore 542 + -- Reached the root directory in absolute path, do nothing + else + -- Reached current directory in relative path, add `..` to the path + table.insert(new_path_components, component) + end + else + table.insert(new_path_components, component) + end + end + + return (is_path_absolute and '/' or '') .. table.concat(new_path_components, '/') +end + --- @class vim.fs.normalize.Opts --- @inlinedoc --- --- Expand environment variables. --- (default: `true`) ---- @field expand_env boolean - ---- Normalize a path to a standard format. A tilde (~) character at the ---- beginning of the path is expanded to the user's home directory and ---- environment variables are also expanded. +--- @field expand_env? boolean +--- +--- Path is a Windows path. +--- (default: `true` in Windows, `false` otherwise) +--- @field win? boolean + +--- Normalize a path to a standard format. A tilde (~) character at the beginning of the path is +--- expanded to the user's home directory and environment variables are also expanded. "." and ".." +--- components are also resolved, except when the path is relative and trying to resolve it would +--- result in an absolute path. +--- - "." as the only part in a relative path: +--- - "." => "." +--- - "././" => "." +--- - ".." when it leads outside the current directory +--- - "foo/../../bar" => "../bar" +--- - "../../foo" => "../../foo" +--- - ".." in the root directory returns the root directory. +--- - "/../../" => "/" --- --- On Windows, backslash (\) characters are converted to forward slashes (/). --- --- Examples: ---- --- ```lua ---- vim.fs.normalize('C:\\\\Users\\\\jdoe') ---- -- On Windows: 'C:/Users/jdoe' ---- ---- vim.fs.normalize('~/src/neovim') ---- -- '/home/jdoe/src/neovim' ---- ---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim') ---- -- '/Users/jdoe/.config/nvim/init.vim' +--- [[C:\Users\jdoe]] => "C:/Users/jdoe" +--- "~/src/neovim" => "/home/jdoe/src/neovim" +--- "$XDG_CONFIG_HOME/nvim/init.vim" => "/Users/jdoe/.config/nvim/init.vim" +--- "~/src/nvim/api/../tui/./tui.c" => "/home/jdoe/src/nvim/tui/tui.c" +--- "./foo/bar" => "foo/bar" +--- "foo/../../../bar" => "../../bar" +--- "/home/jdoe/../../../bar" => "/bar" +--- "C:foo/../../baz" => "C:../baz" +--- "C:/foo/../../baz" => "C:/baz" +--- [[\\?\UNC\server\share\foo\..\..\..\bar]] => "//?/UNC/server/share/bar" --- ``` --- ---@param path (string) Path to normalize @@ -369,12 +486,21 @@ function M.normalize(path, opts) vim.validate({ path = { path, { 'string' } }, expand_env = { opts.expand_env, { 'boolean' }, true }, + win = { opts.win, { 'boolean' }, true }, }) + local win = opts.win == nil and iswin or not not opts.win + local os_sep_local = win and '\\' or '/' + + -- Empty path is already normalized + if path == '' then + return '' + end + -- Expand ~ to users home directory if vim.startswith(path, '~') then local home = vim.uv.os_homedir() or '~' - if home:sub(-1) == os_sep then + if home:sub(-1) == os_sep_local then home = home:sub(1, -2) end path = home .. path:sub(2) @@ -386,24 +512,35 @@ function M.normalize(path, opts) end -- Convert path separator to `/` - path = path:gsub(os_sep, '/') + path = path:gsub(os_sep_local, '/') - -- Don't modify leading double slash as those have implementation-defined behavior according to - -- POSIX. They are also valid UNC paths. Three or more leading slashes are however collapsed to - -- a single slash. - if vim.startswith(path, '//') and not vim.startswith(path, '///') then - path = '/' .. path:gsub('/+', '/') - else - path = path:gsub('/+', '/') - end + -- Check for double slashes at the start of the path because they have special meaning + local double_slash = vim.startswith(path, '//') and not vim.startswith(path, '///') + local prefix = '' - -- Ensure last slash is not truncated from root drive on Windows - if iswin and path:match('^%w:/$') then - return path + if win then + local is_valid --- @type boolean + -- Split Windows paths into prefix and body to make processing easier + prefix, path, is_valid = split_windows_path(path) + + -- If path is not valid, return it as-is + if not is_valid then + return prefix .. path + end + + -- Remove extraneous slashes from the prefix + prefix = prefix:gsub('/+', '/') end - -- Remove trailing slashes - path = path:gsub('(.)/$', '%1') + -- Resolve `.` and `..` components and remove extraneous slashes from path, then recombine prefix + -- and path. Preserve leading double slashes as they indicate UNC paths and DOS device paths in + -- Windows and have implementation-defined behavior in POSIX. + path = (double_slash and '/' or '') .. prefix .. path_resolve_dot(path) + + -- Change empty path to `.` + if path == '' then + path = '.' + end return path end -- cgit From 97323d821be97deeb1a5797b4ca534156b9e9b0c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 18 Apr 2024 15:34:10 +0200 Subject: refactor(lsp): merge rpc.domain_socket_connect into rpc.connect (#28398) See discussion in https://github.com/neovim/neovim/pull/26850 --- runtime/lua/vim/lsp/rpc.lua | 95 ++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 66 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 984e4f040a..6748b32ec0 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -621,95 +621,53 @@ local function merge_dispatchers(dispatchers) return merged end ---- Create a LSP RPC client factory that connects via TCP to the given host and port. +--- Create a LSP RPC client factory that connects to either: +--- +--- - a named pipe (windows) +--- - a domain socket (unix) +--- - a host and port via TCP --- --- Return a function that can be passed to the `cmd` field for --- |vim.lsp.start_client()| or |vim.lsp.start()|. --- ----@param host string host to connect to ----@param port integer port to connect to +---@param host_or_path string host to connect to or path to a pipe/domain socket +---@param port integer? TCP port to connect to. If absent the first argument must be a pipe ---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient -function M.connect(host, port) +function M.connect(host_or_path, port) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) - local tcp = assert(uv.new_tcp()) + local handle = ( + port == nil + and assert( + uv.new_pipe(false), + string.format('Pipe with name %s could not be opened.', host_or_path) + ) + or assert(uv.new_tcp(), 'Could not create new TCP socket') + ) local closing = false local transport = { write = function(msg) - tcp:write(msg) - end, - is_closing = function() - return closing - end, - terminate = function() - if not closing then - closing = true - tcp:shutdown() - tcp:close() - dispatchers.on_exit(0, 0) - end + handle:write(msg) end, - } - local client = new_client(dispatchers, transport) - tcp:connect(host, port, function(err) - if err then - vim.schedule(function() - vim.notify( - string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)), - vim.log.levels.WARN - ) - end) - return - end - local handle_body = function(body) - client:handle_body(body) - end - tcp:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) - client:on_error(M.client_errors.READ_ERROR, read_err) - end)) - end) - - return public_client(client) - end -end - ---- Create a LSP RPC client factory that connects via named pipes (Windows) ---- or unix domain sockets (Unix) to the given pipe_path (file path on ---- Unix and name on Windows). ---- ---- Return a function that can be passed to the `cmd` field for ---- |vim.lsp.start_client()| or |vim.lsp.start()|. ---- ----@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to ----@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient -function M.domain_socket_connect(pipe_path) - return function(dispatchers) - dispatchers = merge_dispatchers(dispatchers) - local pipe = - assert(uv.new_pipe(false), string.format('pipe with name %s could not be opened.', pipe_path)) - local closing = false - local transport = { - write = vim.schedule_wrap(function(msg) - pipe:write(msg) - end), is_closing = function() return closing end, terminate = function() if not closing then closing = true - pipe:shutdown() - pipe:close() + handle:shutdown() + handle:close() dispatchers.on_exit(0, 0) end end, } local client = new_client(dispatchers, transport) - pipe:connect(pipe_path, function(err) + local function on_connect(err) if err then + local address = port == nil and host_or_path or (host_or_path .. ':' .. port) vim.schedule(function() vim.notify( - string.format('Could not connect to :%s, reason: %s', pipe_path, vim.inspect(err)), + string.format('Could not connect to %s, reason: %s', address, vim.inspect(err)), vim.log.levels.WARN ) end) @@ -718,10 +676,15 @@ function M.domain_socket_connect(pipe_path) local handle_body = function(body) client:handle_body(body) end - pipe:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) + handle:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) client:on_error(M.client_errors.READ_ERROR, read_err) end)) - end) + end + if port == nil then + handle:connect(host_or_path, on_connect) + else + handle:connect(host_or_path, port, on_connect) + end return public_client(client) end -- cgit From f1dfe32bf5552197e0068298b0527526a4f918b1 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 18 Apr 2024 07:57:58 -0700 Subject: feat(lua): enable(enable:boolean, filter:table) #28374 Problem: We need to establish a pattern for `enable()`. Solution: - First `enable()` parameter is always `enable:boolean`. - Update `vim.diagnostic.enable()` - Update `vim.lsp.inlay_hint.enable()`. - It was not released yet, so no deprecation is needed. But to help HEAD users, it will show an informative error. - vim.deprecate(): - Improve message when the "removal version" is a *current or older* version. --- runtime/lua/vim/_editor.lua | 10 +++- runtime/lua/vim/diagnostic.lua | 109 ++++++++++++++++++++----------------- runtime/lua/vim/lsp/inlay_hint.lua | 30 +++++++--- 3 files changed, 88 insertions(+), 61 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index a40b298a37..2f6057c1f8 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1049,10 +1049,11 @@ function vim.deprecate(name, alternative, version, plugin, backtrace) plugin = { plugin, 'string', true }, } plugin = plugin or 'Nvim' + local will_be_removed = 'will be removed' -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md. - -- e.g., when planned to be removed in version = '0.12' (soft-deprecated since 0.10-dev), - -- show warnings since 0.11, including 0.11-dev (hard_deprecated_since = 0.11-dev). + -- Example: if removal_version is 0.12 (soft-deprecated since 0.10-dev), show warnings starting at + -- 0.11, including 0.11-dev (hard_deprecated_since = 0.11-dev). if plugin == 'Nvim' then local current_version = vim.version() ---@type vim.Version local removal_version = assert(vim.version.parse(version)) @@ -1075,14 +1076,17 @@ function vim.deprecate(name, alternative, version, plugin, backtrace) if not is_hard_deprecated then return + elseif current_version >= removal_version then + will_be_removed = 'was removed' end end local msg = ('%s is deprecated'):format(name) msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.') - msg = ('%s%s\nThis feature will be removed in %s version %s'):format( + msg = ('%s%s\nFeature %s in %s %s'):format( msg, (plugin == 'Nvim' and ' :help deprecated' or ''), + will_be_removed, plugin, version ) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 4fcbfa7507..6233bb7058 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -239,8 +239,16 @@ local M = {} --- whole line the sign is placed in. --- @field linehl? table ---- @class vim.diagnostic.Filter : vim.diagnostic.Opts ---- @field ns_id? integer Namespace +-- TODO: inherit from `vim.diagnostic.Opts`, implement its fields. +--- Optional filters |kwargs|, or `nil` for all. +--- @class vim.diagnostic.Filter +--- @inlinedoc +--- +--- Diagnostic namespace, or `nil` for all. +--- @field ns_id? integer +--- +--- Buffer number, or 0 for current buffer, or `nil` for all buffers. +--- @field bufnr? integer --- @nodoc --- @enum vim.diagnostic.Severity @@ -1522,18 +1530,21 @@ end --- Check whether diagnostics are enabled. --- ---- @param bufnr integer? Buffer number, or 0 for current buffer. ---- @param namespace integer? Diagnostic namespace, or `nil` for all diagnostics in {bufnr}. +--- @param filter vim.diagnostic.Filter? --- @return boolean --- @since 12 -function M.is_enabled(bufnr, namespace) - bufnr = get_bufnr(bufnr) - if namespace and M.get_namespace(namespace).disabled then +function M.is_enabled(filter) + filter = filter or {} + if filter.ns_id and M.get_namespace(filter.ns_id).disabled then return false + elseif filter.bufnr == nil then + -- See enable() logic. + return vim.tbl_isempty(diagnostic_disabled) and not diagnostic_disabled[1] end + local bufnr = get_bufnr(filter.bufnr) if type(diagnostic_disabled[bufnr]) == 'table' then - return not diagnostic_disabled[bufnr][namespace] + return not diagnostic_disabled[bufnr][filter.ns_id] end return diagnostic_disabled[bufnr] == nil @@ -1542,7 +1553,7 @@ end --- @deprecated use `vim.diagnostic.is_enabled()` function M.is_disabled(bufnr, namespace) vim.deprecate('vim.diagnostic.is_disabled()', 'vim.diagnostic.is_enabled()', '0.12', nil, false) - return not M.is_enabled(bufnr, namespace) + return not M.is_enabled { bufnr = bufnr or 0, ns_id = namespace } end --- Display diagnostics for the given namespace and buffer. @@ -1588,7 +1599,7 @@ function M.show(namespace, bufnr, diagnostics, opts) return end - if not M.is_enabled(bufnr, namespace) then + if not M.is_enabled { bufnr = bufnr or 0, ns_id = namespace } then return end @@ -1934,12 +1945,12 @@ end function M.disable(bufnr, namespace) vim.deprecate( 'vim.diagnostic.disable()', - 'vim.diagnostic.enabled(…, false)', + 'vim.diagnostic.enabled(false, …)', '0.12', nil, false ) - M.enable(bufnr, false, { ns_id = namespace }) + M.enable(false, { bufnr = bufnr, ns_id = namespace }) end --- Enables or disables diagnostics. @@ -1947,53 +1958,49 @@ end --- To "toggle", pass the inverse of `is_enabled()`: --- --- ```lua ---- vim.diagnostic.enable(0, not vim.diagnostic.is_enabled()) +--- vim.diagnostic.enable(not vim.diagnostic.is_enabled()) --- ``` --- ---- @param bufnr integer? Buffer number, or 0 for current buffer, or `nil` for all buffers. --- @param enable (boolean|nil) true/nil to enable, false to disable ---- @param opts vim.diagnostic.Filter? Filter by these opts, or `nil` for all. Only `ns_id` is ---- supported, currently. -function M.enable(bufnr, enable, opts) - opts = opts or {} - if type(enable) == 'number' then - -- Legacy signature. +--- @param filter vim.diagnostic.Filter? +function M.enable(enable, filter) + -- Deprecated signature. Drop this in 0.12 + local legacy = (enable or filter) + and vim.tbl_contains({ 'number', 'nil' }, type(enable)) + and vim.tbl_contains({ 'number', 'nil' }, type(filter)) + + if legacy then vim.deprecate( - 'vim.diagnostic.enable(buf:number, namespace)', - 'vim.diagnostic.enable(buf:number, enable:boolean, opts)', + 'vim.diagnostic.enable(buf:number, namespace:number)', + 'vim.diagnostic.enable(enable:boolean, filter:table)', '0.12', nil, false ) - opts.ns_id = enable + + vim.validate({ + enable = { enable, 'n', true }, -- Legacy `bufnr` arg. + filter = { filter, 'n', true }, -- Legacy `namespace` arg. + }) + + local ns_id = type(filter) == 'number' and filter or nil + filter = {} + filter.ns_id = ns_id + filter.bufnr = type(enable) == 'number' and enable or nil enable = true + else + filter = filter or {} + vim.validate({ + enable = { enable, 'b', true }, + filter = { filter, 't', true }, + }) end - vim.validate({ - bufnr = { bufnr, 'n', true }, - enable = { - enable, - function(o) - return o == nil or type(o) == 'boolean' or type(o) == 'number' - end, - 'boolean or number (deprecated)', - }, - opts = { - opts, - function(o) - return o == nil - or ( - type(o) == 'table' - -- TODO(justinmk): support other `vim.diagnostic.Filter` fields. - and (vim.tbl_isempty(o) or vim.deep_equal(vim.tbl_keys(o), { 'ns_id' })) - ) - end, - 'vim.diagnostic.Filter table (only ns_id is supported currently)', - }, - }) + enable = enable == nil and true or enable + local bufnr = filter.bufnr if bufnr == nil then - if opts.ns_id == nil then + if filter.ns_id == nil then diagnostic_disabled = ( enable -- Enable everything by setting diagnostic_disabled to an empty table. @@ -2007,12 +2014,12 @@ function M.enable(bufnr, enable, opts) }) ) else - local ns = M.get_namespace(opts.ns_id) + local ns = M.get_namespace(filter.ns_id) ns.disabled = not enable end else bufnr = get_bufnr(bufnr) - if opts.ns_id == nil then + if filter.ns_id == nil then diagnostic_disabled[bufnr] = (not enable) and true or nil else if type(diagnostic_disabled[bufnr]) ~= 'table' then @@ -2022,14 +2029,14 @@ function M.enable(bufnr, enable, opts) diagnostic_disabled[bufnr] = {} end end - diagnostic_disabled[bufnr][opts.ns_id] = (not enable) and true or nil + diagnostic_disabled[bufnr][filter.ns_id] = (not enable) and true or nil end end if enable then - M.show(opts.ns_id, bufnr) + M.show(filter.ns_id, bufnr) else - M.hide(opts.ns_id, bufnr) + M.hide(filter.ns_id, bufnr) end end diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index ec676ea97f..6305f82efb 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -349,7 +349,7 @@ api.nvim_set_decoration_provider(namespace, { end, }) ---- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current +--- @param bufnr (integer|nil) Buffer handle, or 0 for current --- @return boolean --- @since 12 function M.is_enabled(bufnr) @@ -360,23 +360,39 @@ function M.is_enabled(bufnr) return bufstates[bufnr] and bufstates[bufnr].enabled or false end +--- Optional filters |kwargs|, or `nil` for all. +--- @class vim.lsp.inlay_hint.enable.Filter +--- @inlinedoc +--- Buffer number, or 0/nil for current buffer. +--- @field bufnr integer? + --- Enables or disables inlay hints for a buffer. --- --- To "toggle", pass the inverse of `is_enabled()`: --- --- ```lua ---- vim.lsp.inlay_hint.enable(0, not vim.lsp.inlay_hint.is_enabled()) +--- vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled()) --- ``` --- ---- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current --- @param enable (boolean|nil) true/nil to enable, false to disable +--- @param filter vim.lsp.inlay_hint.enable.Filter? --- @since 12 -function M.enable(bufnr, enable) - vim.validate({ enable = { enable, 'boolean', true }, bufnr = { bufnr, 'number', true } }) +function M.enable(enable, filter) + if type(enable) == 'number' or type(filter) == 'boolean' then + vim.deprecate( + 'vim.lsp.inlay_hint.enable(bufnr:number, enable:boolean)', + 'vim.diagnostic.enable(enable:boolean, filter:table)', + '0.10-dev' + ) + error('see :help vim.lsp.inlay_hint.enable() for updated parameters') + end + + vim.validate({ enable = { enable, 'boolean', true }, filter = { filter, 'table', true } }) + filter = filter or {} if enable == false then - _disable(bufnr) + _disable(filter.bufnr) else - _enable(bufnr) + _enable(filter.bufnr) end end -- cgit From 97c0a52416b873e22aee41d7e1f2464c57c8be71 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Thu, 18 Apr 2024 12:06:52 -0700 Subject: fix(lsp): correct deprecation message #28403 --- runtime/lua/vim/lsp/inlay_hint.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 6305f82efb..b27795d797 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -381,7 +381,7 @@ function M.enable(enable, filter) if type(enable) == 'number' or type(filter) == 'boolean' then vim.deprecate( 'vim.lsp.inlay_hint.enable(bufnr:number, enable:boolean)', - 'vim.diagnostic.enable(enable:boolean, filter:table)', + 'vim.lsp.inlay_hint.enable(enable:boolean, filter:table)', '0.10-dev' ) error('see :help vim.lsp.inlay_hint.enable() for updated parameters') -- cgit From 8d77061051d3d5e7b0eb067a0bf776f2c62a7133 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 19 Apr 2024 14:50:12 +0800 Subject: vim-patch:9.1.0354: runtime(uci): No support for uci file types (#28409) Problem: runtime(uci): No support for uci file types (Wu, Zhenyu) Solution: include basic uci ftplugin and syntax plugins (Colin Caine) closes: vim/vim#14575 https://github.com/vim/vim/commit/4b3fab14dbde971f15d8783e9ef125b19fdbc829 Co-authored-by: Colin Caine Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 4 +++- runtime/lua/vim/filetype/detect.lua | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c6ad89320b..bcc745f125 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -14,7 +14,8 @@ local M = {} local function starsetf(ft, opts) return { function(path, bufnr) - local f = type(ft) == 'function' and ft(path, bufnr) or ft + -- Note: when `ft` is a function its return value may be nil. + local f = type(ft) ~= 'function' and ft or ft(path, bufnr) if not vim.g.ft_ignore_pat then return f end @@ -2138,6 +2139,7 @@ local pattern = { ['.*/%.init/.*%.conf'] = 'upstart', ['.*/usr/share/upstart/.*%.override'] = 'upstart', ['.*%.[Ll][Oo][Gg]'] = detect.log, + ['.*/etc/config/.*'] = starsetf(detect.uci), ['.*%.vhdl_[0-9].*'] = starsetf('vhdl'), ['.*%.ws[fc]'] = 'wsh', ['.*/Xresources/.*'] = starsetf('xdefaults'), diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index c48984d151..05b4ffc223 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1584,6 +1584,26 @@ function M.typ(_, bufnr) return 'typst' end +--- @type vim.filetype.mapfn +function M.uci(_, bufnr) + -- Return "uci" iff the file has a config or package statement near the + -- top of the file and all preceding lines were comments or blank. + for _, line in ipairs(getlines(bufnr, 1, 3)) do + -- Match a config or package statement at the start of the line. + if + line:find('^%s*[cp]%s+%S') + or line:find('^%s*config%s+%S') + or line:find('^%s*package%s+%S') + then + return 'uci' + end + -- Match a line that is either all blank or blank followed by a comment + if not (line:find('^%s*$') or line:find('^%s*#')) then + break + end + end +end + -- Determine if a .v file is Verilog, V, or Coq --- @type vim.filetype.mapfn function M.v(_, bufnr) -- cgit From 18da6964cc610795ba1b7010e75d4f12ce73322a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 19 Apr 2024 06:22:41 -0700 Subject: refactor(vim.iter)!: remove vim.iter.map/filter/totable #26138 Problem: The use-case for the convenience functions vim.iter.map(), vim.iter.filter(), vim.iter.totable() is not clear. Solution: Drop them for now. We can revisit after 0.10 release. --- runtime/lua/vim/iter.lua | 52 ------------------------------------------------ 1 file changed, 52 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 2f2c21aee8..302463b136 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -60,9 +60,6 @@ --- vim.iter(rb):totable() --- -- { "a", "b" } --- ``` ---- ---- In addition to the |vim.iter()| function, the |vim.iter| module provides ---- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|. --- LuaLS is bad at generics which this module mostly deals with --- @diagnostic disable:no-unknown @@ -1092,55 +1089,6 @@ function ListIter.new(t) return it end ---- Collects an |iterable| into a table. ---- ---- ```lua ---- -- Equivalent to: ---- vim.iter(f):totable() ---- ``` ---- ----@param f function Iterator function ----@return table -function M.totable(f, ...) - return Iter.new(f, ...):totable() -end - ---- Filters a table or other |iterable|. ---- ---- ```lua ---- -- Equivalent to: ---- vim.iter(src):filter(f):totable() ---- ``` ---- ----@see |Iter:filter()| ---- ----@param f fun(...):boolean Filter function. Accepts the current iterator or table values as ---- arguments and returns true if those values should be kept in the ---- final table ----@param src table|function Table or iterator function to filter ----@return table -function M.filter(f, src, ...) - return Iter.new(src, ...):filter(f):totable() -end - ---- Maps a table or other |iterable|. ---- ---- ```lua ---- -- Equivalent to: ---- vim.iter(src):map(f):totable() ---- ``` ---- ----@see |Iter:map()| ---- ----@param f fun(...): any? Map function. Accepts the current iterator or table values as ---- arguments and returns one or more new values. Nil values are removed ---- from the final table. ----@param src table|function Table or iterator function to filter ----@return table -function M.map(f, src, ...) - return Iter.new(src, ...):map(f):totable() -end - return setmetatable(M, { __call = function(_, ...) return Iter.new(...) -- cgit From 52d2851ca4e747146c30e1996c08bf504d99fe95 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 19 Apr 2024 15:35:25 +0200 Subject: vim-patch:9.1.0355: filetype: flake.lock files are not recognized Problem: filetype: flake.lock files are not recognized Solution: Detect 'flake.lock' as json filetype (Riley Bruins) closes: vim/vim#14589 https://github.com/vim/vim/commit/ce736033ae86e14e8b1a56a3e4843c7ab24e48d2 Co-authored-by: Riley Bruins --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bcc745f125..a4c57bbc52 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1436,6 +1436,7 @@ local filename = { ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', ['.stylelintrc'] = 'json', + ['flake.lock'] = 'json', ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', ['.hintrc'] = 'jsonc', -- cgit From fd085d90820149caecf213b299f19e46305043ee Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 20 Apr 2024 05:47:08 -0700 Subject: fix(vim.ui.open): try wslview before explorer.exe #28424 Problem: explorer.exe is unreliable on WSL. Solution: Try wslview before explorer.exe. fix #28410 --- runtime/lua/vim/ui.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index b6695734a3..e02acaf25c 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -149,12 +149,14 @@ function M.open(path) else return nil, 'vim.ui.open: rundll32 not found' end + elseif vim.fn.executable('wslview') == 1 then + cmd = { 'wslview', path } elseif vim.fn.executable('explorer.exe') == 1 then cmd = { 'explorer.exe', path } elseif vim.fn.executable('xdg-open') == 1 then cmd = { 'xdg-open', path } else - return nil, 'vim.ui.open: no handler found (tried: explorer.exe, xdg-open)' + return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)' end return vim.system(cmd, { text = true, detach = true }), nil -- cgit From f190f758ac58d9cc955368e047b070e0a2261033 Mon Sep 17 00:00:00 2001 From: Yinzuo Jiang Date: Sat, 20 Apr 2024 21:40:01 +0800 Subject: feat(lsp): add vim.lsp.buf.subtypes(), vim.lsp.buf.supertypes() (#28388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Fußenegger Co-authored-by: Maria José Solano --- runtime/lua/vim/lsp.lua | 3 ++ runtime/lua/vim/lsp/buf.lua | 84 ++++++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/handlers.lua | 39 +++++++++++++++++++ 3 files changed, 126 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index eb604caacd..ab22c5901a 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -43,6 +43,9 @@ lsp._request_name_to_capability = { [ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' }, [ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' }, [ms.callHierarchy_outgoingCalls] = { 'callHierarchyProvider' }, + [ms.textDocument_prepareTypeHierarchy] = { 'typeHierarchyProvider' }, + [ms.typeHierarchy_subtypes] = { 'typeHierarchyProvider' }, + [ms.typeHierarchy_supertypes] = { 'typeHierarchyProvider' }, [ms.textDocument_rename] = { 'renameProvider' }, [ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' }, [ms.textDocument_codeAction] = { 'codeActionProvider' }, diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 17cc698b76..fa0cbab138 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -495,6 +495,90 @@ function M.outgoing_calls() call_hierarchy(ms.callHierarchy_outgoingCalls) end +--- @param method string +local function type_hierarchy(method) + --- Merge results from multiple clients into a single table. Client-ID is preserved. + --- + --- @param results table + local function merge_results(results) + local merged_results = {} + for client_id, client_result in pairs(results) do + if client_result.error then + vim.notify(client_result.error.message, vim.log.levels.WARN) + elseif client_result.result then + for _, item in pairs(client_result.result) do + table.insert(merged_results, { client_id, item }) + end + end + end + return merged_results + end + + local bufnr = api.nvim_get_current_buf() + local params = util.make_position_params() + --- @param results table + vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results) + local merged_results = merge_results(results) + if #merged_results == 0 then + vim.notify('No items resolved', vim.log.levels.INFO) + return + end + + if #merged_results == 1 then + --- @type {integer, lsp.TypeHierarchyItem} + local item = merged_results[1] + local client = vim.lsp.get_client_by_id(item[1]) + if client then + --- @type lsp.TypeHierarchyItem + client.request(method, { item = item[2] }, nil, bufnr) + else + vim.notify( + string.format('Client with id=%d disappeared during call hierarchy request', item[1]), + vim.log.levels.WARN + ) + end + else + local opts = { + prompt = 'Select a type hierarchy item:', + kind = 'typehierarchy', + format_item = function(item) + if not item[2].detail or #item[2].detail == 0 then + return item[2].name + end + return string.format('%s %s', item[2].name, item[2].detail) + end, + } + + vim.ui.select(merged_results, opts, function(item) + local client = vim.lsp.get_client_by_id(item[1]) + if client then + --- @type lsp.TypeHierarchyItem + client.request(method, { item = item[2] }, nil, bufnr) + else + vim.notify( + string.format('Client with id=%d disappeared during call hierarchy request', item[1]), + vim.log.levels.WARN + ) + end + end) + end + end) +end + +--- Lists all the subtypes of the symbol under the +--- cursor in the |quickfix| window. If the symbol can resolve to +--- multiple items, the user can pick one using |vim.ui.select()|. +function M.subtypes() + type_hierarchy(ms.typeHierarchy_subtypes) +end + +--- Lists all the supertypes of the symbol under the +--- cursor in the |quickfix| window. If the symbol can resolve to +--- multiple items, the user can pick one using |vim.ui.select()|. +function M.supertypes() + type_hierarchy(ms.typeHierarchy_supertypes) +end + --- List workspace folders. --- function M.list_workspace_folders() diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 1c5291e7fd..a15096fdad 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -565,6 +565,45 @@ M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from') --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to') +--- Displays type hierarchy in the quickfix window. +local function make_type_hierarchy_handler() + --- @param result lsp.TypeHierarchyItem[] + return function(_, result, ctx, _) + if not result then + return + end + local function format_item(item) + if not item.detail or #item.detail == 0 then + return item.name + end + return string.format('%s %s', item.name, item.detail) + end + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + local items = {} + for _, type_hierarchy_item in pairs(result) do + local col = util._get_line_byte_from_position( + ctx.bufnr, + type_hierarchy_item.range.start, + client.offset_encoding + ) + table.insert(items, { + filename = assert(vim.uri_to_fname(type_hierarchy_item.uri)), + text = format_item(type_hierarchy_item), + lnum = type_hierarchy_item.range.start.line + 1, + col = col + 1, + }) + end + vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items }) + api.nvim_command('botright copen') + end +end + +--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls +M[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler() + +--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls +M[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler() + --- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage --- @param result lsp.LogMessageParams M[ms.window_logMessage] = function(_, result, ctx, _) -- cgit From 5e6240ffc24e55ecf7721fd9dc3f33c6f178be8c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Sat, 20 Apr 2024 10:36:17 -0700 Subject: feat(treesitter): handle quantified fold captures --- runtime/lua/vim/treesitter/_fold.lua | 58 +++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 21 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d96cc966de..d8b9f4a261 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -149,27 +149,43 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) -- Collect folds starting from srow - 1, because we should first subtract the folds that end at -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for id, node, metadata in query:iter_captures(tree:root(), bufnr, math.max(srow - 1, 0), erow) do - if query.captures[id] == 'fold' then - local range = ts.get_range(node, bufnr, metadata[id]) - local start, _, stop, stop_col = Range.unpack4(range) - - if stop_col == 0 then - stop = stop - 1 - end - - local fold_length = stop - start + 1 - - -- Fold only multiline nodes that are not exactly the same as previously met folds - -- Checking against just the previously found fold is sufficient if nodes - -- are returned in preorder or postorder when traversing tree - if - fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) - then - enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 - leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 - prev_start = start - prev_stop = stop + for _, match, metadata in + query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true }) + do + for id, nodes in pairs(match) do + if query.captures[id] == 'fold' then + local range = ts.get_range(nodes[1], bufnr, metadata[id]) + local start, _, stop, stop_col = Range.unpack4(range) + + for i = 2, #nodes, 1 do + local node_range = ts.get_range(nodes[i], bufnr, metadata[id]) + local node_start, _, node_stop, node_stop_col = Range.unpack4(node_range) + if node_start < start then + start = node_start + end + if node_stop > stop then + stop = node_stop + stop_col = node_stop_col + end + end + + if stop_col == 0 then + stop = stop - 1 + end + + local fold_length = stop - start + 1 + + -- Fold only multiline nodes that are not exactly the same as previously met folds + -- Checking against just the previously found fold is sufficient if nodes + -- are returned in preorder or postorder when traversing tree + if + fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop) + then + enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1 + leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1 + prev_start = start + prev_stop = stop + end end end end -- cgit From 2b6c9bbe7f7ac950683e81129b76e35e35839ede Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Sat, 20 Apr 2024 02:33:44 +0900 Subject: perf(treesitter): incremental foldupdate Problem: While the fold level computation is incremental, the evaluation of the foldexpr is done on the full buffer. Despite that the foldexpr reads from the cache, it can take tens of milliseconds for moderately big (10K lines) buffers. Solution: Track the range of lines on which the foldexpr should be evaluated. --- runtime/lua/vim/treesitter/_fold.lua | 122 +++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 50 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d8b9f4a261..d511bef7a5 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -4,10 +4,21 @@ local Range = require('vim.treesitter._range') local api = vim.api +---Treesitter folding is done in two steps: +---(1) compute the fold levels with the syntax tree and cache the result (`compute_folds_levels`) +---(2) evaluate foldexpr for each window, which reads from the cache (`foldupdate`) ---@class TS.FoldInfo ----@field levels string[] the foldexpr result for each line ----@field levels0 integer[] the raw fold levels ----@field edits? {[1]: integer, [2]: integer} line range edited since the last invocation of the callback scheduled in on_bytes. 0-indexed, end-exclusive. +--- +---@field levels string[] the cached foldexpr result for each line +---@field levels0 integer[] the cached raw fold levels +--- +---The range edited since the last invocation of the callback scheduled in on_bytes. +---Should compute fold levels in this range. +---@field on_bytes_range? Range2 +--- +---The range on which to evaluate foldexpr. +---When in insert mode, the evaluation is deferred to InsertLeave. +---@field foldupdate_range? Range2 local FoldInfo = {} FoldInfo.__index = FoldInfo @@ -80,31 +91,16 @@ function FoldInfo:add_range(srow, erow) list_insert(self.levels0, srow + 1, erow, -1) end ----@package +---@param range Range2 ---@param srow integer ---@param erow_old integer ---@param erow_new integer 0-indexed, exclusive -function FoldInfo:edit_range(srow, erow_old, erow_new) - if self.edits then - self.edits[1] = math.min(srow, self.edits[1]) - if erow_old <= self.edits[2] then - self.edits[2] = self.edits[2] + (erow_new - erow_old) - end - self.edits[2] = math.max(self.edits[2], erow_new) - else - self.edits = { srow, erow_new } - end -end - ----@package ----@return integer? srow ----@return integer? erow 0-indexed, exclusive -function FoldInfo:flush_edit() - if self.edits then - local srow, erow = self.edits[1], self.edits[2] - self.edits = nil - return srow, erow +local function edit_range(range, srow, erow_old, erow_new) + range[1] = math.min(srow, range[1]) + if erow_old <= range[2] then + range[2] = range[2] + (erow_new - erow_old) end + range[2] = math.max(range[2], erow_new) end --- If a parser doesn't have any ranges explicitly set, treesitter will @@ -128,7 +124,7 @@ end ---@param srow integer? ---@param erow integer? 0-indexed, exclusive ---@param parse_injections? boolean -local function get_folds_levels(bufnr, info, srow, erow, parse_injections) +local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = normalise_erow(bufnr, erow) @@ -231,7 +227,7 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections) clamped = nestmax end - -- Record the "real" level, so that it can be used as "base" of later get_folds_levels(). + -- Record the "real" level, so that it can be used as "base" of later compute_folds_levels(). info.levels0[lnum] = adjusted info.levels[lnum] = prefix .. tostring(clamped) @@ -252,15 +248,14 @@ local group = api.nvim_create_augroup('treesitter/fold', {}) --- --- Nvim usually automatically updates folds when text changes, but it doesn't work here because --- FoldInfo update is scheduled. So we do it manually. -local function foldupdate(bufnr) - local function do_update() - for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do - api.nvim_win_call(win, function() - if vim.wo.foldmethod == 'expr' then - vim._foldupdate() - end - end) - end +---@package +---@param srow integer +---@param erow integer 0-indexed, exclusive +function FoldInfo:foldupdate(bufnr, srow, erow) + if self.foldupdate_range then + edit_range(self.foldupdate_range, srow, erow, erow) + else + self.foldupdate_range = { srow, erow } end if api.nvim_get_mode().mode == 'i' then @@ -275,12 +270,25 @@ local function foldupdate(bufnr) group = group, buffer = bufnr, once = true, - callback = do_update, + callback = function() + self:do_foldupdate(bufnr) + end, }) return end - do_update() + self:do_foldupdate(bufnr) +end + +---@package +function FoldInfo:do_foldupdate(bufnr) + local srow, erow = self.foldupdate_range[1], self.foldupdate_range[2] + self.foldupdate_range = nil + for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do + if vim.wo[win].foldmethod == 'expr' then + vim._foldupdate(win, srow, erow) + end + end end --- Schedule a function only if bufnr is loaded. @@ -288,7 +296,7 @@ end --- * queries seem to use the old buffer state in on_bytes for some unknown reason; --- * to avoid textlock; --- * to avoid infinite recursion: ---- get_folds_levels → parse → _do_callback → on_changedtree → get_folds_levels. +--- compute_folds_levels → parse → _do_callback → on_changedtree → compute_folds_levels. ---@param bufnr integer ---@param fn function local function schedule_if_loaded(bufnr, fn) @@ -305,16 +313,20 @@ end ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) schedule_if_loaded(bufnr, function() + local srow_upd, erow_upd ---@type integer?, integer? for _, change in ipairs(tree_changes) do local srow, _, erow, ecol = Range.unpack4(change) if ecol > 0 then erow = erow + 1 end -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. - get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow) + srow = math.max(srow - vim.wo.foldminlines, 0) + compute_folds_levels(bufnr, foldinfo, srow, erow) + srow_upd = srow_upd and math.min(srow_upd, srow) or srow + erow_upd = erow_upd and math.max(erow_upd, erow) or erow end if #tree_changes > 0 then - foldupdate(bufnr) + foldinfo:foldupdate(bufnr, srow_upd, erow_upd) end end) end @@ -351,19 +363,29 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, foldinfo:add_range(end_row_old, end_row_new) end end - foldinfo:edit_range(start_row, end_row_old, end_row_new) + + if foldinfo.on_bytes_range then + edit_range(foldinfo.on_bytes_range, start_row, end_row_old, end_row_new) + else + foldinfo.on_bytes_range = { start_row, end_row_new } + end + if foldinfo.foldupdate_range then + edit_range(foldinfo.foldupdate_range, start_row, end_row_old, end_row_new) + end -- This callback must not use on_bytes arguments, because they can be outdated when the callback -- is invoked. For example, `J` with non-zero count triggers multiple on_bytes before executing - -- the scheduled callback. So we should collect the edits. + -- the scheduled callback. So we accumulate the edited ranges in `on_bytes_range`. schedule_if_loaded(bufnr, function() - local srow, erow = foldinfo:flush_edit() - if not srow then + if not foldinfo.on_bytes_range then return end + local srow, erow = foldinfo.on_bytes_range[1], foldinfo.on_bytes_range[2] + foldinfo.on_bytes_range = nil -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. - get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow) - foldupdate(bufnr) + srow = math.max(srow - vim.wo.foldminlines, 0) + compute_folds_levels(bufnr, foldinfo, srow, erow) + foldinfo:foldupdate(bufnr, srow, erow) end) end end @@ -382,7 +404,7 @@ function M.foldexpr(lnum) if not foldinfos[bufnr] then foldinfos[bufnr] = FoldInfo.new() - get_folds_levels(bufnr, foldinfos[bufnr]) + compute_folds_levels(bufnr, foldinfos[bufnr]) parser:register_cbs({ on_changedtree = function(tree_changes) @@ -406,10 +428,10 @@ api.nvim_create_autocmd('OptionSet', { pattern = { 'foldminlines', 'foldnestmax' }, desc = 'Refresh treesitter folds', callback = function() - for _, bufnr in ipairs(vim.tbl_keys(foldinfos)) do + for bufnr, _ in pairs(foldinfos) do foldinfos[bufnr] = FoldInfo.new() - get_folds_levels(bufnr, foldinfos[bufnr]) - foldupdate(bufnr) + compute_folds_levels(bufnr, foldinfos[bufnr]) + foldinfos[bufnr]:foldupdate(bufnr, 0, api.nvim_buf_line_count(bufnr)) end end, }) -- cgit From 032df963bb3fb0b5652e1817e9f4da986996fa6d Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 21 Apr 2024 13:39:08 +0100 Subject: refactor(treesitter): language loading --- runtime/lua/vim/treesitter/_meta.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index e2768d4b06..34a51e42f6 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -60,9 +60,17 @@ local TSNode = {} ---@field captures string[] ---@field patterns table +--- @param lang string +vim._ts_inspect_language = function(lang) end + ---@return integer vim._ts_get_language_version = function() end +--- @param path string +--- @param lang string +--- @param symbol_name? string +vim._ts_add_language = function(path, lang, symbol_name) end + ---@return integer vim._ts_get_minimum_language_version = function() end -- cgit From d9d890562e43493c999f8a6ff2b848959686f5b6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 21 Apr 2024 15:19:43 +0200 Subject: refactor(lua): rename tbl_islist => islist ref #24572 --- runtime/lua/vim/_options.lua | 2 +- runtime/lua/vim/diagnostic.lua | 14 +++++++------- runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/shared.lua | 12 +++++++++--- 4 files changed, 18 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 13ad6cc58f..b41e298dd7 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -642,7 +642,7 @@ end --- @param t table --- @param val any local function remove_one_item(t, val) - if vim.tbl_islist(t) then + if vim.islist(t) then local remove_index = nil for i, v in ipairs(t) do if v == val then diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 6233bb7058..5e4835ab88 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -974,7 +974,7 @@ function M.set(namespace, bufnr, diagnostics, opts) bufnr = { bufnr, 'n' }, diagnostics = { diagnostics, - vim.tbl_islist, + vim.islist, 'a list of diagnostics', }, opts = { opts, 't', true }, @@ -1186,7 +1186,7 @@ M.handlers.signs = { bufnr = { bufnr, 'n' }, diagnostics = { diagnostics, - vim.tbl_islist, + vim.islist, 'a list of diagnostics', }, opts = { opts, 't', true }, @@ -1309,7 +1309,7 @@ M.handlers.underline = { bufnr = { bufnr, 'n' }, diagnostics = { diagnostics, - vim.tbl_islist, + vim.islist, 'a list of diagnostics', }, opts = { opts, 't', true }, @@ -1382,7 +1382,7 @@ M.handlers.virtual_text = { bufnr = { bufnr, 'n' }, diagnostics = { diagnostics, - vim.tbl_islist, + vim.islist, 'a list of diagnostics', }, opts = { opts, 't', true }, @@ -1576,7 +1576,7 @@ function M.show(namespace, bufnr, diagnostics, opts) diagnostics = { diagnostics, function(v) - return v == nil or vim.tbl_islist(v) + return v == nil or vim.islist(v) end, 'a list of diagnostics', }, @@ -2120,7 +2120,7 @@ function M.toqflist(diagnostics) vim.validate({ diagnostics = { diagnostics, - vim.tbl_islist, + vim.islist, 'a list of diagnostics', }, }) @@ -2160,7 +2160,7 @@ function M.fromqflist(list) vim.validate({ list = { list, - vim.tbl_islist, + vim.islist, 'a list of quickfix items', }, }) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a15096fdad..d6579cf4b3 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -428,7 +428,7 @@ local function location_handler(_, result, ctx, config) -- textDocument/definition can return Location or Location[] -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition - if not vim.tbl_islist(result) then + if not vim.islist(result) then result = { result } end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index eb51c244ef..6d9e4ad809 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -601,7 +601,7 @@ end --- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous). --- ---- If the indexes start from 1 and are contiguous then the array is also a list. |vim.tbl_islist()| +--- If the indexes start from 1 and are contiguous then the array is also a list. |vim.islist()| --- --- Empty table `{}` is an array, unless it was created by |vim.empty_dict()| or returned as --- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|. @@ -640,6 +640,12 @@ function vim.tbl_isarray(t) end end +--- @deprecated +function vim.tbl_islist(t) + vim.deprecate('vim.tbl_islist', 'vim.islist', '0.12') + return vim.islist(t) +end + --- Tests if `t` is a "list": a table indexed _only_ by contiguous integers starting from 1 (what --- |lua-length| calls a "regular array"). --- @@ -648,9 +654,9 @@ end --- ---@see |vim.tbl_isarray()| --- ----@param t table +---@param t? table ---@return boolean `true` if list-like table, else `false`. -function vim.tbl_islist(t) +function vim.islist(t) if type(t) ~= 'table' then return false end -- cgit From 5c8dfb0e379cd4ae8de418e7aa554dbc5ab7f236 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 21 Apr 2024 17:29:10 +0200 Subject: refactor(lua): rename tbl_isarray => isarray tbl_isarray was not released yet, so it will not go through a deprecation cycle. ref #24572 --- runtime/lua/vim/shared.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 6d9e4ad809..85720b6ea3 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -356,7 +356,7 @@ end --- We only merge empty tables or tables that are not an array (indexed by integers) local function can_merge(v) - return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_isarray(v)) + return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.isarray(v)) end local function tbl_extend(behavior, deep_extend, ...) @@ -502,7 +502,7 @@ end --- ---@param o table Table to index ---@param ... any Optional keys (0 or more, variadic) via which to index the table ----@return any : Nested value indexed by key (if it exists), else nil +---@return any # Nested value indexed by key (if it exists), else nil function vim.tbl_get(o, ...) local keys = { ... } if #keys == 0 then @@ -599,6 +599,12 @@ function vim.spairs(t) t end +--- @deprecated +function vim.tbl_isarray() + vim.deprecate('vim.tbl_isarray', 'vim.isarray', '0.10-dev') + error('vim.tbl_isarray was renamed to vim.isarray') +end + --- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous). --- --- If the indexes start from 1 and are contiguous then the array is also a list. |vim.islist()| @@ -608,9 +614,9 @@ end --- ---@see https://github.com/openresty/luajit2#tableisarray --- ----@param t table +---@param t? table ---@return boolean `true` if array-like table, else `false`. -function vim.tbl_isarray(t) +function vim.isarray(t) if type(t) ~= 'table' then return false end @@ -652,7 +658,7 @@ end --- Empty table `{}` is a list, unless it was created by |vim.empty_dict()| or returned as --- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|. --- ----@see |vim.tbl_isarray()| +---@see |vim.isarray()| --- ---@param t? table ---@return boolean `true` if list-like table, else `false`. -- cgit From 9912a4c81b0856200f44a38e99d38eae44cef5c9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 22 Apr 2024 00:58:48 +0200 Subject: refactor(lua): deprecate tbl_flatten Problem: Besides being redundant with vim.iter():flatten(), `tbl_flatten` has these problems: - Has `tbl_` prefix but only accepts lists. - Discards some results! Compare the following: - iter.flatten(): ``` vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable() ``` - tbl_flatten: ``` vim.tbl_flatten({1, { { a = 2 } }, { 3 } }) ``` Solution: Deprecate tbl_flatten. Note: iter:flatten() currently fails ("flatten() requires a list-like table") on this code from gen_lsp.lua: local anonym = vim.iter({ -- remove nil anonymous_num > 1 and '' or nil, '---@class ' .. anonymous_classname, }):flatten():totable() Should we enhance :flatten() to work for arrays? --- runtime/lua/vim/health.lua | 17 +++++++++-------- runtime/lua/vim/lsp/client.lua | 2 +- runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/shared.lua | 1 + 4 files changed, 12 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 18d20d4b40..8326214604 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -366,14 +366,15 @@ 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 names = vim.tbl_flatten(vim.tbl_map(function(pattern) - return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true)) - end, PATTERNS)) - -- Remove duplicates - local unique = {} - vim.tbl_map(function(f) - unique[f] = true - end, names) + local unique = vim + .iter(vim.tbl_map(function(pattern) + return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true)) + end, PATTERNS)) + :flatten() + :fold({}, function(t, name) + t[name] = true -- Remove duplicates + return t + end) -- vim.health is this file, which is not a healthcheck unique['vim'] = nil return vim.tbl_keys(unique) diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 09064b9510..98b3cfc762 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -722,7 +722,7 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err --- --- @param ... string List to write to the buffer local function err_message(...) - local message = table.concat(vim.tbl_flatten({ ... })) + local message = table.concat(vim.iter({ ... }):flatten():totable()) if vim.in_fast_event() then vim.schedule(function() api.nvim_err_writeln(message) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index d6579cf4b3..4672d94105 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -12,7 +12,7 @@ local M = {} --- Writes to error buffer. ---@param ... string Will be concatenated before being written local function err_message(...) - vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR) + vim.notify(table.concat(vim.iter({ ... }):flatten():totable()), vim.log.levels.ERROR) api.nvim_command('redraw') end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 85720b6ea3..5bbf8801e8 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -544,6 +544,7 @@ function vim.list_extend(dst, src, start, finish) return dst end +--- @deprecated --- Creates a copy of a list-like table such that any nested tables are --- "unrolled" and appended to the result. --- -- cgit From f112ac73bd340707173db4d28660e76aa96b36de Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 22 Apr 2024 02:43:24 +0200 Subject: fix: unreliable "checkhealth completions" test ref https://github.com/neovim/neovim/issues/19596 FAILED test/functional/plugin/health_spec.lua @ 37: :checkhealth completions can be listed via getcompletion() test/functional/plugin/health_spec.lua:40: Expected objects to be the same. Passed in: (string) 'provider.node' Expected: (string) 'provider.clipboard' stack traceback: test/functional/plugin/health_spec.lua:40: in function --- runtime/lua/vim/health.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 8326214604..d72a32b541 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -377,7 +377,9 @@ M._complete = function() end) -- vim.health is this file, which is not a healthcheck unique['vim'] = nil - return vim.tbl_keys(unique) + local rv = vim.tbl_keys(unique) + table.sort(rv) + return rv end --- Runs the specified healthchecks. -- cgit From 013afc6863b0bed401e1d9e70cb861ee347b158f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 22 Apr 2024 04:27:57 -0700 Subject: refactor(lua): deprecate tbl_flatten #28457 forgot some changes in 9912a4c81b0856200f44a38e99d38eae44cef5c9 --- runtime/lua/vim/shared.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 5bbf8801e8..1c8059adab 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -553,6 +553,7 @@ end ---@param t table List-like table ---@return table Flattened copy of the given list-like table function vim.tbl_flatten(t) + vim.deprecate('vim.tbl_flatten', 'vim.iter(…):flatten():totable()', '0.12') local result = {} --- @param _t table local function _tbl_flatten(_t) -- cgit From 39fc340276a4fdbe1f1bb4bfbe7328267ad7f9d6 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 23 Apr 2024 02:18:49 +0800 Subject: fix(lsp): avoid assertion when `client_hints` do not exist (#28461) --- runtime/lua/vim/lsp/inlay_hint.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index b27795d797..37b330f5f6 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -311,6 +311,10 @@ api.nvim_set_decoration_provider(namespace, { if bufstate.version ~= util.buf_versions[bufnr] then return end + + if not bufstate.client_hints then + return + end local hints_by_client = assert(bufstate.client_hints) for lnum = topline, botline do -- cgit From aef120d1e94e83a367a631d6bc8ce0b4a64f9dbd Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 22 Apr 2024 23:41:31 +0200 Subject: vim-patch:9.1.0366: filetype: ondir files are not recognized Problem: filetype: ondir files are not recognized Solution: Detect '.ondirrc' as ondir filetype (Jon Parise) closes: vim/vim#14604 https://github.com/vim/vim/commit/ea999037a41292b3d3e00700a87a82fe5d2c12b2 Co-authored-by: Jon Parise --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a4c57bbc52..7667664edb 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1503,6 +1503,7 @@ local filename = { ['.octaverc'] = 'octave', octaverc = 'octave', ['octave.conf'] = 'octave', + ['.ondirrc'] = 'ondir', opam = 'opam', ['pacman.log'] = 'pacmanlog', ['/etc/pam.conf'] = 'pamconf', -- cgit From ad76b050eb2cd03174c108b5ae6759b3c1ea8941 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 23 Apr 2024 19:06:41 +0800 Subject: fix(diagnostic): open_float on multi-line diagnostics #28301 Problem: when diagnostic have a range of line, open_float not work. Solution: filter diagnostic by line number range. --- runtime/lua/vim/diagnostic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 5e4835ab88..7371b5241f 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1697,7 +1697,7 @@ function M.open_float(opts, ...) if scope == 'line' then --- @param d vim.Diagnostic diagnostics = vim.tbl_filter(function(d) - return d.lnum == lnum + return lnum >= d.lnum and lnum <= d.end_lnum end, diagnostics) elseif scope == 'cursor' then -- LSP servers can send diagnostics with `end_col` past the length of the line -- cgit From a4fc3bb0e68c8b078377fd9826e4cca3b4b3fdbf Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 23 Apr 2024 19:13:58 +0800 Subject: fix(diagnostic): vim.diagnostic.get(…,{lnum=…}) on multi-line diagnostic #28273 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: vim.diagnostic.get(…,{lnum=…}) does not match multi-line diagnostics. Solution: add end_lnum support. --- runtime/lua/vim/diagnostic.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 7371b5241f..b42eece4c2 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -717,7 +717,7 @@ local function get_diagnostics(bufnr, opts, clamp) ---@param b integer ---@param d vim.Diagnostic local function add(b, d) - if not opts.lnum or d.lnum == opts.lnum then + if not opts.lnum or (opts.lnum >= d.lnum and opts.lnum <= (d.end_lnum or d.lnum)) then if clamp and api.nvim_buf_is_loaded(b) then local line_count = buf_line_count[b] - 1 if @@ -1140,7 +1140,7 @@ end --- Limit diagnostics to one or more namespaces. --- @field namespace? integer[]|integer --- ---- Limit diagnostics to the given line number. +--- Limit diagnostics to those spanning the specified line number. --- @field lnum? integer --- --- See |diagnostic-severity|. -- cgit From c5af5c0b9ab84c86f84e32210512923e7eb641ba Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Tue, 23 Apr 2024 18:23:45 +0300 Subject: perf(lua): faster vim.deprecate() #28470 Problem: `vim.deprecate()` can be relatively significantly slower than the deprecated function in "Nvim" plugin. Solution: Optimize checks for "Nvim" plugin. This also results into not distinguishing "xxx-dev" and "xxx" versions when doing checks, which is essentially covered by the deprecation logic itself. With this rewrite I get the times from #28459: `{ 0.024827, 0.003797, 0.002024, 0.001774, 0.001703 }`. For quicker reference: - On current Nightly it is something like `{ 3.72243, 0.918169, 0.968143, 0.763256, 0.783424 }`. - On 0.9.5: `{ 0.002955, 0.000361, 0.000281, 0.000251, 0.00019 }`. --- runtime/lua/vim/_editor.lua | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 2f6057c1f8..ad2b3f5dab 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1042,43 +1042,32 @@ end --- ---@return string|nil # Deprecated message, or nil if no message was shown. function vim.deprecate(name, alternative, version, plugin, backtrace) - vim.validate { - name = { name, 'string' }, - alternative = { alternative, 'string', true }, - version = { version, 'string', true }, - plugin = { plugin, 'string', true }, - } plugin = plugin or 'Nvim' local will_be_removed = 'will be removed' -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md. -- Example: if removal_version is 0.12 (soft-deprecated since 0.10-dev), show warnings starting at - -- 0.11, including 0.11-dev (hard_deprecated_since = 0.11-dev). + -- 0.11, including 0.11-dev if plugin == 'Nvim' then - local current_version = vim.version() ---@type vim.Version - local removal_version = assert(vim.version.parse(version)) - local is_hard_deprecated ---@type boolean - - if removal_version.minor > 0 then - local hard_deprecated_since = assert(vim.version._version({ - major = removal_version.major, - minor = removal_version.minor - 1, - patch = 0, - prerelease = 'dev', -- Show deprecation warnings in devel (nightly) version as well - })) - is_hard_deprecated = (current_version >= hard_deprecated_since) - else - -- Assume there will be no next minor version before bumping up the major version; - -- therefore we can always show a warning. - assert(removal_version.minor == 0, vim.inspect(removal_version)) - is_hard_deprecated = true - end + local major, minor = version:match('(%d+)%.(%d+)') + major, minor = tonumber(major), tonumber(minor) + local hard_deprecated_since = string.format('nvim-%d.%d', major, minor - 1) + -- Assume there will be no next minor version before bumping up the major version + local is_hard_deprecated = minor == 0 or vim.fn.has(hard_deprecated_since) == 1 if not is_hard_deprecated then return - elseif current_version >= removal_version then - will_be_removed = 'was removed' end + + local removal_version = string.format('nvim-%d.%d', major, minor) + will_be_removed = vim.fn.has(removal_version) == 1 and 'was removed' or will_be_removed + else + vim.validate { + name = { name, 'string' }, + alternative = { alternative, 'string', true }, + version = { version, 'string', true }, + plugin = { plugin, 'string', true }, + } end local msg = ('%s is deprecated'):format(name) -- cgit From c81b7849a0f677164c01cf84ecfb25c1f47acf21 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 23 Apr 2024 19:05:01 +0200 Subject: refactor(lsp): merge subtypes and supertypes into typehierarchy (#28467) Both methods had pretty much the same documentation and shared the implementation. --- runtime/lua/vim/lsp/buf.lua | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index fa0cbab138..79c1aeb53d 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -495,11 +495,17 @@ function M.outgoing_calls() call_hierarchy(ms.callHierarchy_outgoingCalls) end ---- @param method string -local function type_hierarchy(method) +--- Lists all the subtypes or supertypes of the symbol under the +--- cursor in the |quickfix| window. If the symbol can resolve to +--- multiple items, the user can pick one using |vim.ui.select()|. +---@param kind "subtypes"|"supertypes" +function M.typehierarchy(kind) + local method = kind == 'subtypes' and ms.typeHierarchy_subtypes or ms.typeHierarchy_supertypes + --- Merge results from multiple clients into a single table. Client-ID is preserved. --- --- @param results table + --- @return [integer, lsp.TypeHierarchyItem][] local function merge_results(results) local merged_results = {} for client_id, client_result in pairs(results) do @@ -525,11 +531,9 @@ local function type_hierarchy(method) end if #merged_results == 1 then - --- @type {integer, lsp.TypeHierarchyItem} local item = merged_results[1] local client = vim.lsp.get_client_by_id(item[1]) if client then - --- @type lsp.TypeHierarchyItem client.request(method, { item = item[2] }, nil, bufnr) else vim.notify( @@ -565,20 +569,6 @@ local function type_hierarchy(method) end) end ---- Lists all the subtypes of the symbol under the ---- cursor in the |quickfix| window. If the symbol can resolve to ---- multiple items, the user can pick one using |vim.ui.select()|. -function M.subtypes() - type_hierarchy(ms.typeHierarchy_subtypes) -end - ---- Lists all the supertypes of the symbol under the ---- cursor in the |quickfix| window. If the symbol can resolve to ---- multiple items, the user can pick one using |vim.ui.select()|. -function M.supertypes() - type_hierarchy(ms.typeHierarchy_supertypes) -end - --- List workspace folders. --- function M.list_workspace_folders() -- cgit From 7f084770c23855083776b0598f2f54bb59a06875 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 24 Apr 2024 21:47:02 +0200 Subject: perf(diagnostic): avoid table copies to filter by severity (#28491) Instead of adding all diagnostics matching lnum filters to a table, and then copying that table to another table while applying the severity filter, this changes the flow to only add diagnostics matching both filters in the first pass. --- runtime/lua/vim/diagnostic.lua | 59 +++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 26 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index b42eece4c2..3321b6ad71 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -372,43 +372,46 @@ local function to_severity(severity) end --- @param severity vim.diagnostic.SeverityFilter ---- @param diagnostics vim.Diagnostic[] ---- @return vim.Diagnostic[] -local function filter_by_severity(severity, diagnostics) - if not severity then - return diagnostics - end - +--- @return fun(vim.Diagnostic):boolean +local function severity_predicate(severity) if type(severity) ~= 'table' then severity = assert(to_severity(severity)) - --- @param t vim.Diagnostic - return vim.tbl_filter(function(t) - return t.severity == severity - end, diagnostics) + ---@param d vim.Diagnostic + return function(d) + return d.severity == severity + end end - if severity.min or severity.max then --- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity} local min_severity = to_severity(severity.min) or M.severity.HINT local max_severity = to_severity(severity.max) or M.severity.ERROR - --- @param t vim.Diagnostic - return vim.tbl_filter(function(t) - return t.severity <= min_severity and t.severity >= max_severity - end, diagnostics) + --- @param d vim.Diagnostic + return function(d) + return d.severity <= min_severity and d.severity >= max_severity + end end --- @cast severity vim.diagnostic.Severity[] - local severities = {} --- @type table for _, s in ipairs(severity) do severities[assert(to_severity(s))] = true end - --- @param t vim.Diagnostic - return vim.tbl_filter(function(t) - return severities[t.severity] - end, diagnostics) + --- @param d vim.Diagnostic + return function(d) + return severities[d.severity] + end +end + +--- @param severity vim.diagnostic.SeverityFilter +--- @param diagnostics vim.Diagnostic[] +--- @return vim.Diagnostic[] +local function filter_by_severity(severity, diagnostics) + if not severity then + return diagnostics + end + return vim.tbl_filter(severity_predicate(severity), diagnostics) end --- @param bufnr integer @@ -714,10 +717,18 @@ local function get_diagnostics(bufnr, opts, clamp) end, }) + local match_severity = opts.severity and severity_predicate(opts.severity) + or function(_) + return true + end + ---@param b integer ---@param d vim.Diagnostic local function add(b, d) - if not opts.lnum or (opts.lnum >= d.lnum and opts.lnum <= (d.end_lnum or d.lnum)) then + if + match_severity(d) + and (not opts.lnum or (opts.lnum >= d.lnum and opts.lnum <= (d.end_lnum or d.lnum))) + then if clamp and api.nvim_buf_is_loaded(b) then local line_count = buf_line_count[b] - 1 if @@ -771,10 +782,6 @@ local function get_diagnostics(bufnr, opts, clamp) end end - if opts.severity then - diagnostics = filter_by_severity(opts.severity, diagnostics) - end - return diagnostics end -- cgit From 38b9c322c97b63f53caef7a651211fc9312d055e Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:43:46 -0500 Subject: feat(fs): add vim.fs.root (#28477) vim.fs.root() is a function for finding a project root relative to a buffer using one or more "root markers". This is useful for LSP and could be useful for other "projects" designs, as well as for any plugins which work with a "projects" concept. --- runtime/lua/vim/fs.lua | 57 +++++++++++++++++++++++++++++++++++++++++++------ runtime/lua/vim/lsp.lua | 8 +++---- 2 files changed, 54 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 65ad58c720..766dc09691 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -197,13 +197,6 @@ end --- Examples: --- --- ```lua ---- -- location of Cargo.toml from the current buffer's path ---- local cargo = vim.fs.find('Cargo.toml', { ---- upward = true, ---- stop = vim.uv.os_homedir(), ---- path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)), ---- }) ---- --- -- list all test directories under the runtime directory --- local test_dirs = vim.fs.find( --- {'test', 'tst', 'testdir'}, @@ -334,6 +327,56 @@ function M.find(names, opts) return matches end +--- Find the first parent directory containing a specific "marker", relative to a buffer's +--- directory. +--- +--- Example: +--- +--- ```lua +--- -- Find the root of a Python project, starting from file 'main.py' +--- vim.fs.root(vim.fs.joinpath(vim.env.PWD, 'main.py'), {'pyproject.toml', 'setup.py' }) +--- +--- -- Find the root of a git repository +--- vim.fs.root(0, '.git') +--- +--- -- Find the parent directory containing any file with a .csproj extension +--- vim.fs.root(0, function(name, path) +--- return name:match('%.csproj$') ~= nil +--- end) +--- ``` +--- +--- @param source integer|string Buffer number (0 for current buffer) or file path to begin the +--- search from. +--- @param marker (string|string[]|fun(name: string, path: string): boolean) A marker, or list +--- of markers, to search for. If a function, the function is called for each +--- evaluated item and should return true if {name} and {path} are a match. +--- @return string? # Directory path containing one of the given markers, or nil if no directory was +--- found. +function M.root(source, marker) + assert(source, 'missing required argument: source') + assert(marker, 'missing required argument: marker') + + local path ---@type string + if type(source) == 'string' then + path = source + elseif type(source) == 'number' then + path = vim.api.nvim_buf_get_name(source) + else + error('invalid type for argument "source": expected string or buffer number') + end + + local paths = M.find(marker, { + upward = true, + path = path, + }) + + if #paths == 0 then + return nil + end + + return vim.fs.dirname(paths[1]) +end + --- Split a Windows path into a prefix and a body, such that the body can be processed like a POSIX --- path. The path must use forward slashes as path separator. --- diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index ab22c5901a..8403aa0ee6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -210,7 +210,7 @@ end --- vim.lsp.start({ --- name = 'my-server-name', --- cmd = {'name-of-language-server-executable'}, ---- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]), +--- root_dir = vim.fs.root(0, {'pyproject.toml', 'setup.py'}), --- }) --- ``` --- @@ -219,9 +219,9 @@ end --- - `name` arbitrary name for the LSP client. Should be unique per language server. --- - `cmd` command string[] or function, described at |vim.lsp.start_client()|. --- - `root_dir` path to the project root. By default this is used to decide if an existing client ---- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the ---- root by traversing the file system upwards starting from the current directory until either ---- a `pyproject.toml` or `setup.py` file is found. +--- should be re-used. The example above uses |vim.fs.root()| and |vim.fs.dirname()| to detect +--- the root by traversing the file system upwards starting from the current directory until +--- either a `pyproject.toml` or `setup.py` file is found. --- - `workspace_folders` list of `{ uri:string, name: string }` tables specifying the project root --- folders used by the language server. If `nil` the property is derived from `root_dir` for --- convenience. -- cgit From e0d92b9cc20b58179599f53dfa74ca821935a539 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 25 Apr 2024 04:15:58 -0700 Subject: fix(vim.ui)!: change open() to return pcall-like values #28502 Problem: `vim.ui.open` unnecessarily invents a different success/failure convention. Its return type was changed in 57adf8c6e01d, so we might as well change it to have a more conventional form. Solution: Change the signature to use the `pcall` convention of `status, result`. --- runtime/lua/vim/_defaults.lua | 15 ++++++++------- runtime/lua/vim/lsp/handlers.lua | 6 +++--- runtime/lua/vim/ui.lua | 14 +++++++------- 3 files changed, 18 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 68ad95b725..419a29a5c6 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -98,18 +98,19 @@ do --- Map |gx| to call |vim.ui.open| on the at cursor. do local function do_open(uri) - local cmd, err = vim.ui.open(uri) - local rv = cmd and cmd:wait(1000) or nil - if cmd and rv and rv.code ~= 0 then - err = ('vim.ui.open: command %s (%d): %s'):format( + local ok, cmd_or_err = vim.ui.open(uri) + local rv = ok and (cmd_or_err --[[@as vim.SystemObj]]):wait(1000) or nil + if rv and rv.code ~= 0 then + ok = false + cmd_or_err = ('vim.ui.open: command %s (%d): %s'):format( (rv.code == 124 and 'timeout' or 'failed'), rv.code, - vim.inspect(cmd.cmd) + vim.inspect(cmd_or_err.cmd) ) end - if err then - vim.notify(err, vim.log.levels.ERROR) + if not ok then + vim.notify(cmd_or_err --[[@as string]], vim.log.levels.ERROR) end end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 4672d94105..ab4fa52c40 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -654,15 +654,15 @@ M[ms.window_showDocument] = function(_, result, ctx, _) if result.external then -- TODO(lvimuser): ask the user for confirmation - local cmd, err = vim.ui.open(uri) - local ret = cmd and cmd:wait(2000) or nil + local ok, cmd_or_err = vim.ui.open(uri) + local ret = ok and (cmd_or_err --[[@as vim.SystemObj]]):wait(2000) or nil if ret == nil or ret.code ~= 0 then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret and ret.stderr or err, + message = ret and ret.stderr or cmd_or_err, }, } end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index e02acaf25c..b8323efa66 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -118,16 +118,16 @@ end --- vim.ui.open("https://neovim.io/") --- vim.ui.open("~/path/to/file") --- -- Synchronous (wait until the process exits). ---- local cmd, err = vim.ui.open("$VIMRUNTIME") ---- if cmd then +--- local ok, cmd = vim.ui.open("$VIMRUNTIME") +--- if ok then --- cmd:wait() --- end --- ``` --- ---@param path string Path or URL to open --- ----@return vim.SystemObj|nil # Command object, or nil if not found. ----@return string|nil # Error message on failure +---@return boolean # false if command not found, else true. +---@return vim.SystemObj|string # Command object, or error message on failure --- ---@see |vim.system()| function M.open(path) @@ -147,7 +147,7 @@ function M.open(path) if vim.fn.executable('rundll32') == 1 then cmd = { 'rundll32', 'url.dll,FileProtocolHandler', path } else - return nil, 'vim.ui.open: rundll32 not found' + return false, 'vim.ui.open: rundll32 not found' end elseif vim.fn.executable('wslview') == 1 then cmd = { 'wslview', path } @@ -156,10 +156,10 @@ function M.open(path) elseif vim.fn.executable('xdg-open') == 1 then cmd = { 'xdg-open', path } else - return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)' + return false, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)' end - return vim.system(cmd, { text = true, detach = true }), nil + return true, vim.system(cmd, { text = true, detach = true }) end return M -- cgit From b13e63db1dbc1dbc7e23690653df1b7317660a2b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 25 Apr 2024 08:07:44 -0500 Subject: feat(diagnostic): goto functions jump to highest severity (#28490) When the "severity" option is nil, vim.diagnostic.goto_next() and vim.diagnostic.goto_prev() jump to the next diagnostic with the highest severity. --- runtime/lua/vim/diagnostic.lua | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 3321b6ad71..1ae370e9b2 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -820,11 +820,35 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 bufnr = get_bufnr(bufnr) local wrap = if_nil(opts.wrap, true) - local line_count = api.nvim_buf_line_count(bufnr) + local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) + + -- When severity is unset we jump to the diagnostic with the highest severity. First sort the + -- diagnostics by severity. The first diagnostic then contains the highest severity, and we can + -- discard all diagnostics with a lower severity. + if opts.severity == nil then + table.sort(diagnostics, function(a, b) + return a.severity < b.severity + end) + + -- Find the first diagnostic where the severity does not match the highest severity, and remove + -- that element and all subsequent elements from the array + local worst = (diagnostics[1] or {}).severity + local len = #diagnostics + for i = 2, len do + if diagnostics[i].severity ~= worst then + for j = i, len do + diagnostics[j] = nil + end + break + end + end + end + local line_diagnostics = diagnostic_lines(diagnostics) + local line_count = api.nvim_buf_line_count(bufnr) for i = 0, line_count do local offset = i * (search_forward and 1 or -1) local lnum = position[1] + offset @@ -1165,8 +1189,8 @@ end --- (default: `true`) --- @field wrap? boolean --- ---- See |diagnostic-severity|. ---- @field severity vim.diagnostic.Severity +--- See |diagnostic-severity|. If `nil`, go to the diagnostic with the highest severity. +--- @field severity? vim.diagnostic.Severity --- --- If `true`, call |vim.diagnostic.open_float()| after moving. --- If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|. -- cgit From 47dbda97d2f40729733b1c0d1d13d914065af23c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 26 Apr 2024 09:57:59 +0200 Subject: fix(lsp): buffer messages until connected to server (#28507) `handle:write(msg)` can fail if the socket is not yet connected to the server. Should address https://github.com/neovim/neovim/pull/28398#issuecomment-2078152491 --- runtime/lua/vim/lsp/rpc.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 6748b32ec0..d73e46edfb 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -645,9 +645,23 @@ function M.connect(host_or_path, port) or assert(uv.new_tcp(), 'Could not create new TCP socket') ) local closing = false + -- Connect returns a PublicClient synchronously so the caller + -- can immediately send messages before the connection is established + -- -> Need to buffer them until that happens + local connected = false + -- size should be enough because the client can't really do anything until initialization is done + -- which required a response from the server - implying the connection got established + local msgbuf = vim.ringbuf(10) local transport = { write = function(msg) - handle:write(msg) + if connected then + local _, err = handle:write(msg) + if err and not closing then + log.error('Error on handle:write: %q', err) + end + else + msgbuf:push(msg) + end end, is_closing = function() return closing @@ -679,6 +693,10 @@ function M.connect(host_or_path, port) handle:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err) client:on_error(M.client_errors.READ_ERROR, read_err) end)) + connected = true + for msg in msgbuf do + handle:write(msg) + end end if port == nil then handle:connect(host_or_path, on_connect) -- cgit From 567f8a300b2c12fbf5a8bf7d85c5714e8dcde79d Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Fri, 26 Apr 2024 19:25:55 +0800 Subject: refactor(lsp): rename foos_by_bar to bar_foos #28505 --- runtime/lua/vim/lsp/inlay_hint.lua | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 37b330f5f6..3d8b54ee3d 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -41,13 +41,13 @@ function M.on_inlayhint(err, result, ctx, _) bufstate.client_hints = vim.defaulttable() bufstate.version = ctx.version end - local hints_by_client = bufstate.client_hints + local client_hints = bufstate.client_hints local client = assert(vim.lsp.get_client_by_id(client_id)) - local new_hints_by_lnum = vim.defaulttable() + local new_lnum_hints = vim.defaulttable() local num_unprocessed = #result if num_unprocessed == 0 then - hints_by_client[client_id] = {} + client_hints[client_id] = {} bufstate.version = ctx.version api.nvim__buf_redraw_range(bufnr, 0, -1) return @@ -73,10 +73,10 @@ function M.on_inlayhint(err, result, ctx, _) for _, hint in ipairs(result) do local lnum = hint.position.line hint.position.character = pos_to_byte(hint.position) - table.insert(new_hints_by_lnum[lnum], hint) + table.insert(new_lnum_hints[lnum], hint) end - hints_by_client[client_id] = new_hints_by_lnum + client_hints[client_id] = new_lnum_hints bufstate.version = ctx.version api.nvim__buf_redraw_range(bufnr, 0, -1) end @@ -175,19 +175,19 @@ function M.get(filter) end --- @type vim.lsp.inlay_hint.get.ret[] - local hints = {} + local result = {} for _, client in pairs(clients) do - local hints_by_lnum = bufstate.client_hints[client.id] - if hints_by_lnum then + local lnum_hints = bufstate.client_hints[client.id] + if lnum_hints then for lnum = range.start.line, range['end'].line do - local line_hints = hints_by_lnum[lnum] or {} - for _, hint in pairs(line_hints) do + local hints = lnum_hints[lnum] or {} + for _, hint in pairs(hints) do local line, char = hint.position.line, hint.position.character if (line > range.start.line or char >= range.start.character) and (line < range['end'].line or char <= range['end'].character) then - table.insert(hints, { + table.insert(result, { bufnr = bufnr, client_id = client.id, inlay_hint = hint, @@ -197,7 +197,7 @@ function M.get(filter) end end end - return hints + return result end --- Clear inlay hints @@ -315,14 +315,14 @@ api.nvim_set_decoration_provider(namespace, { if not bufstate.client_hints then return end - local hints_by_client = assert(bufstate.client_hints) + local client_hints = assert(bufstate.client_hints) for lnum = topline, botline do if bufstate.applied[lnum] ~= bufstate.version then api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1) - for _, hints_by_lnum in pairs(hints_by_client) do - local line_hints = hints_by_lnum[lnum] or {} - for _, hint in pairs(line_hints) do + for _, lnum_hints in pairs(client_hints) do + local hints = lnum_hints[lnum] or {} + for _, hint in pairs(hints) do local text = '' local label = hint.label if type(label) == 'string' then -- cgit From 37d8e504593646c81542f8c66f0d608e0a59f036 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 26 Apr 2024 08:15:44 -0500 Subject: fix(lsp): add "silent" option to vim.lsp.start (#28478) vim.notify cannot be suppressed and it is not always necessary to display a visible warning to the user if the RPC process fails to start. For instance, a user may have the same LSP configuration across systems, some of which may not have all of the LSP server executables installed. In that case, the user receives a notification every time a file is opened that they cannot suppress. Instead of using vim.notify in vim.lsp.rpc, propagate a normal error up through the call stack and use vim.notify in vim.lsp.start() only if the "silent" option is not set. This also updates lsp.start_client() to return an error message as its second return value if an error occurred, rather than calling vim.notify directly. Callers of lsp.start_client() will need to update call sites appropriately if they wish to report errors to the user (or even better, switch to vim.lsp.start). --- runtime/lua/vim/lsp.lua | 43 ++++++++++++++++++++++++++---------------- runtime/lua/vim/lsp/client.lua | 14 +++----------- runtime/lua/vim/lsp/rpc.lua | 5 ++--- 3 files changed, 32 insertions(+), 30 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 8403aa0ee6..fcb1ad5b4b 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -195,10 +195,13 @@ end --- Predicate used to decide if a client should be re-used. Used on all --- running clients. The default implementation re-uses a client if name and --- root_dir matches. ---- @field reuse_client fun(client: vim.lsp.Client, config: table): boolean +--- @field reuse_client fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean --- --- Buffer handle to attach to if starting or re-using a client (0 for current). --- @field bufnr integer +--- +--- Suppress error reporting if the LSP server fails to start (default false). +--- @field silent boolean --- Create a new LSP client and start a language server or reuses an already --- running client if one is found matching `name` and `root_dir`. @@ -246,19 +249,25 @@ function lsp.start(config, opts) for _, client in pairs(all_clients) do if reuse_client(client, config) then - lsp.buf_attach_client(bufnr, client.id) - return client.id + if lsp.buf_attach_client(bufnr, client.id) then + return client.id + end end end - local client_id = lsp.start_client(config) + local client_id, err = lsp.start_client(config) + if err then + if not opts.silent then + vim.notify(err, vim.log.levels.WARN) + end + return nil + end - if not client_id then - return -- lsp.start_client will have printed an error + if client_id and lsp.buf_attach_client(bufnr, client_id) then + return client_id end - lsp.buf_attach_client(bufnr, client_id) - return client_id + return nil end --- Consumes the latest progress messages from all clients and formats them as a string. @@ -420,16 +429,18 @@ end --- Starts and initializes a client with the given configuration. --- @param config vim.lsp.ClientConfig Configuration for the server. ---- @return integer|nil client_id |vim.lsp.get_client_by_id()| Note: client may not be ---- fully initialized. Use `on_init` to do any actions once ---- the client has been initialized. +--- @return integer? client_id |vim.lsp.get_client_by_id()| Note: client may not be +--- fully initialized. Use `on_init` to do any actions once +--- the client has been initialized. +--- @return string? # Error message, if any function lsp.start_client(config) - local client = require('vim.lsp.client').create(config) - - if not client then - return + local ok, res = pcall(require('vim.lsp.client').create, config) + if not ok then + return nil, res --[[@as string]] end + local client = assert(res) + --- @diagnostic disable-next-line: invisible table.insert(client._on_exit_cbs, on_client_exit) @@ -437,7 +448,7 @@ function lsp.start_client(config) client:initialize() - return client.id + return client.id, nil end --- Notify all attached clients that a buffer has changed. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 98b3cfc762..448f986cd3 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -37,7 +37,7 @@ local validate = vim.validate --- `is_closing` and `terminate`. --- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|. --- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()| ---- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient? +--- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient --- --- Directory to launch the `cmd` process. Not related to `root_dir`. --- (default: cwd) @@ -506,25 +506,17 @@ function Client.create(config) } -- Start the RPC client. - local rpc --- @type vim.lsp.rpc.PublicClient? local config_cmd = config.cmd if type(config_cmd) == 'function' then - rpc = config_cmd(dispatchers) + self.rpc = config_cmd(dispatchers) else - rpc = lsp.rpc.start(config_cmd, dispatchers, { + self.rpc = lsp.rpc.start(config_cmd, dispatchers, { cwd = config.cmd_cwd, env = config.cmd_env, detached = config.detached, }) end - -- Return nil if the rpc client fails to start - if not rpc then - return - end - - self.rpc = rpc - setmetatable(self, Client) return self diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index d73e46edfb..3c63a12da2 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -722,7 +722,7 @@ end --- @param cmd string[] Command to start the LSP server. --- @param dispatchers? vim.lsp.rpc.Dispatchers --- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams ---- @return vim.lsp.rpc.PublicClient? : Client RPC object, with these methods: +--- @return vim.lsp.rpc.PublicClient : Client RPC object, with these methods: --- - `notify()` |vim.lsp.rpc.notify()| --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. @@ -797,8 +797,7 @@ function M.start(cmd, dispatchers, extra_spawn_params) end local msg = string.format('Spawning language server with cmd: `%s` failed%s', vim.inspect(cmd), sfx) - vim.notify(msg, vim.log.levels.WARN) - return nil + error(msg) end sysobj = sysobj_or_err --[[@as vim.SystemObj]] -- cgit From c5b9fb2f256516398592c81f496dae75a036b18e Mon Sep 17 00:00:00 2001 From: TheLeoP Date: Fri, 26 Apr 2024 08:28:22 -0500 Subject: fix(treesitter.foldexpr): check for all insert submodes --- runtime/lua/vim/treesitter/_fold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d511bef7a5..09d3f3368f 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -258,7 +258,7 @@ function FoldInfo:foldupdate(bufnr, srow, erow) self.foldupdate_range = { srow, erow } end - if api.nvim_get_mode().mode == 'i' then + if api.nvim_get_mode().mode:match('^i') then -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave if #(api.nvim_get_autocmds({ group = group, -- cgit From b8273c9a339626078d49e706d882878090b07d42 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 26 Apr 2024 14:07:47 +0100 Subject: fix: lua annotations --- runtime/lua/vim/_defaults.lua | 2 + runtime/lua/vim/_inspector.lua | 6 +-- runtime/lua/vim/_meta/base64.lua | 4 +- runtime/lua/vim/_meta/builtin.lua | 25 ++++++------ runtime/lua/vim/_meta/diff.lua | 85 ++++++++++++++++++++------------------- runtime/lua/vim/_meta/re.lua | 4 +- runtime/lua/vim/_meta/spell.lua | 6 +-- runtime/lua/vim/treesitter.lua | 2 +- 8 files changed, 68 insertions(+), 66 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 419a29a5c6..25bef2352c 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -196,6 +196,7 @@ do group = nvim_terminal_augroup, desc = 'Respond to OSC foreground/background color requests', callback = function(args) + --- @type integer local channel = vim.bo[args.buf].channel if channel == 0 then return @@ -270,6 +271,7 @@ if tty then -- Wait until Nvim is finished starting to set the option to ensure the -- OptionSet event fires. if vim.v.vim_did_enter == 1 then + --- @diagnostic disable-next-line:no-unknown vim.o[option] = value else vim.api.nvim_create_autocmd('VimEnter', { diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index afbd6211cd..f5d1640c82 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -55,8 +55,8 @@ function vim.inspect_pos(bufnr, row, col, filter) bufnr = bufnr == 0 and vim.api.nvim_get_current_buf() or bufnr local results = { - treesitter = {}, - syntax = {}, + treesitter = {}, --- @type table[] + syntax = {}, --- @type table[] extmarks = {}, semantic_tokens = {}, buffer = bufnr, @@ -93,7 +93,7 @@ function vim.inspect_pos(bufnr, row, col, filter) end -- namespace id -> name map - local nsmap = {} + local nsmap = {} --- @type table for name, id in pairs(vim.api.nvim_get_namespaces()) do nsmap[id] = name end diff --git a/runtime/lua/vim/_meta/base64.lua b/runtime/lua/vim/_meta/base64.lua index f25b4af234..8ba59e1703 100644 --- a/runtime/lua/vim/_meta/base64.lua +++ b/runtime/lua/vim/_meta/base64.lua @@ -3,11 +3,11 @@ --- Encode {str} using Base64. --- --- @param str string String to encode ---- @return string Encoded string +--- @return string : Encoded string function vim.base64.encode(str) end --- Decode a Base64 encoded string. --- --- @param str string Base64 encoded string ---- @return string Decoded string +--- @return string : Decoded string function vim.base64.decode(str) end diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 20b6d9dabe..75737bd040 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -119,15 +119,15 @@ function vim.stricmp(a, b) end --- An {index} in the middle of a UTF-16 sequence is rounded upwards to --- the end of that sequence. --- @param str string ---- @param index number ---- @param use_utf16? any +--- @param index integer +--- @param use_utf16? boolean function vim.str_byteindex(str, index, use_utf16) end --- Gets a list of the starting byte positions of each UTF-8 codepoint in the given string. --- --- Embedded NUL bytes are treated as terminating the string. --- @param str string ---- @return table +--- @return integer[] function vim.str_utf_pos(str) end --- Gets the distance (in bytes) from the starting byte of the codepoint (character) that {index} @@ -148,8 +148,8 @@ function vim.str_utf_pos(str) end --- ``` --- --- @param str string ---- @param index number ---- @return number +--- @param index integer +--- @return integer function vim.str_utf_start(str, index) end --- Gets the distance (in bytes) from the last byte of the codepoint (character) that {index} points @@ -168,8 +168,8 @@ function vim.str_utf_start(str, index) end --- ``` --- --- @param str string ---- @param index number ---- @return number +--- @param index integer +--- @return integer function vim.str_utf_end(str, index) end --- Convert byte index to UTF-32 and UTF-16 indices. If {index} is not @@ -180,7 +180,7 @@ function vim.str_utf_end(str, index) end --- {index} in the middle of a UTF-8 sequence is rounded upwards to the end of --- that sequence. --- @param str string ---- @param index? number +--- @param index? integer --- @return integer UTF-32 index --- @return integer UTF-16 index function vim.str_utfindex(str, index) end @@ -193,15 +193,14 @@ function vim.str_utfindex(str, index) end --- can accept, see ":Man 3 iconv". --- --- @param str string Text to convert ---- @param from number Encoding of {str} ---- @param to number Target encoding ---- @param opts? table ---- @return string|nil Converted string if conversion succeeds, `nil` otherwise. +--- @param from string Encoding of {str} +--- @param to string Target encoding +--- @return string? : Converted string if conversion succeeds, `nil` otherwise. function vim.iconv(str, from, to, opts) end --- Schedules {fn} to be invoked soon by the main event-loop. Useful --- to avoid |textlock| or other temporary restrictions. ---- @param fn function +--- @param fn fun() function vim.schedule(fn) end --- Wait for {time} in milliseconds until {callback} returns `true`. diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua index f265139448..617bc87f59 100644 --- a/runtime/lua/vim/_meta/diff.lua +++ b/runtime/lua/vim/_meta/diff.lua @@ -1,5 +1,46 @@ ---@meta +--- Optional parameters: +--- @class vim.diff.Opts +--- @inlinedoc +--- +--- Invoked for each hunk in the diff. Return a negative number +--- to cancel the callback for any remaining hunks. +--- Arguments: +--- - `start_a` (`integer`): Start line of hunk in {a}. +--- - `count_a` (`integer`): Hunk size in {a}. +--- - `start_b` (`integer`): Start line of hunk in {b}. +--- - `count_b` (`integer`): Hunk size in {b}. +--- @field on_hunk fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer +--- +--- Form of the returned diff: +--- - `unified`: String in unified format. +--- - `indices`: Array of hunk locations. +--- Note: This option is ignored if `on_hunk` is used. +--- (default: `'unified'`) +--- @field result_type 'unified'|'indices' +--- +--- Run linematch on the resulting hunks from xdiff. When integer, only hunks +--- upto this size in lines are run through linematch. +--- Requires `result_type = indices`, ignored otherwise. +--- @field linematch boolean|integer +--- +--- Diff algorithm to use. Values: +--- - `myers`: the default algorithm +--- - `minimal`: spend extra time to generate the smallest possible diff +--- - `patience`: patience diff algorithm +--- - `histogram`: histogram diff algorithm +--- (default: `'myers'`) +--- @field algorithm 'myers'|'minimal'|'patience'|'histogram' +--- @field ctxlen integer Context length +--- @field interhunkctxlen integer Inter hunk context length +--- @field ignore_whitespace boolean Ignore whitespace +--- @field ignore_whitespace_change boolean Ignore whitespace change +--- @field ignore_whitespace_change_at_eol boolean Ignore whitespace change at end-of-line. +--- @field ignore_cr_at_eol boolean Ignore carriage return at end-of-line +--- @field ignore_blank_lines boolean Ignore blank lines +--- @field indent_heuristic boolean Use the indent heuristic for the internal diff library. + -- luacheck: no unused args --- Run diff on strings {a} and {b}. Any indices returned by this function, @@ -24,47 +65,7 @@ --- ---@param a string First string to compare ---@param b string Second string to compare ----@param opts table Optional parameters: ---- - `on_hunk` (callback): ---- Invoked for each hunk in the diff. Return a negative number ---- to cancel the callback for any remaining hunks. ---- Args: ---- - `start_a` (integer): Start line of hunk in {a}. ---- - `count_a` (integer): Hunk size in {a}. ---- - `start_b` (integer): Start line of hunk in {b}. ---- - `count_b` (integer): Hunk size in {b}. ---- - `result_type` (string): Form of the returned diff: ---- - "unified": (default) String in unified format. ---- - "indices": Array of hunk locations. ---- Note: This option is ignored if `on_hunk` is used. ---- - `linematch` (boolean|integer): Run linematch on the resulting hunks ---- from xdiff. When integer, only hunks upto this size in ---- lines are run through linematch. Requires `result_type = indices`, ---- ignored otherwise. ---- - `algorithm` (string): ---- Diff algorithm to use. Values: ---- - "myers" the default algorithm ---- - "minimal" spend extra time to generate the ---- smallest possible diff ---- - "patience" patience diff algorithm ---- - "histogram" histogram diff algorithm ---- - `ctxlen` (integer): Context length ---- - `interhunkctxlen` (integer): ---- Inter hunk context length ---- - `ignore_whitespace` (boolean): ---- Ignore whitespace ---- - `ignore_whitespace_change` (boolean): ---- Ignore whitespace change ---- - `ignore_whitespace_change_at_eol` (boolean) ---- Ignore whitespace change at end-of-line. ---- - `ignore_cr_at_eol` (boolean) ---- Ignore carriage return at end-of-line ---- - `ignore_blank_lines` (boolean) ---- Ignore blank lines ---- - `indent_heuristic` (boolean): ---- Use the indent heuristic for the internal ---- diff library. ---- ----@return string|table|nil +---@param opts vim.diff.Opts +---@return string|integer[][]? --- See {opts.result_type}. `nil` if {opts.on_hunk} is given. function vim.diff(a, b, opts) end diff --git a/runtime/lua/vim/_meta/re.lua b/runtime/lua/vim/_meta/re.lua index 14c94c7824..9721e6f8c8 100644 --- a/runtime/lua/vim/_meta/re.lua +++ b/runtime/lua/vim/_meta/re.lua @@ -30,8 +30,8 @@ function vim.re.compile(string, defs) end --- @param subject string --- @param pattern vim.lpeg.Pattern|string --- @param init? integer ---- @return integer|nil the index where the occurrence starts, nil if no match ---- @return integer|nil the index where the occurrence ends, nil if no match +--- @return integer|nil : the index where the occurrence starts, nil if no match +--- @return integer|nil : the index where the occurrence ends, nil if no match function vim.re.find(subject, pattern, init) end --- Does a global substitution, replacing all occurrences of {pattern} in the given {subject} by diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua index 57f2180895..c636db3b53 100644 --- a/runtime/lua/vim/_meta/spell.lua +++ b/runtime/lua/vim/_meta/spell.lua @@ -3,11 +3,11 @@ -- luacheck: no unused args --- Check {str} for spelling errors. Similar to the Vimscript function ---- |spellbadword()|. +--- [spellbadword()]. --- --- Note: The behaviour of this function is dependent on: 'spelllang', --- 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be local to ---- the buffer. Consider calling this with |nvim_buf_call()|. +--- the buffer. Consider calling this with [nvim_buf_call()]. --- --- Example: --- @@ -20,7 +20,7 @@ --- ``` --- --- @param str string ---- @return {[1]: string, [2]: string, [3]: string}[] +--- @return {[1]: string, [2]: 'bad'|'rare'|'local'|'caps', [3]: integer}[] --- List of tuples with three items: --- - The badly spelled word. --- - The type of the spelling error: diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index a09619f369..4f4762547a 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -257,7 +257,7 @@ end ---@param row integer Position row ---@param col integer Position column --- ----@return table[] List of captures `{ capture = "name", metadata = { ... } }` +---@return {capture: string, lang: string, metadata: table}[] function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then bufnr = api.nvim_get_current_buf() -- cgit From b2c26a875b9dfd17fd05cf01cf5cc13eb2a10dfd Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 26 Apr 2024 14:58:17 +0100 Subject: fix(lsp): ensure buffer is not attached more than once Fixes regression introduced in #28030 If an LSP server is restarted, then the associated `nvim_buf_attach` call will not detach if no buffer changes are sent between the client stopping and a new one being created. This leads to `nvim_buf_attach` being called multiple times for the same buffer, which then leads to changetracking sending duplicate requests to the server (one per attach). To solve this, introduce separate tracking (client agnostic) on which buffers have had calls to `nvim_buf_attach`. --- runtime/lua/vim/lsp.lua | 55 +++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fcb1ad5b4b..25feeb0e8d 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -451,30 +451,6 @@ function lsp.start_client(config) return client.id, nil end ---- Notify all attached clients that a buffer has changed. ----@param _ integer ----@param bufnr integer ----@param changedtick integer ----@param firstline integer ----@param lastline integer ----@param new_lastline integer ----@return true? -local function text_document_did_change_handler( - _, - bufnr, - changedtick, - firstline, - lastline, - new_lastline -) - -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached - if #lsp.get_clients({ bufnr = bufnr }) == 0 then - return true - end - util.buf_versions[bufnr] = changedtick - changetracking.send_changes(bufnr, firstline, lastline, new_lastline) -end - ---Buffer lifecycle handler for textDocument/didSave --- @param bufnr integer local function text_document_did_save_handler(bufnr) @@ -516,11 +492,18 @@ local function text_document_did_save_handler(bufnr) end end +--- @type table +local attached_buffers = {} + --- @param bufnr integer ---- @param client_id integer -local function buf_attach(bufnr, client_id) +local function buf_attach(bufnr) + if attached_buffers[bufnr] then + return + end + attached_buffers[bufnr] = true + local uri = vim.uri_from_bufnr(bufnr) - local augroup = ('lsp_c_%d_b_%d_save'):format(client_id, bufnr) + local augroup = ('lsp_b_%d_save'):format(bufnr) local group = api.nvim_create_augroup(augroup, { clear = true }) api.nvim_create_autocmd('BufWritePre', { group = group, @@ -559,7 +542,14 @@ local function buf_attach(bufnr, client_id) }) -- First time, so attach and set up stuff. api.nvim_buf_attach(bufnr, false, { - on_lines = text_document_did_change_handler, + on_lines = function(_, _, changedtick, firstline, lastline, new_lastline) + if #lsp.get_clients({ bufnr = bufnr }) == 0 then + return true -- detach + end + util.buf_versions[bufnr] = changedtick + changetracking.send_changes(bufnr, firstline, lastline, new_lastline) + end, + on_reload = function() local params = { textDocument = { uri = uri } } for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do @@ -570,6 +560,7 @@ local function buf_attach(bufnr, client_id) client:_text_document_did_open_handler(bufnr) end end, + on_detach = function() local params = { textDocument = { uri = uri } } for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do @@ -582,7 +573,9 @@ local function buf_attach(bufnr, client_id) client.attached_buffers[bufnr] = nil end util.buf_versions[bufnr] = nil + attached_buffers[bufnr] = nil end, + -- TODO if we know all of the potential clients ahead of time, then we -- could conditionally set this. -- utf_sizes = size_index > 1; @@ -608,16 +601,14 @@ function lsp.buf_attach_client(bufnr, client_id) log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr)) return false end - -- This is our first time attaching to this buffer. - if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then - buf_attach(bufnr, client_id) - end local client = lsp.get_client_by_id(client_id) if not client then return false end + buf_attach(bufnr) + if client.attached_buffers[bufnr] then return true end -- cgit From 9b028bd64f4260a3a5576a3632f95a44bd658615 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 26 Apr 2024 08:43:29 -0700 Subject: refactor(vim.iter)!: rename xxback() => rxx() #28503 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: vim.iter has both `rfind()` and various `*back()` methods, which work in "reverse" or "backwards" order. It's inconsistent to have both kinds of names, and "back" is fairly uncommon (rust) compared to python (rfind, rstrip, rsplit, …). Solution: - Remove `nthback()` and let `nth()` take a negative index. - Because `rnth()` looks pretty obscure, and because it's intuitive for a function named `nth()` to take negative indexes. - Rename `xxback()` methods to `rxx()`. - This informally groups the "list-iterator" functions under a common `r` prefix, which helps discoverability. - Rename `peekback()` to `pop()`, in duality with the existing `peek`. --- runtime/lua/vim/iter.lua | 100 +++++++++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 46 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 302463b136..04137e18d4 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -630,7 +630,7 @@ function Iter:find(f) return unpack(result) end ---- Gets the first value in a |list-iterator| that satisfies a predicate, starting from the end. +--- Gets the first value satisfying a predicate, from the end of a |list-iterator|. --- --- Advances the iterator. Returns nil and drains the iterator if no value is found. --- @@ -717,19 +717,19 @@ end --- --- ```lua --- local it = vim.iter({1, 2, 3, 4}) ---- it:nextback() +--- it:pop() --- -- 4 ---- it:nextback() +--- it:pop() --- -- 3 --- ``` --- ---@return any -function Iter:nextback() - error('nextback() requires a list-like table') +function Iter:pop() + error('pop() requires a list-like table') end --- @nodoc -function ListIter:nextback() +function ListIter:pop() if self._head ~= self._tail then local inc = self._head < self._tail and 1 or -1 self._tail = self._tail - inc @@ -739,27 +739,27 @@ end --- Gets the last value of a |list-iterator| without consuming it. --- ---- See also |Iter:last()|. ---- --- Example: --- --- ```lua --- local it = vim.iter({1, 2, 3, 4}) ---- it:peekback() +--- it:rpeek() --- -- 4 ---- it:peekback() +--- it:rpeek() --- -- 4 ---- it:nextback() +--- it:pop() --- -- 4 --- ``` --- +---@see Iter.last +--- ---@return any -function Iter:peekback() - error('peekback() requires a list-like table') +function Iter:rpeek() + error('rpeek() requires a list-like table') end ---@nodoc -function ListIter:peekback() +function ListIter:rpeek() if self._head ~= self._tail then local inc = self._head < self._tail and 1 or -1 return self._table[self._tail - inc] @@ -797,27 +797,27 @@ function ListIter:skip(n) return self end ---- Skips `n` values backwards from the end of a |list-iterator| pipeline. +--- Discards `n` values from the end of a |list-iterator| pipeline. --- --- Example: --- --- ```lua ---- local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2) +--- local it = vim.iter({ 1, 2, 3, 4, 5 }):rskip(2) --- it:next() --- -- 1 ---- it:nextback() +--- it:pop() --- -- 3 --- ``` --- ---@param n number Number of values to skip. ---@return Iter ---@diagnostic disable-next-line: unused-local -function Iter:skipback(n) -- luacheck: no unused args - error('skipback() requires a list-like table') +function Iter:rskip(n) -- luacheck: no unused args + error('rskip() requires a list-like table') end ---@private -function ListIter:skipback(n) +function ListIter:rskip(n) local inc = self._head < self._tail and n or -n self._tail = self._tail - inc if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then @@ -828,51 +828,37 @@ end --- Gets the nth value of an iterator (and advances to it). --- +--- If `n` is negative, offsets from the end of a |list-iterator|. +--- --- Example: --- --- ```lua ---- --- local it = vim.iter({ 3, 6, 9, 12 }) --- it:nth(2) --- -- 6 --- it:nth(2) --- -- 12 --- ---- ``` ---- ----@param n number The index of the value to return. ----@return any -function Iter:nth(n) - if n > 0 then - return self:skip(n - 1):next() - end -end - ---- Gets the nth value from the end of a |list-iterator| (and advances to it). ---- ---- Example: ---- ---- ```lua ---- ---- local it = vim.iter({ 3, 6, 9, 12 }) ---- it:nthback(2) +--- local it2 = vim.iter({ 3, 6, 9, 12 }) +--- it2:nth(-2) --- -- 9 ---- it:nthback(2) +--- it2:nth(-2) --- -- 3 ---- --- ``` --- ----@param n number The index of the value to return. +---@param n number Index of the value to return. May be negative if the source is a |list-iterator|. ---@return any -function Iter:nthback(n) +function Iter:nth(n) if n > 0 then - return self:skipback(n - 1):nextback() + return self:skip(n - 1):next() + elseif n < 0 then + return self:rskip(math.abs(n) - 1):pop() end end --- Sets the start and end of a |list-iterator| pipeline. --- ---- Equivalent to `:skip(first - 1):skipback(len - last + 1)`. +--- Equivalent to `:skip(first - 1):rskip(len - last + 1)`. --- ---@param first number ---@param last number @@ -884,7 +870,7 @@ end ---@private function ListIter:slice(first, last) - return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1)) + return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1)) end --- Returns true if any of the items in the iterator match the given predicate. @@ -950,6 +936,8 @@ end --- --- ``` --- +---@see Iter.rpeek +--- ---@return any function Iter:last() local last = self:next() @@ -1016,6 +1004,26 @@ function ListIter:enumerate() return self end +---@deprecated +function Iter:nextback() + error('Iter:nextback() was renamed to Iter:pop()') +end + +---@deprecated +function Iter:peekback() + error('Iter:peekback() was renamed to Iter:rpeek()') +end + +---@deprecated +function Iter:skipback() + error('Iter:skipback() was renamed to Iter:rskip()') +end + +---@deprecated +function Iter:nthback() + error('Iter:nthback() was removed, use Iter:nth() with negative index') +end + --- Creates a new Iter object from a table or other |iterable|. --- ---@param src table|function Table or iterator to drain values from -- cgit From 688860741589b4583129e426f4df0523f9213275 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:12:49 -0500 Subject: feat(lsp): add more LSP defaults (#28500) - crn for rename - crr for code actions - gr for references - (in Insert mode) for signature help --- runtime/lua/vim/_defaults.lua | 450 ++++++++++++++++++++++-------------------- runtime/lua/vim/lsp.lua | 2 +- 2 files changed, 240 insertions(+), 212 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 25bef2352c..b1cd4f6ec6 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -144,6 +144,31 @@ do end vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) end + + --- Default maps for LSP functions. + --- + --- These are mapped unconditionally to avoid confusion. If no server is attached, or if a server + --- does not support a capability, an error message is displayed rather than exhibiting different + --- behavior. + --- + --- See |gr-default|, |crn|, |crr|, |i_CTRL-S|. + do + vim.keymap.set('n', 'crn', function() + vim.lsp.buf.rename() + end, { desc = 'vim.lsp.buf.rename()' }) + + vim.keymap.set({ 'n', 'v' }, 'crr', function() + vim.lsp.buf.code_action() + end, { desc = 'vim.lsp.buf.code_action()' }) + + vim.keymap.set('n', 'gr', function() + vim.lsp.buf.references() + end, { desc = 'vim.lsp.buf.references()' }) + + vim.keymap.set('i', '', function() + vim.lsp.buf.signature_help() + end, { desc = 'vim.lsp.buf.signature_help()' }) + end end --- Default menus @@ -243,230 +268,140 @@ do vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid)) end, }) -end - --- Only do the following when the TUI is attached -local tty = nil -for _, ui in ipairs(vim.api.nvim_list_uis()) do - if ui.chan == 1 and ui.stdout_tty then - tty = ui - break - end -end - -if tty then - local group = vim.api.nvim_create_augroup('nvim_tty', {}) - --- Set an option after startup (so that OptionSet is fired), but only if not - --- already set by the user. - --- - --- @param option string Option name - --- @param value any Option value - local function setoption(option, value) - if vim.api.nvim_get_option_info2(option, {}).was_set then - -- Don't do anything if option is already set - return - end - - -- Wait until Nvim is finished starting to set the option to ensure the - -- OptionSet event fires. - if vim.v.vim_did_enter == 1 then - --- @diagnostic disable-next-line:no-unknown - vim.o[option] = value - else - vim.api.nvim_create_autocmd('VimEnter', { - group = group, - once = true, - nested = true, - callback = function() - setoption(option, value) - end, - }) + -- Only do the following when the TUI is attached + local tty = nil + for _, ui in ipairs(vim.api.nvim_list_uis()) do + if ui.chan == 1 and ui.stdout_tty then + tty = ui + break end end - --- Guess value of 'background' based on terminal color. - --- - --- We write Operating System Command (OSC) 11 to the terminal to request the - --- terminal's background color. We then wait for a response. If the response - --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then - --- compute the luminance[1] of the RGB color and classify it as light/dark - --- accordingly. Note that the color components may have anywhere from one to - --- four hex digits, and require scaling accordingly as values out of 4, 8, 12, - --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but - --- ignored in the calculations. - --- - --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 - do - --- Parse a string of hex characters as a color. - --- - --- The string can contain 1 to 4 hex characters. The returned value is - --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. - --- - --- For instance, if only a single hex char "a" is used, then this function - --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / - --- 256). + if tty then + local group = vim.api.nvim_create_augroup('nvim_tty', {}) + + --- Set an option after startup (so that OptionSet is fired), but only if not + --- already set by the user. --- - --- @param c string Color as a string of hex chars - --- @return number? Intensity of the color - local function parsecolor(c) - if #c == 0 or #c > 4 then - return nil + --- @param option string Option name + --- @param value any Option value + local function setoption(option, value) + if vim.api.nvim_get_option_info2(option, {}).was_set then + -- Don't do anything if option is already set + return end - local val = tonumber(c, 16) - if not val then - return nil + -- Wait until Nvim is finished starting to set the option to ensure the + -- OptionSet event fires. + if vim.v.vim_did_enter == 1 then + --- @diagnostic disable-next-line:no-unknown + vim.o[option] = value + else + vim.api.nvim_create_autocmd('VimEnter', { + group = group, + once = true, + nested = true, + callback = function() + setoption(option, value) + end, + }) end - - local max = tonumber(string.rep('f', #c), 16) - return val / max end - --- Parse an OSC 11 response - --- - --- Either of the two formats below are accepted: + --- Guess value of 'background' based on terminal color. --- - --- OSC 11 ; rgb:// + --- We write Operating System Command (OSC) 11 to the terminal to request the + --- terminal's background color. We then wait for a response. If the response + --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then + --- compute the luminance[1] of the RGB color and classify it as light/dark + --- accordingly. Note that the color components may have anywhere from one to + --- four hex digits, and require scaling accordingly as values out of 4, 8, 12, + --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but + --- ignored in the calculations. --- - --- or - --- - --- OSC 11 ; rgba:/// - --- - --- where - --- - --- , , , := h | hh | hhh | hhhh - --- - --- The alpha component is ignored, if present. - --- - --- @param resp string OSC 11 response - --- @return string? Red component - --- @return string? Green component - --- @return string? Blue component - local function parseosc11(resp) - local r, g, b - r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') - if not r and not g and not b then - local a - r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') - if not a or #a > 4 then - return nil, nil, nil + --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29 + do + --- Parse a string of hex characters as a color. + --- + --- The string can contain 1 to 4 hex characters. The returned value is + --- between 0.0 and 1.0 (inclusive) representing the intensity of the color. + --- + --- For instance, if only a single hex char "a" is used, then this function + --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 / + --- 256). + --- + --- @param c string Color as a string of hex chars + --- @return number? Intensity of the color + local function parsecolor(c) + if #c == 0 or #c > 4 then + return nil end - end - - if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then - return r, g, b - end - return nil, nil, nil - end - - local timer = assert(vim.uv.new_timer()) - - local id = vim.api.nvim_create_autocmd('TermResponse', { - group = group, - nested = true, - callback = function(args) - local resp = args.data ---@type string - local r, g, b = parseosc11(resp) - if r and g and b then - local rr = parsecolor(r) - local gg = parsecolor(g) - local bb = parsecolor(b) - - if rr and gg and bb then - local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) - local bg = luminance < 0.5 and 'dark' or 'light' - setoption('background', bg) - end - - return true + local val = tonumber(c, 16) + if not val then + return nil end - end, - }) - - io.stdout:write('\027]11;?\007') - - timer:start(1000, 0, function() - -- Delete the autocommand if no response was received - vim.schedule(function() - -- Suppress error if autocommand has already been deleted - pcall(vim.api.nvim_del_autocmd, id) - end) - if not timer:is_closing() then - timer:close() + local max = tonumber(string.rep('f', #c), 16) + return val / max end - end) - end - --- If the TUI (term_has_truecolor) was able to determine that the host - --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the - --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's - --- response indicates that it does support truecolor enable 'termguicolors', - --- but only if the user has not already disabled it. - do - if tty.rgb then - -- The TUI was able to determine truecolor support - setoption('termguicolors', true) - else - local caps = {} ---@type table - require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) - if not found then - return + --- Parse an OSC 11 response + --- + --- Either of the two formats below are accepted: + --- + --- OSC 11 ; rgb:// + --- + --- or + --- + --- OSC 11 ; rgba:/// + --- + --- where + --- + --- , , , := h | hh | hhh | hhhh + --- + --- The alpha component is ignored, if present. + --- + --- @param resp string OSC 11 response + --- @return string? Red component + --- @return string? Green component + --- @return string? Blue component + local function parseosc11(resp) + local r, g, b + r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$') + if not r and not g and not b then + local a + r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$') + if not a or #a > 4 then + return nil, nil, nil + end end - caps[cap] = true - if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then - setoption('termguicolors', true) + if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then + return r, g, b end - end) - local timer = assert(vim.uv.new_timer()) + return nil, nil, nil + end - -- Arbitrary colors to set in the SGR sequence - local r = 1 - local g = 2 - local b = 3 + local timer = assert(vim.uv.new_timer()) local id = vim.api.nvim_create_autocmd('TermResponse', { group = group, nested = true, callback = function(args) local resp = args.data ---@type string - local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') - - if decrqss then - -- The DECRQSS SGR response first contains attributes separated by - -- semicolons, followed by the SGR itself with parameters separated - -- by colons. Some terminals include "0" in the attribute list - -- unconditionally; others do not. Our SGR sequence did not set any - -- attributes, so there should be no attributes in the list. - local attrs = vim.split(decrqss, ';') - if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then - return false - end - - -- The returned SGR sequence should begin with 48:2 - local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') - if not sgr then - return false - end - - -- The remaining elements of the SGR sequence should be the 3 colors - -- we set. Some terminals also include an additional parameter - -- (which can even be empty!), so handle those cases as well - local params = vim.split(sgr, ':') - if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then - return true - end - - if - tonumber(params[#params - 2]) == r - and tonumber(params[#params - 1]) == g - and tonumber(params[#params]) == b - then - setoption('termguicolors', true) + local r, g, b = parseosc11(resp) + if r and g and b then + local rr = parsecolor(r) + local gg = parsecolor(g) + local bb = parsecolor(b) + + if rr and gg and bb then + local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb) + local bg = luminance < 0.5 and 'dark' or 'light' + setoption('background', bg) end return true @@ -474,16 +409,7 @@ if tty then end, }) - -- Write SGR followed by DECRQSS. This sets the background color then - -- immediately asks the terminal what the background color is. If the - -- terminal responds to the DECRQSS with the same SGR sequence that we - -- sent then the terminal supports truecolor. - local decrqss = '\027P$qm\027\\' - if os.getenv('TMUX') then - decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) - end - -- Reset attributes first, as other code may have set attributes. - io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + io.stdout:write('\027]11;?\007') timer:start(1000, 0, function() -- Delete the autocommand if no response was received @@ -497,13 +423,115 @@ if tty then end end) end + + --- If the TUI (term_has_truecolor) was able to determine that the host + --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the + --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's + --- response indicates that it does support truecolor enable 'termguicolors', + --- but only if the user has not already disabled it. + do + if tty.rgb then + -- The TUI was able to determine truecolor support + setoption('termguicolors', true) + else + local caps = {} ---@type table + require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) + if not found then + return + end + + caps[cap] = true + if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then + setoption('termguicolors', true) + end + end) + + local timer = assert(vim.uv.new_timer()) + + -- Arbitrary colors to set in the SGR sequence + local r = 1 + local g = 2 + local b = 3 + + local id = vim.api.nvim_create_autocmd('TermResponse', { + group = group, + nested = true, + callback = function(args) + local resp = args.data ---@type string + local decrqss = resp:match('^\027P1%$r([%d;:]+)m$') + + if decrqss then + -- The DECRQSS SGR response first contains attributes separated by + -- semicolons, followed by the SGR itself with parameters separated + -- by colons. Some terminals include "0" in the attribute list + -- unconditionally; others do not. Our SGR sequence did not set any + -- attributes, so there should be no attributes in the list. + local attrs = vim.split(decrqss, ';') + if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then + return false + end + + -- The returned SGR sequence should begin with 48:2 + local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$') + if not sgr then + return false + end + + -- The remaining elements of the SGR sequence should be the 3 colors + -- we set. Some terminals also include an additional parameter + -- (which can even be empty!), so handle those cases as well + local params = vim.split(sgr, ':') + if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then + return true + end + + if + tonumber(params[#params - 2]) == r + and tonumber(params[#params - 1]) == g + and tonumber(params[#params]) == b + then + setoption('termguicolors', true) + end + + return true + end + end, + }) + + -- Write SGR followed by DECRQSS. This sets the background color then + -- immediately asks the terminal what the background color is. If the + -- terminal responds to the DECRQSS with the same SGR sequence that we + -- sent then the terminal supports truecolor. + local decrqss = '\027P$qm\027\\' + if os.getenv('TMUX') then + decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027')) + end + -- Reset attributes first, as other code may have set attributes. + io.stdout:write(string.format('\027[0m\027[48;2;%d;%d;%dm%s', r, g, b, decrqss)) + + timer:start(1000, 0, function() + -- Delete the autocommand if no response was received + vim.schedule(function() + -- Suppress error if autocommand has already been deleted + pcall(vim.api.nvim_del_autocmd, id) + end) + + if not timer:is_closing() then + timer:close() + end + end) + end + end end end ---- Default 'grepprg' to ripgrep if available. -if vim.fn.executable('rg') == 1 then - -- Match :grep default, otherwise rg searches cwd by default - -- Use -uuu to make ripgrep not do its default filtering - vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul') - vim.o.grepformat = '%f:%l:%c:%m' +--- Default options +do + --- Default 'grepprg' to ripgrep if available. + if vim.fn.executable('rg') == 1 then + -- Match :grep default, otherwise rg searches cwd by default + -- Use -uuu to make ripgrep not do its default filtering + vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul') + vim.o.grepformat = '%f:%l:%c:%m' + end end diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 25feeb0e8d..3f459491f3 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -348,7 +348,7 @@ function lsp._set_defaults(client, bufnr) and is_empty_or_default(bufnr, 'keywordprg') and vim.fn.maparg('K', 'n', false, false) == '' then - vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr }) + vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr, desc = 'vim.lsp.buf.hover()' }) end end) if client.supports_method(ms.textDocument_diagnostic) then -- cgit From 73034611c25d16df5e87c8afb2d339a03a91bd0d Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 26 Apr 2024 13:16:12 -0500 Subject: feat(diagnostic): add default mappings for diagnostics (#16230) --- runtime/lua/vim/_defaults.lua | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index b1cd4f6ec6..98dfbf3225 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -127,7 +127,9 @@ do end, { desc = gx_desc }) end - --- Default maps for built-in commenting + --- Default maps for built-in commenting. + --- + --- See |gc-default| and |gcc-default|. do local operator_rhs = function() return require('vim._comment').operator() @@ -169,6 +171,35 @@ do vim.lsp.buf.signature_help() end, { desc = 'vim.lsp.buf.signature_help()' }) end + + --- Map [d and ]d to move to the previous/next diagnostic. Map d to open a floating window + --- for the diagnostic under the cursor. + --- + --- See |[d-default|, |]d-default|, and |CTRL-W_d-default|. + do + vim.keymap.set('n', ']d', function() + vim.diagnostic.goto_next({ float = false }) + end, { + desc = 'Jump to the next diagnostic with the highest severity', + }) + + vim.keymap.set('n', '[d', function() + vim.diagnostic.goto_prev({ float = false }) + end, { + desc = 'Jump to the previous diagnostic with the highest severity', + }) + + vim.keymap.set('n', 'd', function() + vim.diagnostic.open_float({ border = 'rounded' }) + end, { + desc = 'Open a floating window showing diagnostics under the cursor', + }) + + vim.keymap.set('n', '', 'd', { + remap = true, + desc = 'Open a floating window showing diagnostics under the cursor', + }) + end end --- Default menus -- cgit From 9b8a0755390b7eb3ad369f3a0a42eb9aecb8cbe2 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 26 Apr 2024 20:26:21 +0200 Subject: fix(lsp): change `silent` in lsp.start.Opts to optional (#28524) --- runtime/lua/vim/lsp.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 3f459491f3..c6d6c8a0cd 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -201,7 +201,7 @@ end --- @field bufnr integer --- --- Suppress error reporting if the LSP server fails to start (default false). ---- @field silent boolean +--- @field silent? boolean --- Create a new LSP client and start a language server or reuses an already --- running client if one is found matching `name` and `root_dir`. -- cgit From f1f5fb911b575372fef49a13d86079b5902ada76 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 27 Apr 2024 05:50:46 +0800 Subject: vim-patch:fe1e2b5e2d65 runtime(doc): clarify syntax vs matching mechanism fixes: vim/vim#14643 https://github.com/vim/vim/commit/fe1e2b5e2d65f05d820f17db935b15454a63be06 Co-authored-by: Christian Brabandt --- runtime/lua/vim/_meta/vimfn.lua | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index d010673d24..02d5eaf575 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -9752,6 +9752,10 @@ function vim.fn.synIDtrans(synID) end --- synconcealed(lnum, 5) [1, 'X', 2] --- synconcealed(lnum, 6) [0, '', 0] --- +--- Note: Doesn't consider |matchadd()| highlighting items, +--- since syntax and matching highlighting are two different +--- mechanisms |syntax-vs-match|. +--- --- @param lnum integer --- @param col integer --- @return {[1]: integer, [2]: string, [3]: integer} -- cgit From 96f59e1b9902f7622eaaea02eea0329a88b98202 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 28 Apr 2024 05:05:41 +0800 Subject: fix(diagnostic): invalid col number compare in next_diagnostic (#28397) Problem: when line is blank link then there will got an invalid column number in math.min compare. Solution: make sure the min column number is 0 not an illegal number. --- runtime/lua/vim/diagnostic.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 1ae370e9b2..9f1e6448e6 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -867,14 +867,14 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) return a.col < b.col end is_next = function(d) - return math.min(d.col, line_length - 1) > position[2] + return math.min(d.col, math.max(line_length - 1, 0)) > position[2] end else sort_diagnostics = function(a, b) return a.col > b.col end is_next = function(d) - return math.min(d.col, line_length - 1) < position[2] + return math.min(d.col, math.max(line_length - 1, 0)) < position[2] end end table.sort(line_diagnostics[lnum], sort_diagnostics) -- cgit From 4625394a767fab311f75ef40f4f15c661156e071 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sun, 28 Apr 2024 12:49:25 +0200 Subject: fix(snippet): do not add extra indent on newlines (#28538) Reverts parts of https://github.com/neovim/neovim/pull/27674 LSP snippets typically do include tabs or spaces to add extra indentation and don't rely on the client using `autoindent` functionality. For example: public static void main(String[] args) {\n\t${0}\n} Notice the `\t` after `{\n` Adding spaces or tabs independent of that breaks snippets for languages like Haskell where you can have snippets like: ${1:name} :: ${2}\n${1:name} ${3}= ${0:undefined} To generate: name :: name = undefined --- runtime/lua/vim/snippet.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index a1e3360b2d..37416c389f 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -459,8 +459,7 @@ function M.expand(input) end -- Add the base indentation. if i > 1 then - line = #line ~= 0 and base_indent .. line - or (expandtab and (' '):rep(shiftwidth) or '\t'):rep(vim.fn.indent('.') / shiftwidth + 1) + line = base_indent .. line end lines[#lines + 1] = line end -- cgit From 26b5405d181e8c9e75c4b4ec9aae963cc25f285f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 28 Apr 2024 16:27:47 +0200 Subject: fix(treesitter): enforce lowercase language names (#28546) * fix(treesitter): enforce lowercase language names Problem: On case-insensitive file systems (e.g., macOS), `has_parser` will return `true` for uppercase aliases, which will then try to inject the uppercase language unsuccessfully. Solution: Enforce and assume parser names to be lowercase when resolving language names. --- runtime/lua/vim/treesitter/language.lua | 3 +++ runtime/lua/vim/treesitter/languagetree.lua | 12 +----------- 2 files changed, 4 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 47abf65332..d0a74daa6c 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -88,6 +88,9 @@ function M.add(lang, opts) filetype = { filetype, { 'string', 'table' }, true }, }) + -- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems) + lang = lang:lower() + if vim._ts_has_language(lang) then M.register(lang, filetype) return diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 990debc77b..e618f29f8f 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -758,7 +758,6 @@ local has_parser = vim.func._memoize(1, function(lang) end) --- Return parser name for language (if exists) or filetype (if registered and exists). ---- Also attempts with the input lower-cased. --- ---@param alias string language or filetype name ---@return string? # resolved parser name @@ -772,19 +771,10 @@ local function resolve_lang(alias) return alias end - if has_parser(alias:lower()) then - return alias:lower() - end - local lang = vim.treesitter.language.get_lang(alias) if lang and has_parser(lang) then return lang end - - lang = vim.treesitter.language.get_lang(alias:lower()) - if lang and has_parser(lang) then - return lang - end end ---@private @@ -808,7 +798,7 @@ function LanguageTree:_get_injection(match, metadata) -- Lang should override any other language tag if name == 'injection.language' then local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) - lang = resolve_lang(text) + lang = resolve_lang(text:lower()) -- language names are always lower case elseif name == 'injection.filename' then local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) local ft = vim.filetype.match({ filename = text }) -- cgit From 83635e4e3db9a99c128e84e79deb590da354215d Mon Sep 17 00:00:00 2001 From: glepnir Date: Sun, 28 Apr 2024 23:15:10 +0800 Subject: fix(diagnostic): get border from config (#28531) Co-authored-by: Justin M. Keyes Co-authored-by: Gregory Anders --- runtime/lua/vim/_defaults.lua | 2 +- runtime/lua/vim/diagnostic.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 98dfbf3225..e9575c0e1e 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -190,7 +190,7 @@ do }) vim.keymap.set('n', 'd', function() - vim.diagnostic.open_float({ border = 'rounded' }) + vim.diagnostic.open_float() end, { desc = 'Open a floating window showing diagnostics under the cursor', }) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 9f1e6448e6..1ed09b51a5 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -152,6 +152,8 @@ local M = {} --- @field suffix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string) --- --- @field focus_id? string +--- +--- @field border? string see |vim.api.nvim_open_win()|. --- @class vim.diagnostic.Opts.Underline --- -- cgit From 513fc461957f370f9e89b3cfd56cb03a816d6941 Mon Sep 17 00:00:00 2001 From: Luna Saphie Mittelbach Date: Sun, 28 Apr 2024 18:00:48 +0200 Subject: feat(defaults): improve :grep defaults #28545 Based on feedback from #28324, pass -H and -I to regular grep (available on all platforms officially supported by Neovim), and only pass -uu to ripgrep. This makes :grep ignore binary files by default in both cases. --- runtime/lua/vim/_defaults.lua | 5 ++--- runtime/lua/vim/_meta/options.lua | 14 ++++---------- 2 files changed, 6 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index e9575c0e1e..7166f7a23c 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -560,9 +560,8 @@ end do --- Default 'grepprg' to ripgrep if available. if vim.fn.executable('rg') == 1 then - -- Match :grep default, otherwise rg searches cwd by default - -- Use -uuu to make ripgrep not do its default filtering - vim.o.grepprg = 'rg --vimgrep -uuu $* ' .. (vim.fn.has('unix') == 1 and '/dev/null' or 'nul') + -- Use -uu to make ripgrep not check ignore files/skip dot-files + vim.o.grepprg = 'rg --vimgrep -uu ' vim.o.grepformat = '%f:%l:%c:%m' end end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index e10eb305b1..9aa20f59bc 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -2638,12 +2638,6 @@ vim.go.gfm = vim.go.grepformat --- line. The placeholder "$*" is allowed to specify where the arguments --- will be included. Environment variables are expanded `:set_env`. See --- `option-backslash` about including spaces and backslashes. ---- When your "grep" accepts the "-H" argument, use this to make ":grep" ---- also work well with a single file: ---- ---- ```vim ---- set grepprg=grep\ -nH ---- ``` --- Special value: When 'grepprg' is set to "internal" the `:grep` command --- works like `:vimgrep`, `:lgrep` like `:lvimgrep`, `:grepadd` like --- `:vimgrepadd` and `:lgrepadd` like `:lvimgrepadd`. @@ -2652,18 +2646,18 @@ vim.go.gfm = vim.go.grepformat --- This option cannot be set from a `modeline` or in the `sandbox`, for --- security reasons. --- This option defaults to: ---- - `rg --vimgrep -uuu $* ...` if ripgrep is available (`:checkhealth`), ---- - `grep -n $* /dev/null` on Unix, +--- - `rg --vimgrep -uu ` if ripgrep is available (`:checkhealth`), +--- - `grep -HIn $* /dev/null` on Unix, --- - `findstr /n $* nul` on Windows. --- Ripgrep can perform additional filtering such as using .gitignore rules ---- and skipping hidden or binary files. This is disabled by default (see the -u option) +--- and skipping hidden files. This is disabled by default (see the -u option) --- to more closely match the behaviour of standard grep. --- You can make ripgrep match Vim's case handling using the --- -i/--ignore-case and -S/--smart-case options. --- An `OptionSet` autocmd can be used to set it up to match automatically. --- --- @type string -vim.o.grepprg = "grep -n $* /dev/null" +vim.o.grepprg = "grep -HIn $* /dev/null" vim.o.gp = vim.o.grepprg vim.bo.grepprg = vim.o.grepprg vim.bo.gp = vim.bo.grepprg -- cgit From 61063653b06bfccac083c0105ecd0c433b3bb8f7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 28 Apr 2024 09:02:18 -0700 Subject: feat(defaults): visual CTRL-R for LSP mappings #28537 Problem: The new LSP "refactor menu" keybinding "crr" is also defined in visual mode, which overlaps with the builtin "c". Solution: Use CTRL-R instead of "crr" for visual mode. fix #28528 --- runtime/lua/vim/_defaults.lua | 11 ++++++++--- runtime/lua/vim/diagnostic.lua | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 7166f7a23c..1094f22793 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -159,9 +159,14 @@ do vim.lsp.buf.rename() end, { desc = 'vim.lsp.buf.rename()' }) - vim.keymap.set({ 'n', 'v' }, 'crr', function() - vim.lsp.buf.code_action() - end, { desc = 'vim.lsp.buf.code_action()' }) + local function map_codeaction(mode, lhs) + vim.keymap.set(mode, lhs, function() + vim.lsp.buf.code_action() + end, { desc = 'vim.lsp.buf.code_action()' }) + end + map_codeaction('n', 'crr') + map_codeaction('x', 'r') + map_codeaction('x', '') vim.keymap.set('n', 'gr', function() vim.lsp.buf.references() diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 1ed09b51a5..b34541c72e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -153,7 +153,7 @@ local M = {} --- --- @field focus_id? string --- ---- @field border? string see |vim.api.nvim_open_win()|. +--- @field border? string see |nvim_open_win()|. --- @class vim.diagnostic.Opts.Underline --- -- cgit From bc7f86209d3961aa479a8caeb792a8d39de55ece Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Mon, 29 Apr 2024 13:45:53 -0700 Subject: fix(lsp): redundant vim.snippet.jumpable #28560 --- runtime/lua/vim/snippet.lua | 55 ++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 26 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 37416c389f..8447d08d17 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -532,29 +532,6 @@ end --- @alias vim.snippet.Direction -1 | 1 ---- Returns `true` if there is an active snippet which can be jumped in the given direction. ---- You can use this function to navigate a snippet as follows: ---- ---- ```lua ---- vim.keymap.set({ 'i', 's' }, '', function() ---- if vim.snippet.jumpable(1) then ---- return 'lua vim.snippet.jump(1)' ---- else ---- return '' ---- end ---- end, { expr = true }) ---- ``` ---- ---- @param direction (vim.snippet.Direction) Navigation direction. -1 for previous, 1 for next. ---- @return boolean -function M.jumpable(direction) - if not M.active() then - return false - end - - return M._session:get_dest_index(direction) ~= nil -end - --- Jumps within the active snippet in the given direction. --- If the jump isn't possible, the function call does nothing. --- @@ -604,11 +581,37 @@ function M.jump(direction) setup_autocmds(M._session.bufnr) end ---- Returns `true` if there's an active snippet in the current buffer. +--- @class vim.snippet.ActiveFilter +--- @field direction vim.snippet.Direction Navigation direction. -1 for previous, 1 for next. + +--- Returns `true` if there's an active snippet in the current buffer, +--- applying the given filter if provided. +--- +--- You can use this function to navigate a snippet as follows: --- +--- ```lua +--- vim.keymap.set({ 'i', 's' }, '', function() +--- if vim.snippet.active({ direction = 1 }) then +--- return 'lua vim.snippet.jump(1)' +--- else +--- return '' +--- end +--- end, { expr = true }) +--- ``` +--- +--- @param filter? vim.snippet.ActiveFilter Filter to constrain the search with: +--- - `direction` (vim.snippet.Direction): Navigation direction. Will return `true` if the snippet +--- can be jumped in the given direction. --- @return boolean -function M.active() - return M._session ~= nil and M._session.bufnr == vim.api.nvim_get_current_buf() +function M.active(filter) + local active = M._session ~= nil and M._session.bufnr == vim.api.nvim_get_current_buf() + + local in_direction = true + if active and filter and filter.direction then + in_direction = M._session:get_dest_index(filter.direction) ~= nil + end + + return active and in_direction end --- Exits the current snippet. -- cgit From 234b5f67019b435b604308a96c366b1187c2cc3a Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 30 Apr 2024 01:04:42 +0200 Subject: docs: various fixes (#28208) Co-authored-by: Evgeni Chasnovski Co-authored-by: Famiu Haque Co-authored-by: Gregory Anders Co-authored-by: Guilherme Soares Co-authored-by: Jannik Buhr Co-authored-by: thomaswuhoileong <72001875+thomaswuhoileong@users.noreply.github.com> Co-authored-by: tom-anders <13141438+tom-anders@users.noreply.github.com> Co-authored-by: zeertzjq --- runtime/lua/vim/_comment.lua | 2 +- runtime/lua/vim/_meta/options.lua | 2 +- runtime/lua/vim/_meta/vimfn.lua | 2 +- runtime/lua/vim/_meta/vvars.lua | 7 ++++--- runtime/lua/vim/termcap.lua | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index e9cd662c9d..b6cb6c9884 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -80,7 +80,7 @@ local function make_comment_check(parts) -- local nonblank_regex = '^%s-' .. l_esc .. '.*' .. r_esc .. '%s-$' - -- Commented blank line can have any amoung of whitespace around parts + -- Commented blank line can have any amount of whitespace around parts local blank_regex = '^%s-' .. vim.trim(l_esc) .. '%s*' .. vim.trim(r_esc) .. '%s-$' return function(line) diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 9aa20f59bc..b51f82401b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -974,7 +974,7 @@ vim.bo.comments = vim.o.comments vim.bo.com = vim.bo.comments --- A template for a comment. The "%s" in the value is replaced with the ---- comment text. For example, C uses "/*%s*/". Currently only used to +--- comment text. For example, C uses "/*%s*/". Used for `commenting` and to --- add markers for folding, see `fold-marker`. --- --- @type string diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 02d5eaf575..74b8590924 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -6583,7 +6583,7 @@ function vim.fn.prevnonblank(lnum) end --- --- @param fmt any --- @param expr1? any ---- @return any +--- @return string function vim.fn.printf(fmt, expr1) end --- Returns the effective prompt text for buffer {buf}. {buf} can diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index ee6d8ddf35..1660d1dd6c 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -54,9 +54,10 @@ vim.v.cmdbang = ... --- @type string vim.v.collate = ... ---- Dictionary containing the most recent `complete-items` after ---- `CompleteDone`. Empty if the completion failed, or after ---- leaving and re-entering insert mode. +--- Dictionary containing the `complete-items` for the most +--- recently completed word after `CompleteDone`. Empty if the +--- completion failed, or after leaving and re-entering insert +--- mode. --- Note: Plugins can modify the value to emulate the builtin --- `CompleteDone` event behavior. --- @type any diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua index 6152f50730..1da2e71839 100644 --- a/runtime/lua/vim/termcap.lua +++ b/runtime/lua/vim/termcap.lua @@ -12,7 +12,7 @@ local M = {} --- emulator supports the XTGETTCAP sequence. --- --- @param caps string|table A terminal capability or list of capabilities to query ---- @param cb fun(cap:string, found:bool, seq:string?) Callback function which is called for +--- @param cb fun(cap:string, found:boolean, seq:string?) Callback function which is called for --- each capability in {caps}. {found} is set to true if the capability was found or false --- otherwise. {seq} is the control sequence for the capability if found, or nil for --- boolean capabilities. -- cgit From 71cf75f96a67aeb79ac3af6aa829bac81bd2d33d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 04:30:21 -0700 Subject: docs: misc #24163 - Also delete old perl scripts which are not used since 8+ years ago. fix #23251 fix #27367 ref https://github.com/neovim/neovim/issues/2252#issuecomment-1902662577 Helped-by: Daniel Kongsgaard Co-authored-by: Kevin Pham --- runtime/lua/vim/_defaults.lua | 2 +- runtime/lua/vim/_meta/api.lua | 6 +++--- runtime/lua/vim/_meta/options.lua | 1 + runtime/lua/vim/highlight.lua | 31 +++++++------------------------ runtime/lua/vim/iter.lua | 22 +++++++++++++--------- 5 files changed, 25 insertions(+), 37 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 1094f22793..29f8e71264 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -255,7 +255,7 @@ do vim.api.nvim_create_autocmd('TermRequest', { group = nvim_terminal_augroup, - desc = 'Respond to OSC foreground/background color requests', + desc = 'Handles OSC foreground/background color requests', callback = function(args) --- @type integer local channel = vim.bo[args.buf].channel diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index f56c256da6..7d20d90990 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -897,8 +897,8 @@ function vim.api.nvim_create_augroup(name, opts) end --- • callback (function|string) optional: Lua function (or --- Vimscript function name, if string) called when the event(s) --- is triggered. Lua callback can return a truthy value (not ---- `false` or `nil`) to delete the autocommand. Receives a ---- table argument with these keys: +--- `false` or `nil`) to delete the autocommand. Receives one +--- argument, a table with these keys: *event-args* --- • id: (number) autocommand id --- • event: (string) name of the triggered event --- `autocmd-events` @@ -907,7 +907,7 @@ function vim.api.nvim_create_augroup(name, opts) end --- • buf: (number) expanded value of --- • file: (string) expanded value of --- • data: (any) arbitrary data passed from ---- `nvim_exec_autocmds()` +--- `nvim_exec_autocmds()` *event-data* --- • command (string) optional: Vim command to execute on event. --- Cannot be used with {callback} --- • once (boolean) optional: defaults to false. Run the diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index b51f82401b..428b7c4d4f 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -7446,6 +7446,7 @@ vim.bo.vts = vim.bo.vartabstop --- --- Level Messages ~ --- ---------------------------------------------------------------------- +--- 1 Enables Lua tracing (see above). Does not produce messages. --- 2 When a file is ":source"'ed, or `shada` file is read or written. --- 3 UI info, terminal capabilities. --- 4 Shell commands. diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 09d3ea5707..cdc0f0a0b1 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -1,26 +1,3 @@ ----@brief ---- ---- Nvim includes a function for highlighting a selection on yank. ---- ---- To enable it, add the following to your `init.vim`: ---- ---- ```vim ---- au TextYankPost * silent! lua vim.highlight.on_yank() ---- ``` ---- ---- You can customize the highlight group and the duration of the highlight via: ---- ---- ```vim ---- au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} ---- ``` ---- ---- If you want to exclude visual selections from highlighting on yank, use: ---- ---- ```vim ---- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false} ---- ``` ---- - local api = vim.api local M = {} @@ -97,7 +74,13 @@ local yank_ns = api.nvim_create_namespace('hlyank') local yank_timer --- @type uv.uv_timer_t? local yank_cancel --- @type fun()? ---- Highlight the yanked text +--- Highlight the yanked text during a |TextYankPost| event. +--- +--- Add the following to your `init.vim`: +--- +--- ```vim +--- autocmd TextYankPost * silent! lua vim.highlight.on_yank {higroup='Visual', timeout=300} +--- ``` --- --- @param opts table|nil Optional parameters --- - higroup highlight group for yanked region (default "IncSearch") diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 04137e18d4..f887a9070b 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -450,20 +450,24 @@ function Iter:join(delim) return table.concat(self:totable(), delim) end ---- Folds ("reduces") an iterator into a single value. +--- Folds ("reduces") an iterator into a single value. [Iter:reduce()]() --- --- Examples: --- --- ```lua --- -- Create a new table with only even values ---- local t = { a = 1, b = 2, c = 3, d = 4 } ---- local it = vim.iter(t) ---- it:filter(function(k, v) return v % 2 == 0 end) ---- it:fold({}, function(t, k, v) ---- t[k] = v ---- return t ---- end) ---- -- { b = 2, d = 4 } +--- vim.iter({ a = 1, b = 2, c = 3, d = 4 }) +--- :filter(function(k, v) return v % 2 == 0 end) +--- :fold({}, function(acc, k, v) +--- acc[k] = v +--- return acc +--- end) --> { b = 2, d = 4 } +--- +--- -- Get the "maximum" item of an iterable. +--- vim.iter({ -99, -4, 3, 42, 0, 0, 7 }) +--- :fold({}, function(acc, v) +--- acc.max = math.max(v, acc.max or v) return acc +--- end) --> { max = 42 } --- ``` --- ---@generic A -- cgit From 0330dd9e69de7567fd2479c0203b778a1d2dce2f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 05:12:51 -0700 Subject: fix(api): mark nvim__complete_set as experimental #28579 Problem: nvim_complete_set was added in 5ed55ff14c8b7e346811cb6228bf63fb5106bae9 but needs more bake time. Solution: Rename it, mark it as experimental. --- runtime/lua/vim/_meta/api.lua | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 7d20d90990..58fa9af8cd 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -23,6 +23,19 @@ function vim.api.nvim__buf_redraw_range(buffer, first, last) end --- @return table function vim.api.nvim__buf_stats(buffer) end +--- @private +--- EXPERIMENTAL: this api may change in the future. +--- +--- Sets info for the completion item at the given index. If the info text was +--- shown in a window, returns the window and buffer ids, or empty dict if not +--- shown. +--- +--- @param index integer Completion candidate index +--- @param opts vim.api.keyset.complete_set Optional parameters. +--- • info: (string) info text. +--- @return table +function vim.api.nvim__complete_set(index, opts) end + --- @private --- @return string function vim.api.nvim__get_lib_dir() end @@ -822,16 +835,6 @@ function vim.api.nvim_command(command) end --- @return string function vim.api.nvim_command_output(command) end ---- Set info for the completion candidate index. if the info was shown in a ---- window, then the window and buffer ids are returned for further ---- customization. If the text was not shown, an empty dict is returned. ---- ---- @param index integer the completion candidate index ---- @param opts vim.api.keyset.complete_set Optional parameters. ---- • info: (string) info text. ---- @return table -function vim.api.nvim_complete_set(index, opts) end - --- Create or get an autocommand group `autocmd-groups`. --- --- To get an existing group id, do: -- cgit From ee41153a945876ad0c7f0927ffa7b5a5afdaca89 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 30 Apr 2024 09:57:31 +0100 Subject: feat(diagnostic): revert default behaviour of goto_next/prev() Follow-up to #28490 Problem: The new behaviour of goto_next/prev() of navigating to the next highest severity doesn't work well when diagnostic providers have different interpretations of severities. E.g. the user may be blocked from navigating to a useful LSP warning, due to some linter error. Solution: The behaviour of next highest severity is now a hidden option `_highest = true`. We can revisit how to integrate this behaviour during the 0.11 cycle. --- runtime/lua/vim/diagnostic.lua | 59 +++++++++++++++++++++++++----------------- 1 file changed, 35 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index b34541c72e..67ce331891 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -76,7 +76,7 @@ local M = {} --- before lower severities (e.g. ERROR is displayed before WARN). --- Options: --- - {reverse}? (boolean) Reverse sort order ---- (default: `false) +--- (default: `false`) --- @field severity_sort? boolean|{reverse?:boolean} --- @class (private) vim.diagnostic.OptsResolved @@ -812,6 +812,29 @@ local function set_list(loclist, opts) end end +--- Jump to the diagnostic with the highest severity. First sort the +--- diagnostics by severity. The first diagnostic then contains the highest severity, and we can +--- discard all diagnostics with a lower severity. +--- @param diagnostics vim.Diagnostic[] +local function filter_highest(diagnostics) + table.sort(diagnostics, function(a, b) + return a.severity < b.severity + end) + + -- Find the first diagnostic where the severity does not match the highest severity, and remove + -- that element and all subsequent elements from the array + local worst = (diagnostics[1] or {}).severity + local len = #diagnostics + for i = 2, len do + if diagnostics[i].severity ~= worst then + for j = i, len do + diagnostics[j] = nil + end + break + end + end +end + --- @param position {[1]: integer, [2]: integer} --- @param search_forward boolean --- @param bufnr integer @@ -823,29 +846,13 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) bufnr = get_bufnr(bufnr) local wrap = if_nil(opts.wrap, true) - local diagnostics = - get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) + local get_opts = vim.deepcopy(opts) + get_opts.namespace = get_opts.namespace or namespace - -- When severity is unset we jump to the diagnostic with the highest severity. First sort the - -- diagnostics by severity. The first diagnostic then contains the highest severity, and we can - -- discard all diagnostics with a lower severity. - if opts.severity == nil then - table.sort(diagnostics, function(a, b) - return a.severity < b.severity - end) + local diagnostics = get_diagnostics(bufnr, get_opts, true) - -- Find the first diagnostic where the severity does not match the highest severity, and remove - -- that element and all subsequent elements from the array - local worst = (diagnostics[1] or {}).severity - local len = #diagnostics - for i = 2, len do - if diagnostics[i].severity ~= worst then - for j = i, len do - diagnostics[j] = nil - end - break - end - end + if opts._highest then + filter_highest(diagnostics) end local line_diagnostics = diagnostic_lines(diagnostics) @@ -1191,8 +1198,12 @@ end --- (default: `true`) --- @field wrap? boolean --- ---- See |diagnostic-severity|. If `nil`, go to the diagnostic with the highest severity. ---- @field severity? vim.diagnostic.Severity +--- See |diagnostic-severity|. +--- @field severity? vim.diagnostic.SeverityFilter +--- +--- Go to the diagnostic with the highest severity. +--- (default: `false`) +--- @field package _highest? boolean --- --- If `true`, call |vim.diagnostic.open_float()| after moving. --- If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|. -- cgit From dafa51c16d9bdaec5011b591b0ce8dff287624b7 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 30 Apr 2024 06:06:14 -0700 Subject: docs(api): sort unreleased nvim__ functions last #28580 --- runtime/lua/vim/_meta/api.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 58fa9af8cd..d799446647 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -24,7 +24,7 @@ function vim.api.nvim__buf_redraw_range(buffer, first, last) end function vim.api.nvim__buf_stats(buffer) end --- @private ---- EXPERIMENTAL: this api may change in the future. +--- EXPERIMENTAL: this API may change in the future. --- --- Sets info for the completion item at the given index. If the info text was --- shown in a window, returns the window and buffer ids, or empty dict if not -- cgit From e7ae9139534fd106481a6cb4787041dc5b966533 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 1 May 2024 10:13:52 +0200 Subject: vim-patch:9.1.0382: filetype: Kbuild files are not recognized Problem: Kbuild files are not recognized. Solution: Detect Kbuild files as make files. (Bruno Belanyi) closes: vim/vim#14676 https://github.com/vim/vim/commit/5cbc9a69e529361e1725f422b8cd6157fe0adc33 Co-authored-by: Bruno BELANYI --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 7667664edb..f9c702eeae 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1480,6 +1480,7 @@ local filename = { ['/etc/mail/aliases'] = 'mailaliases', mailcap = 'mailcap', ['.mailcap'] = 'mailcap', + Kbuild = 'make', ['/etc/man.conf'] = 'manconf', ['man.config'] = 'manconf', ['maxima-init.mac'] = 'maxima', -- cgit From b5583acc482b125399e9fa6c2454a6db6b1ae3e4 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 1 May 2024 10:16:09 +0200 Subject: vim-patch:9.1.0383: filetype: .out files recognized as tex files Problem: filetype: .out files recognized as tex files Solution: Do not set an explicit filetype until it is clear what this should be (shane.xb.qian) closes: vim/vim#14670 https://github.com/vim/vim/commit/e35478bc9d48189322432248105d3b24e0efb3d0 Co-authored-by: shane.xb.qian --- runtime/lua/vim/filetype.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index f9c702eeae..3cb634256b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1047,7 +1047,6 @@ local extension = { pgf = 'tex', nlo = 'tex', nls = 'tex', - out = 'tex', thm = 'tex', eps_tex = 'tex', pygtex = 'tex', -- cgit From 0b8a72b73934d33a05e20c255298e88cd921df32 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 1 May 2024 08:08:22 -0500 Subject: revert: "feat(extmarks): subpriorities (relative to declaration order) (#27131)" (#28585) This reverts commit 15e77a56b711102fdc123e15b3f37d49bc0b1df1. Subpriorities were added in https://github.com/neovim/neovim/pull/27131 as a mechanism for enforcing query order when using iter_matches in the Tree-sitter highlighter. However, iter_matches proved to have too many complications to use in the highlighter so we eventually reverted back to using iter_captures (https://github.com/neovim/neovim/pull/27901). Thus, subpriorities are no longer needed and can be removed. --- runtime/lua/vim/_meta/api_keysets.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 37e4372196..9f9ade3e76 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -253,7 +253,6 @@ error('Cannot require a meta file') --- @field undo_restore? boolean --- @field url? string --- @field scoped? boolean ---- @field _subpriority? integer --- @class vim.api.keyset.user_command --- @field addr? any -- cgit From 9e2f378b6d255cd4b02a39b1a1dc5aea2df1a84c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 1 May 2024 18:46:10 +0200 Subject: vim-patch:9.1.0386: filetype: stylus files not recognized Problem: filetype: stylus files not recognized Solution: Detect '*.styl' and '*.stylus' as stylus filetype, include indent, filetype and syntax plugin (Philip H) closes: vim/vim#14656 https://github.com/vim/vim/commit/2d919d2744a99c9bb9e79984e85b8e8f5ec14c07 Co-authored-by: Philip H <47042125+pheiduck@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 3cb634256b..77b3d29d01 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1015,6 +1015,8 @@ local extension = { mata = 'stata', ado = 'stata', stp = 'stp', + styl = 'stylus', + stylus = 'stylus', quark = 'supercollider', sface = 'surface', svelte = 'svelte', -- cgit From 54dfee8f0a68985a196857821af1add287b5991a Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 2 May 2024 11:25:21 +0300 Subject: docs: add `hl-SnippetTabstop` tag --- 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 8447d08d17..18c613cb85 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -401,7 +401,7 @@ end --- Refer to https://microsoft.github.io/language-server-protocol/specification/#snippet_syntax --- for the specification of valid input. --- ---- Tabstops are highlighted with hl-SnippetTabstop. +--- Tabstops are highlighted with |hl-SnippetTabstop|. --- --- @param input string function M.expand(input) -- cgit From ebf8237af8af794654c045f836e6caa479053c7b Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 2 May 2024 13:54:14 +0200 Subject: vim-patch:9.1.0389: filetype: templ files are not recognized Problem: filetype: templ files are not recognized Solution: Detect '*.templ' files as filetype templ (Tristan Knight) See: - https://github.com/a-h/templ - https://templ.guide/ closes: vim/vim#14697 https://github.com/vim/vim/commit/54e79157c536c631b2f9b3dfefec30b9b966ed97 Co-authored-by: tris203 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 77b3d29d01..e8d88ca309 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1039,6 +1039,7 @@ local extension = { tk = 'tcl', jacl = 'tcl', tl = 'teal', + templ = 'templ', tmpl = 'template', ti = 'terminfo', dtx = 'tex', -- cgit From 2becec289c77a359087ab6322276811aea9e87c8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 2 May 2024 13:55:44 +0200 Subject: vim-patch:9.1.0390: filetype: inko files are not recognized Problem: filetype: inko files are not recognized Solution: Detect '*.inko' as ink filetype (Yorick Peterse) See: - https://github.com/inko-lang/inko.vim - https://inko-lang.org/ closes: vim/vim#14699 https://github.com/vim/vim/commit/a01968448a0bdf04d9e4a822d32732a304849238 Co-authored-by: Yorick Peterse --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e8d88ca309..3b7ea03c2d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -545,6 +545,7 @@ local extension = { inf = 'inform', INF = 'inform', ii = 'initng', + inko = 'inko', inp = detect.inp, ms = detect_seq(detect.nroff, 'xmath'), iss = 'iss', -- cgit From d5063f4b290e1c4262f7ced6d425ff2d7a2e2045 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Thu, 2 May 2024 21:16:20 +0800 Subject: feat(lsp): vim.lsp.inlay_hint.enable(nil) applies to all buffers #28543 Problem: Inlay hints `enable()` does not fully implement the `:help dev-lua` guidelines: Interface conventions ~ - When accepting a buffer id, etc., 0 means "current buffer", nil means "all buffers". Likewise for window id, tabpage id, etc. - Examples: |vim.lsp.codelens.clear()| |vim.diagnostic.enable()| Solution: Implement globally enabling inlay hints. * refactor(lsp): do not rely on `enable` to create autocmds * refactor(lsp): make `bufstates` a defaulttable * refactor(lsp): make `bufstate` inherit values from `globalstate` * feat(lsp): `vim.lsp.inlay_hints` now take effect on all buffers by default * test(lsp): add basic tests for enable inlay hints for all buffers * test(lsp): add test cases cover more than one buffer --- runtime/lua/vim/lsp/inlay_hint.lua | 160 ++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 66 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 3d8b54ee3d..985cbef5ff 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -4,13 +4,26 @@ local ms = require('vim.lsp.protocol').Methods local api = vim.api local M = {} ----@class (private) vim.lsp.inlay_hint.bufstate +---@class (private) vim.lsp.inlay_hint.globalstate Global state for inlay hints +---@field enabled boolean Whether inlay hints are enabled for this scope +---@type vim.lsp.inlay_hint.globalstate +local globalstate = { + enabled = false, +} + +---@class (private) vim.lsp.inlay_hint.bufstate: vim.lsp.inlay_hint.globalstate Buffer local state for inlay hints ---@field version? integer ---@field client_hints? table> client_id -> (lnum -> hints) ---@field applied table Last version of hints applied to this line ----@field enabled boolean Whether inlay hints are enabled for this buffer ---@type table -local bufstates = {} +local bufstates = vim.defaulttable(function(_) + return setmetatable({ applied = {} }, { + __index = globalstate, + __newindex = function(state, key, value) + rawset(state, key, (globalstate[key] ~= value) and value or nil) + end, + }) +end) local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) @@ -34,7 +47,7 @@ function M.on_inlayhint(err, result, ctx, _) return end local bufstate = bufstates[bufnr] - if not bufstate or not bufstate.enabled then + if not bufstate.enabled then return end if not (bufstate.client_hints and bufstate.version) then @@ -91,11 +104,7 @@ function M.on_refresh(err, _, ctx, _) for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do for _, winid in ipairs(api.nvim_list_wins()) do if api.nvim_win_get_buf(winid) == bufnr then - local bufstate = bufstates[bufnr] - if bufstate then - util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) - break - end + util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr }) end end end @@ -154,7 +163,7 @@ function M.get(filter) end local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.client_hints) then + if not bufstate.client_hints then return {} end @@ -203,12 +212,9 @@ end --- Clear inlay hints ---@param bufnr (integer) Buffer handle, or 0 for current local function clear(bufnr) - if bufnr == nil or bufnr == 0 then + if bufnr == 0 then bufnr = api.nvim_get_current_buf() end - if not bufstates[bufnr] then - return - end local bufstate = bufstates[bufnr] local client_lens = (bufstate or {}).client_hints or {} local client_ids = vim.tbl_keys(client_lens) --- @type integer[] @@ -222,15 +228,14 @@ local function clear(bufnr) end --- Disable inlay hints for a buffer ----@param bufnr (integer|nil) Buffer handle, or 0 or nil for current +---@param bufnr (integer) Buffer handle, or 0 for current local function _disable(bufnr) - if bufnr == nil or bufnr == 0 then + if bufnr == 0 then bufnr = api.nvim_get_current_buf() end clear(bufnr) - if bufstates[bufnr] then - bufstates[bufnr] = { enabled = false, applied = {} } - end + bufstates[bufnr] = nil + bufstates[bufnr].enabled = false end --- Refresh inlay hints, only if we have attached clients that support it @@ -244,30 +249,38 @@ local function _refresh(bufnr, opts) end --- Enable inlay hints for a buffer ----@param bufnr (integer|nil) Buffer handle, or 0 or nil for current +---@param bufnr (integer) Buffer handle, or 0 for current local function _enable(bufnr) - if bufnr == nil or bufnr == 0 then + if bufnr == 0 then bufnr = api.nvim_get_current_buf() end - local bufstate = bufstates[bufnr] - if not bufstate then - bufstates[bufnr] = { applied = {}, enabled = true } - api.nvim_create_autocmd('LspNotify', { - buffer = bufnr, - callback = function(opts) - if - opts.data.method ~= ms.textDocument_didChange - and opts.data.method ~= ms.textDocument_didOpen - then - return - end - if bufstates[bufnr] and bufstates[bufnr].enabled then - _refresh(bufnr, { client_id = opts.data.client_id }) - end - end, - group = augroup, - }) - _refresh(bufnr) + bufstates[bufnr] = nil + bufstates[bufnr].enabled = true + _refresh(bufnr) +end + +api.nvim_create_autocmd('LspNotify', { + callback = function(args) + ---@type integer + local bufnr = args.buf + + if + args.data.method ~= ms.textDocument_didChange + and args.data.method ~= ms.textDocument_didOpen + then + return + end + if bufstates[bufnr].enabled then + _refresh(bufnr, { client_id = args.data.client_id }) + end + end, + group = augroup, +}) +api.nvim_create_autocmd('LspAttach', { + callback = function(args) + ---@type integer + local bufnr = args.buf + api.nvim_buf_attach(bufnr, false, { on_reload = function(_, cb_bufnr) clear(cb_bufnr) @@ -278,32 +291,30 @@ local function _enable(bufnr) end, on_detach = function(_, cb_bufnr) _disable(cb_bufnr) + bufstates[cb_bufnr] = nil end, }) - api.nvim_create_autocmd('LspDetach', { - buffer = bufnr, - callback = function(args) - local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_inlayHint }) - - if - not vim.iter(clients):any(function(c) - return c.id ~= args.data.client_id - end) - then - _disable(bufnr) - end - end, - group = augroup, - }) - else - bufstate.enabled = true - _refresh(bufnr) - end -end + end, + group = augroup, +}) +api.nvim_create_autocmd('LspDetach', { + callback = function(args) + ---@type integer + local bufnr = args.buf + local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_inlayHint }) + if not vim.iter(clients):any(function(c) + return c.id ~= args.data.client_id + end) then + _disable(bufnr) + end + end, + group = augroup, +}) api.nvim_set_decoration_provider(namespace, { on_win = function(_, _, bufnr, topline, botline) - local bufstate = bufstates[bufnr] + ---@type vim.lsp.inlay_hint.bufstate + local bufstate = rawget(bufstates, bufnr) if not bufstate then return end @@ -361,13 +372,13 @@ function M.is_enabled(bufnr) if bufnr == nil or bufnr == 0 then bufnr = api.nvim_get_current_buf() end - return bufstates[bufnr] and bufstates[bufnr].enabled or false + return bufstates[bufnr].enabled end --- Optional filters |kwargs|, or `nil` for all. --- @class vim.lsp.inlay_hint.enable.Filter --- @inlinedoc ---- Buffer number, or 0/nil for current buffer. +--- Buffer number, or 0 for current buffer, or nil for all. --- @field bufnr integer? --- Enables or disables inlay hints for a buffer. @@ -392,11 +403,28 @@ function M.enable(enable, filter) end vim.validate({ enable = { enable, 'boolean', true }, filter = { filter, 'table', true } }) + enable = enable == nil or enable filter = filter or {} - if enable == false then - _disable(filter.bufnr) + + if filter.bufnr == nil then + globalstate.enabled = enable + for bufnr, _ in pairs(bufstates) do + if api.nvim_buf_is_loaded(bufnr) then + if enable == false then + _disable(bufnr) + else + _enable(bufnr) + end + else + bufstates[bufnr] = nil + end + end else - _enable(filter.bufnr) + if enable == false then + _disable(filter.bufnr) + else + _enable(filter.bufnr) + end end end -- cgit From 037ea6e786b5d05f4a8965e4c2ba6aa60ec7c01a Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Wed, 10 Apr 2024 11:42:46 +0200 Subject: feat(api): add nvim__redraw for more granular redrawing Experimental and subject to future changes. Add a way to redraw certain elements that are not redrawn while Nvim is waiting for input, or currently have no API to do so. This API covers all that can be done with the :redraw* commands, in addition to the following new features: - Immediately move the cursor to a (non-current) window. - Target a specific window or buffer to mark for redraw. - Mark a buffer range for redraw (replaces nvim__buf_redraw_range()). - Redraw the 'statuscolumn'. --- runtime/lua/vim/_meta/api.lua | 32 ++++++++++++++++++++++++------ runtime/lua/vim/_meta/api_keysets.lua | 12 +++++++++++ runtime/lua/vim/lsp/inlay_hint.lua | 6 +++--- runtime/lua/vim/lsp/semantic_tokens.lua | 2 +- runtime/lua/vim/treesitter/highlighter.lua | 4 ++-- 5 files changed, 44 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index d799446647..64c67be076 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -12,12 +12,6 @@ vim.api = {} --- @return string function vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) end ---- @private ---- @param buffer integer ---- @param first integer ---- @param last integer -function vim.api.nvim__buf_redraw_range(buffer, first, last) end - --- @private --- @param buffer integer --- @return table @@ -105,6 +99,32 @@ function vim.api.nvim__inspect_cell(grid, row, col) end --- function vim.api.nvim__invalidate_glyph_cache() end +--- @private +--- EXPERIMENTAL: this API may change in the future. +--- +--- Instruct Nvim to redraw various components. +--- +--- @param opts vim.api.keyset.redraw Optional parameters. +--- • win: Target a specific `window-ID` as described below. +--- • buf: Target a specific buffer number as described below. +--- • flush: Update the screen with pending updates. +--- • valid: When present mark `win`, `buf`, or all windows for +--- redraw. When `true`, only redraw changed lines (useful for +--- decoration providers). When `false`, forcefully redraw. +--- • range: Redraw a range in `buf`, the buffer in `win` or the +--- current buffer (useful for decoration providers). Expects a +--- tuple `[first, last]` with the first and last line number of +--- the range, 0-based end-exclusive `api-indexing`. +--- • cursor: Immediately update cursor position on the screen in +--- `win` or the current window. +--- • statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or +--- all windows. +--- • statusline: Redraw the 'statusline' in `buf`, `win` or all +--- windows. +--- • winbar: Redraw the 'winbar' in `buf`, `win` or all windows. +--- • tabline: Redraw the 'tabline'. +function vim.api.nvim__redraw(opts) end + --- @private --- @return any[] function vim.api.nvim__runtime_inspect() end diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua index 9f9ade3e76..f7cd92a3b2 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -207,6 +207,18 @@ error('Cannot require a meta file') --- @field buf? integer --- @field filetype? string +--- @class vim.api.keyset.redraw +--- @field flush? boolean +--- @field cursor? boolean +--- @field valid? boolean +--- @field statuscolumn? boolean +--- @field statusline? boolean +--- @field tabline? boolean +--- @field winbar? boolean +--- @field range? any[] +--- @field win? integer +--- @field buf? integer + --- @class vim.api.keyset.runtime --- @field is_lua? boolean --- @field do_source? boolean diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 985cbef5ff..c9e485f474 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -62,7 +62,7 @@ function M.on_inlayhint(err, result, ctx, _) if num_unprocessed == 0 then client_hints[client_id] = {} bufstate.version = ctx.version - api.nvim__buf_redraw_range(bufnr, 0, -1) + api.nvim__redraw({ buf = bufnr, valid = true }) return end @@ -91,7 +91,7 @@ function M.on_inlayhint(err, result, ctx, _) client_hints[client_id] = new_lnum_hints bufstate.version = ctx.version - api.nvim__buf_redraw_range(bufnr, 0, -1) + api.nvim__redraw({ buf = bufnr, valid = true }) end --- |lsp-handler| for the method `textDocument/inlayHint/refresh` @@ -224,7 +224,7 @@ local function clear(bufnr) end end api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) - api.nvim__buf_redraw_range(bufnr, 0, -1) + api.nvim__redraw({ buf = bufnr, valid = true }) end --- Disable inlay hints for a buffer diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 20ac0a125f..be2d6ee0ae 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -394,7 +394,7 @@ function STHighlighter:process_response(response, client, version) current_result.namespace_cleared = false -- redraw all windows displaying buffer - api.nvim__buf_redraw_range(self.bufnr, 0, -1) + api.nvim__redraw({ buf = self.bufnr, valid = true }) end --- on_win handler for the decoration provider (see |nvim_set_decoration_provider|) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 3f7e31212c..d2f986b874 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -215,7 +215,7 @@ end ---@param start_row integer ---@param new_end integer function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) - api.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) + api.nvim__redraw({ buf = self.bufnr, range = { start_row, start_row + new_end + 1 } }) end ---@package @@ -227,7 +227,7 @@ end ---@param changes Range6[] function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes) do - api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1) + api.nvim__redraw({ buf = self.bufnr, range = { ch[1], ch[4] + 1 } }) end end -- cgit From 350d81856473b45100d6b0e5920b757df1b4ad27 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Thu, 2 May 2024 22:26:07 +0800 Subject: feat(lsp): inlay_hint.is_enabled({filter}) #28523 vim.diagnostic.enable and vim.diagnostic.is_enabled() use the same pattern. --- runtime/lua/vim/lsp/inlay_hint.lua | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 985cbef5ff..dc0b6d7037 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -364,12 +364,29 @@ api.nvim_set_decoration_provider(namespace, { end, }) ---- @param bufnr (integer|nil) Buffer handle, or 0 for current +--- @param filter vim.lsp.inlay_hint.enable.Filter --- @return boolean --- @since 12 -function M.is_enabled(bufnr) +function M.is_enabled(filter) + ---@type integer + local bufnr + if type(filter) == 'number' then + vim.deprecate( + 'vim.lsp.inlay_hint.is_enabled(bufnr:number)', + 'vim.lsp.inlay_hint.is_enabled(filter:table)', + '0.10-dev' + ) + bufnr = filter + else + vim.validate({ filter = { filter, 'table', true } }) + filter = filter or {} + bufnr = filter.bufnr + end + vim.validate({ bufnr = { bufnr, 'number', true } }) - if bufnr == nil or bufnr == 0 then + if bufnr == nil then + return globalstate.enabled + elseif bufnr == 0 then bufnr = api.nvim_get_current_buf() end return bufstates[bufnr].enabled -- cgit From d44ed3a885e163df33cce8180ca9f72fb5c0661a Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 3 May 2024 18:02:25 +0800 Subject: perf(extmarks): better track whether namespace has extmarks (#28615) This avoids redraw when adding/removing an empty namespace for a window. This also avoids marktree traversal when clearing a namespace that has already been cleared, which is added as a benchmark. --- runtime/lua/vim/highlight.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index cdc0f0a0b1..781fabe5fb 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -129,13 +129,13 @@ function M.on_yank(opts) yank_cancel() end + vim.api.nvim_win_add_ns(winid, yank_ns) M.range(bufnr, yank_ns, higroup, "'[", "']", { regtype = event.regtype, inclusive = event.inclusive, priority = opts.priority or M.priorities.user, _scoped = true, }) - vim.api.nvim_win_add_ns(winid, yank_ns) yank_cancel = function() yank_timer = nil -- cgit From 40ce8577977fcdce8ad76863c70eb522e4cefd4d Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 3 May 2024 03:20:03 -0700 Subject: fix(vim.ui)!: change open() to return `result|nil, errmsg|nil` #28612 reverts e0d92b9cc20b58179599f53dfa74ca821935a539 #28502 Problem: `vim.ui.open()` has a `pcall()` like signature, under the assumption that this is the Lua idiom for returning result-or-error. However, the `result|nil, errmsg|nil` pattern: - has precedent in: - `io.open` - `vim.uv` (`:help luv-error-handling`) - has these advantages: - Can be used with `assert()`: ``` local result, err = assert(foobar()) ``` - Allows LuaLS to infer the type of `result`: ``` local result, err = foobar() if err then ... elseif result then ... end ``` Solution: - Revert to the `result|nil, errmsg|nil` pattern. - Document the pattern in our guidelines. --- runtime/lua/vim/_defaults.lua | 15 +++++++-------- runtime/lua/vim/lsp/handlers.lua | 6 +++--- runtime/lua/vim/ui.lua | 14 +++++++------- 3 files changed, 17 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 29f8e71264..6d31a3ea93 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -98,19 +98,18 @@ do --- Map |gx| to call |vim.ui.open| on the at cursor. do local function do_open(uri) - local ok, cmd_or_err = vim.ui.open(uri) - local rv = ok and (cmd_or_err --[[@as vim.SystemObj]]):wait(1000) or nil - if rv and rv.code ~= 0 then - ok = false - cmd_or_err = ('vim.ui.open: command %s (%d): %s'):format( + local cmd, err = vim.ui.open(uri) + local rv = cmd and cmd:wait(1000) or nil + if cmd and rv and rv.code ~= 0 then + err = ('vim.ui.open: command %s (%d): %s'):format( (rv.code == 124 and 'timeout' or 'failed'), rv.code, - vim.inspect(cmd_or_err.cmd) + vim.inspect(cmd.cmd) ) end - if not ok then - vim.notify(cmd_or_err --[[@as string]], vim.log.levels.ERROR) + if err then + vim.notify(err, vim.log.levels.ERROR) end end diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index ab4fa52c40..4672d94105 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -654,15 +654,15 @@ M[ms.window_showDocument] = function(_, result, ctx, _) if result.external then -- TODO(lvimuser): ask the user for confirmation - local ok, cmd_or_err = vim.ui.open(uri) - local ret = ok and (cmd_or_err --[[@as vim.SystemObj]]):wait(2000) or nil + local cmd, err = vim.ui.open(uri) + local ret = cmd and cmd:wait(2000) or nil if ret == nil or ret.code ~= 0 then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret and ret.stderr or cmd_or_err, + message = ret and ret.stderr or err, }, } end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index b8323efa66..3c947c51b0 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -118,16 +118,16 @@ end --- vim.ui.open("https://neovim.io/") --- vim.ui.open("~/path/to/file") --- -- Synchronous (wait until the process exits). ---- local ok, cmd = vim.ui.open("$VIMRUNTIME") ---- if ok then +--- local cmd, err = vim.ui.open("$VIMRUNTIME") +--- if cmd then --- cmd:wait() --- end --- ``` --- ---@param path string Path or URL to open --- ----@return boolean # false if command not found, else true. ----@return vim.SystemObj|string # Command object, or error message on failure +---@return vim.SystemObj|nil # Command object, or nil if not found. +---@return nil|string # Error message on failure, or nil on success. --- ---@see |vim.system()| function M.open(path) @@ -147,7 +147,7 @@ function M.open(path) if vim.fn.executable('rundll32') == 1 then cmd = { 'rundll32', 'url.dll,FileProtocolHandler', path } else - return false, 'vim.ui.open: rundll32 not found' + return nil, 'vim.ui.open: rundll32 not found' end elseif vim.fn.executable('wslview') == 1 then cmd = { 'wslview', path } @@ -156,10 +156,10 @@ function M.open(path) elseif vim.fn.executable('xdg-open') == 1 then cmd = { 'xdg-open', path } else - return false, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)' + return nil, 'vim.ui.open: no handler found (tried: wslview, explorer.exe, xdg-open)' end - return true, vim.system(cmd, { text = true, detach = true }) + return vim.system(cmd, { text = true, detach = true }), nil end return M -- cgit From 52823616bc4d77898ddc03da7629280841d3bced Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Fri, 3 May 2024 22:18:55 +0800 Subject: fix(lsp): replace bug-prone ternary operation #28627 ref #28624 --- runtime/lua/vim/lsp/inlay_hint.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 2c81a3f465..c6be54e65f 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -20,7 +20,11 @@ local bufstates = vim.defaulttable(function(_) return setmetatable({ applied = {} }, { __index = globalstate, __newindex = function(state, key, value) - rawset(state, key, (globalstate[key] ~= value) and value or nil) + if globalstate[key] == value then + rawset(state, key, nil) + else + rawset(state, key, value) + end end, }) end) -- cgit From 3a8265266e0c0fe31f34b7c0192e8ae7d83ae950 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 3 May 2024 09:34:02 -0700 Subject: fix(treesitter): escape "\" in :InspectTree #28613 Some parsers for, e.g., LaTeX or PHP have anonymous nodes like `"\"` or `"\text"` that behave wonkily (especially the first example) in the `InspectTree` window, so this PR escapes them by adding another backslash in front of them --- runtime/lua/vim/treesitter/dev.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index dc2a14d238..5c91f101c0 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -226,7 +226,7 @@ function TSTreeView:draw(bufnr) text = string.format('(%s', item.node:type()) end else - text = string.format('"%s"', item.node:type():gsub('\n', '\\n'):gsub('"', '\\"')) + text = string.format('%q', item.node:type()):gsub('\n', 'n') end local next = self:get(i + 1) -- cgit From e948d7feba240568b1c0ab9bcb37cc264666a67d Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 4 May 2024 15:53:42 +0800 Subject: vim-patch:ad4881cb3c04 (#28636) runtime(doc): correct getscriptinfo() example (vim/vim#14718) When "sid" is specified, it returns a List with a single item. https://github.com/vim/vim/commit/ad4881cb3c04048242f69dc77af2dde889c9beea --- runtime/lua/vim/_meta/vimfn.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 74b8590924..6674bbf82d 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3628,7 +3628,7 @@ function vim.fn.getregtype(regname) end --- --- Examples: >vim --- echo getscriptinfo({'name': 'myscript'}) ---- echo getscriptinfo({'sid': 15}).variables +--- echo getscriptinfo({'sid': 15})[0].variables --- < --- --- @param opts? table -- cgit From efb44e0cad294f51e330d57d7590d38de5cec62c Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Sat, 4 May 2024 15:08:17 -0700 Subject: docs: fix lua type warnings (#28633) --- runtime/lua/vim/_meta/api_keysets_extra.lua | 2 +- runtime/lua/vim/_meta/builtin_types.lua | 8 ++++++++ runtime/lua/vim/_meta/vimfn.lua | 2 +- runtime/lua/vim/lsp.lua | 7 ++++--- runtime/lua/vim/treesitter.lua | 2 +- 5 files changed, 15 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index d61dd2c02f..76b56b04e7 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -124,7 +124,7 @@ error('Cannot require a meta file') --- @field commalist boolean --- @field flaglist boolean --- @field was_set boolean ---- @field last_set_id integer +--- @field last_set_sid integer --- @field last_set_linenr integer --- @field last_set_chan integer --- @field type 'string'|'boolean'|'number' diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua index 0bbc3e9bc8..9f0d2e7038 100644 --- a/runtime/lua/vim/_meta/builtin_types.lua +++ b/runtime/lua/vim/_meta/builtin_types.lua @@ -127,3 +127,11 @@ --- @field skipcol integer --- @field topfill integer --- @field topline integer + +--- @class vim.fn.getscriptinfo.ret +--- @field autoload false +--- @field functions? string[] +--- @field name string +--- @field sid string +--- @field variables? table +--- @field version 1 diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 6674bbf82d..dc25b0dd40 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3632,7 +3632,7 @@ function vim.fn.getregtype(regname) end --- < --- --- @param opts? table ---- @return any +--- @return vim.fn.getscriptinfo.ret[] function vim.fn.getscriptinfo(opts) end --- If {tabnr} is not specified, then information about all the diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c6d6c8a0cd..325c30ca38 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -310,6 +310,7 @@ local function is_empty_or_default(bufnr, option) end local info = api.nvim_get_option_info2(option, { buf = bufnr }) + ---@param e vim.fn.getscriptinfo.ret local scriptinfo = vim.tbl_filter(function(e) return e.sid == info.last_set_sid end, vim.fn.getscriptinfo()) @@ -515,7 +516,7 @@ local function buf_attach(bufnr) textDocument = { uri = uri, }, - reason = protocol.TextDocumentSaveReason.Manual, + reason = protocol.TextDocumentSaveReason.Manual, ---@type integer } if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then client.notify(ms.textDocument_willSave, params) @@ -899,7 +900,7 @@ end --- a `client_id:result` map. ---@return function cancel Function that cancels all requests. function lsp.buf_request_all(bufnr, method, params, handler) - local results = {} --- @type table + local results = {} --- @type table local result_count = 0 local expected_result_count = 0 @@ -940,7 +941,7 @@ end ---@return table? result Map of client_id:request_result. ---@return string? err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil. function lsp.buf_request_sync(bufnr, method, params, timeout_ms) - local request_results + local request_results ---@type table local cancel = lsp.buf_request_all(bufnr, method, params, function(it) request_results = it diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4f4762547a..db544c1ab1 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -327,7 +327,7 @@ function M.get_captures_at_cursor(winnr) end --- Optional keyword arguments: ---- @class vim.treesitter.get_node.Opts +--- @class vim.treesitter.get_node.Opts : vim.treesitter.LanguageTree.tree_for_range.Opts --- @inlinedoc --- --- Buffer number (nil or 0 for current buffer) -- cgit From 5e98439f6d8c3a4d2da9a295e522e96c9cdaf891 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Sun, 5 May 2024 17:45:47 +0300 Subject: fix(defaults): diagnostic mappings descriptions #28646 --- runtime/lua/vim/_defaults.lua | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 6d31a3ea93..bd1afd0beb 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -183,26 +183,22 @@ do do vim.keymap.set('n', ']d', function() vim.diagnostic.goto_next({ float = false }) - end, { - desc = 'Jump to the next diagnostic with the highest severity', - }) + end, { desc = 'Jump to the next diagnostic' }) vim.keymap.set('n', '[d', function() vim.diagnostic.goto_prev({ float = false }) - end, { - desc = 'Jump to the previous diagnostic with the highest severity', - }) + end, { desc = 'Jump to the previous diagnostic' }) vim.keymap.set('n', 'd', function() vim.diagnostic.open_float() - end, { - desc = 'Open a floating window showing diagnostics under the cursor', - }) - - vim.keymap.set('n', '', 'd', { - remap = true, - desc = 'Open a floating window showing diagnostics under the cursor', - }) + end, { desc = 'Show diagnostics under the cursor' }) + + vim.keymap.set( + 'n', + '', + 'd', + { remap = true, desc = 'Show diagnostics under the cursor' } + ) end end -- cgit From 783c1e596c35e44e9699e5a1881d3c8f5c4fc596 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 6 May 2024 04:42:30 -0700 Subject: refactor(snippet): rename exit() => stop() #28628 --- runtime/lua/vim/snippet.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 18c613cb85..8fe03b3882 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -343,7 +343,7 @@ local function setup_autocmds(bufnr) or cursor_row > snippet_range[3] or (cursor_row == snippet_range[3] and cursor_col > snippet_range[4]) then - M.exit() + M.stop() return true end @@ -362,7 +362,7 @@ local function setup_autocmds(bufnr) end -- The cursor is either not on a tabstop or we reached the end, so exit the session. - M.exit() + M.stop() return true end, }) @@ -378,7 +378,7 @@ local function setup_autocmds(bufnr) (snippet_range[1] == snippet_range[3] and snippet_range[2] == snippet_range[4]) or snippet_range[3] + 1 > vim.fn.line('$') then - M.exit() + M.stop() end if not M.active() then @@ -615,7 +615,7 @@ function M.active(filter) end --- Exits the current snippet. -function M.exit() +function M.stop() if not M.active() then return end -- cgit From bb032d952bfc692062fceb764ff2742c5bdd3324 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Mon, 6 May 2024 08:13:50 -0500 Subject: revert: default LSP mappings (#28649) Revert the default LSP mappings before the 0.10 release as these might need some further consideration. In particular, it's not clear if "c" prefixed maps in Normal mode are acceptable as defaults since they interfere with text objects or operator ranges. We will re-introduce default mappings at the beginning of the 0.11 release cycle, this reversion is only for the imminent 0.10 release. --- runtime/lua/vim/_defaults.lua | 30 ------------------------------ 1 file changed, 30 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index bd1afd0beb..4684902410 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -146,36 +146,6 @@ do vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) end - --- Default maps for LSP functions. - --- - --- These are mapped unconditionally to avoid confusion. If no server is attached, or if a server - --- does not support a capability, an error message is displayed rather than exhibiting different - --- behavior. - --- - --- See |gr-default|, |crn|, |crr|, |i_CTRL-S|. - do - vim.keymap.set('n', 'crn', function() - vim.lsp.buf.rename() - end, { desc = 'vim.lsp.buf.rename()' }) - - local function map_codeaction(mode, lhs) - vim.keymap.set(mode, lhs, function() - vim.lsp.buf.code_action() - end, { desc = 'vim.lsp.buf.code_action()' }) - end - map_codeaction('n', 'crr') - map_codeaction('x', 'r') - map_codeaction('x', '') - - vim.keymap.set('n', 'gr', function() - vim.lsp.buf.references() - end, { desc = 'vim.lsp.buf.references()' }) - - vim.keymap.set('i', '', function() - vim.lsp.buf.signature_help() - end, { desc = 'vim.lsp.buf.signature_help()' }) - end - --- Map [d and ]d to move to the previous/next diagnostic. Map d to open a floating window --- for the diagnostic under the cursor. --- -- cgit From c3c673cdeca25594454f8721e725e6ff1127e2a8 Mon Sep 17 00:00:00 2001 From: Yi Ming Date: Tue, 7 May 2024 17:30:19 +0800 Subject: fix(lsp): enable() does not activate inlay hints on open buffers #28629 Problem: inlay_hint `enable()` does not activate inlay hints on open buffers. If a buffer does not have a corresponding `bufstate` in `bufstates`, then `enable` all buffers will not take effect on it. Solution: Make the effective range determined by the loaded buffers. Fix #28624 --- runtime/lua/vim/lsp/inlay_hint.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index c6be54e65f..d983357a2c 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -429,7 +429,7 @@ function M.enable(enable, filter) if filter.bufnr == nil then globalstate.enabled = enable - for bufnr, _ in pairs(bufstates) do + for _, bufnr in ipairs(api.nvim_list_bufs()) do if api.nvim_buf_is_loaded(bufnr) then if enable == false then _disable(bufnr) -- cgit From e7f50f43c82225eeecbff531b55d6ed26fad1bf5 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Tue, 23 Apr 2024 00:29:14 +0900 Subject: fix(treesitter): clip end row early Problem: UINT32_MAX + 1 passed to vim._foldupdate. Solution: Clip the end row from treesitter asap to avoid such issues. --- runtime/lua/vim/treesitter/_fold.lua | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 09d3f3368f..eecf1ad6b1 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -103,20 +103,6 @@ local function edit_range(range, srow, erow_old, erow_new) range[2] = math.max(range[2], erow_new) end ---- If a parser doesn't have any ranges explicitly set, treesitter will ---- return a range with end_row and end_bytes with a value of UINT32_MAX, ---- so clip end_row to the max buffer line. ---- ---- TODO(lewis6991): Handle this generally ---- ---- @param bufnr integer ---- @param erow integer? 0-indexed, exclusive ---- @return integer -local function normalise_erow(bufnr, erow) - local max_erow = api.nvim_buf_line_count(bufnr) - return math.min(erow or max_erow, max_erow) -end - -- TODO(lewis6991): Setup a decor provider so injections folds can be parsed -- as the window is redrawn ---@param bufnr integer @@ -126,7 +112,7 @@ end ---@param parse_injections? boolean local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 - erow = normalise_erow(bufnr, erow) + erow = erow or api.nvim_buf_line_count(bufnr) local parser = ts.get_parser(bufnr) @@ -314,9 +300,16 @@ end local function on_changedtree(bufnr, foldinfo, tree_changes) schedule_if_loaded(bufnr, function() local srow_upd, erow_upd ---@type integer?, integer? + local max_erow = api.nvim_buf_line_count(bufnr) for _, change in ipairs(tree_changes) do local srow, _, erow, ecol = Range.unpack4(change) - if ecol > 0 then + -- If a parser doesn't have any ranges explicitly set, treesitter will + -- return a range with end_row and end_bytes with a value of UINT32_MAX, + -- so clip end_row to the max buffer line. + -- TODO(lewis6991): Handle this generally + if erow > max_erow then + erow = max_erow + elseif ecol > 0 then erow = erow + 1 end -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit. -- cgit From 4e5086a67e8916d9a5c5c5cb1933633b3e200eee Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Mon, 29 Apr 2024 19:20:31 +0200 Subject: refactor(lsp): s/options/opts for parameters in vim.lsp.buf See https://github.com/neovim/neovim/pull/28483#discussion_r1583344120 --- runtime/lua/vim/lsp/buf.lua | 116 ++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 58 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 79c1aeb53d..820ff9c2ca 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -35,13 +35,13 @@ function M.hover() request(ms.textDocument_hover, params) end -local function request_with_options(name, params, options) +local function request_with_opts(name, params, opts) local req_handler --- @type function? - if options then + if opts then req_handler = function(err, result, ctx, config) local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) local handler = client.handlers[name] or vim.lsp.handlers[name] - handler(err, result, ctx, vim.tbl_extend('force', config or {}, options)) + handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts)) end end request(name, params, req_handler) @@ -83,32 +83,32 @@ end --- Jumps to the declaration of the symbol under the cursor. --- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead. ---- @param options? vim.lsp.LocationOpts -function M.declaration(options) +--- @param opts? vim.lsp.LocationOpts +function M.declaration(opts) local params = util.make_position_params() - request_with_options(ms.textDocument_declaration, params, options) + request_with_opts(ms.textDocument_declaration, params, opts) end --- Jumps to the definition of the symbol under the cursor. ---- @param options? vim.lsp.LocationOpts -function M.definition(options) +--- @param opts? vim.lsp.LocationOpts +function M.definition(opts) local params = util.make_position_params() - request_with_options(ms.textDocument_definition, params, options) + request_with_opts(ms.textDocument_definition, params, opts) end --- Jumps to the definition of the type of the symbol under the cursor. ---- @param options? vim.lsp.LocationOpts -function M.type_definition(options) +--- @param opts? vim.lsp.LocationOpts +function M.type_definition(opts) local params = util.make_position_params() - request_with_options(ms.textDocument_typeDefinition, params, options) + request_with_opts(ms.textDocument_typeDefinition, params, opts) end --- Lists all the implementations for the symbol under the cursor in the --- quickfix window. ---- @param options? vim.lsp.LocationOpts -function M.implementation(options) +--- @param opts? vim.lsp.LocationOpts +function M.implementation(opts) local params = util.make_position_params() - request_with_options(ms.textDocument_implementation, params, options) + request_with_opts(ms.textDocument_implementation, params, opts) end --- Displays signature information about the symbol under the cursor in a @@ -213,25 +213,25 @@ end --- Formats a buffer using the attached (and optionally filtered) language --- server clients. --- ---- @param options? vim.lsp.buf.format.Opts -function M.format(options) - options = options or {} - local bufnr = options.bufnr or api.nvim_get_current_buf() +--- @param opts? vim.lsp.buf.format.Opts +function M.format(opts) + opts = opts or {} + local bufnr = opts.bufnr or api.nvim_get_current_buf() local mode = api.nvim_get_mode().mode - local range = options.range + local range = opts.range if not range and mode == 'v' or mode == 'V' then range = range_from_selection(bufnr, mode) end local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting local clients = vim.lsp.get_clients({ - id = options.id, + id = opts.id, bufnr = bufnr, - name = options.name, + name = opts.name, method = method, }) - if options.filter then - clients = vim.tbl_filter(options.filter, clients) + if opts.filter then + clients = vim.tbl_filter(opts.filter, clients) end if #clients == 0 then @@ -250,12 +250,12 @@ function M.format(options) return params end - if options.async then + if opts.async then local function do_format(idx, client) if not client then return end - local params = set_range(client, util.make_formatting_params(options.formatting_options)) + local params = set_range(client, util.make_formatting_params(opts.formatting_options)) client.request(method, params, function(...) local handler = client.handlers[method] or vim.lsp.handlers[method] handler(...) @@ -264,9 +264,9 @@ function M.format(options) end do_format(next(clients)) else - local timeout_ms = options.timeout_ms or 1000 + local timeout_ms = opts.timeout_ms or 1000 for _, client in pairs(clients) do - local params = set_range(client, util.make_formatting_params(options.formatting_options)) + local params = set_range(client, util.make_formatting_params(opts.formatting_options)) local result, err = client.request_sync(method, params, timeout_ms, bufnr) if result and result.result then util.apply_text_edits(result.result, bufnr, client.offset_encoding) @@ -295,18 +295,18 @@ end --- ---@param new_name string|nil If not provided, the user will be prompted for a new --- name using |vim.ui.input()|. ----@param options? vim.lsp.buf.rename.Opts Additional options: -function M.rename(new_name, options) - options = options or {} - local bufnr = options.bufnr or api.nvim_get_current_buf() +---@param opts? vim.lsp.buf.rename.Opts Additional options: +function M.rename(new_name, opts) + opts = opts or {} + local bufnr = opts.bufnr or api.nvim_get_current_buf() local clients = vim.lsp.get_clients({ bufnr = bufnr, - name = options.name, + name = opts.name, -- Clients must at least support rename, prepareRename is optional method = ms.textDocument_rename, }) - if options.filter then - clients = vim.tbl_filter(options.filter, clients) + if opts.filter then + clients = vim.tbl_filter(opts.filter, clients) end if #clients == 0 then @@ -415,21 +415,21 @@ end --- ---@param context (table|nil) Context for the request ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references ----@param options? vim.lsp.ListOpts -function M.references(context, options) +---@param opts? vim.lsp.ListOpts +function M.references(context, opts) validate({ context = { context, 't', true } }) local params = util.make_position_params() params.context = context or { includeDeclaration = true, } - request_with_options(ms.textDocument_references, params, options) + request_with_opts(ms.textDocument_references, params, opts) end --- Lists all symbols in the current buffer in the quickfix window. ---- @param options? vim.lsp.ListOpts -function M.document_symbol(options) +--- @param opts? vim.lsp.ListOpts +function M.document_symbol(opts) local params = { textDocument = util.make_text_document_params() } - request_with_options(ms.textDocument_documentSymbol, params, options) + request_with_opts(ms.textDocument_documentSymbol, params, opts) end --- @param call_hierarchy_items lsp.CallHierarchyItem[]? @@ -542,7 +542,7 @@ function M.typehierarchy(kind) ) end else - local opts = { + local select_opts = { prompt = 'Select a type hierarchy item:', kind = 'typehierarchy', format_item = function(item) @@ -553,7 +553,7 @@ function M.typehierarchy(kind) end, } - vim.ui.select(merged_results, opts, function(item) + vim.ui.select(merged_results, select_opts, function(item) local client = vim.lsp.get_client_by_id(item[1]) if client then --- @type lsp.TypeHierarchyItem @@ -626,14 +626,14 @@ end --- string means no filtering is done. --- --- @param query string? optional ---- @param options? vim.lsp.ListOpts -function M.workspace_symbol(query, options) +--- @param opts? vim.lsp.ListOpts +function M.workspace_symbol(query, opts) query = query or npcall(vim.fn.input, 'Query: ') if query == nil then return end local params = { query = query } - request_with_options(ms.workspace_symbol, params, options) + request_with_opts(ms.workspace_symbol, params, opts) end --- Send request to the server to resolve document highlights for the current @@ -825,19 +825,19 @@ end --- Selects a code action available at the current --- cursor position. --- ----@param options? vim.lsp.buf.code_action.Opts +---@param opts? vim.lsp.buf.code_action.Opts ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction ---@see vim.lsp.protocol.CodeActionTriggerKind -function M.code_action(options) - validate({ options = { options, 't', true } }) - options = options or {} +function M.code_action(opts) + validate({ options = { opts, 't', true } }) + opts = opts or {} -- Detect old API call code_action(context) which should now be -- code_action({ context = context} ) --- @diagnostic disable-next-line:undefined-field - if options.diagnostics or options.only then - options = { options = options } + if opts.diagnostics or opts.only then + opts = { options = opts } end - local context = options.context or {} + local context = opts.context or {} if not context.triggerKind then context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked end @@ -867,17 +867,17 @@ function M.code_action(options) results[ctx.client_id] = { error = err, result = result, ctx = ctx } remaining = remaining - 1 if remaining == 0 then - on_code_action_results(results, options) + on_code_action_results(results, opts) end end for _, client in ipairs(clients) do ---@type lsp.CodeActionParams local params - if options.range then - assert(type(options.range) == 'table', 'code_action range must be a table') - local start = assert(options.range.start, 'range must have a `start` property') - local end_ = assert(options.range['end'], 'range must have a `end` property') + if opts.range then + assert(type(opts.range) == 'table', 'code_action range must be a table') + local start = assert(opts.range.start, 'range must have a `start` property') + local end_ = assert(opts.range['end'], 'range must have a `end` property') params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding) elseif mode == 'v' or mode == 'V' then local range = range_from_selection(bufnr, mode) -- cgit From 3da251efc6329c4d5e6b94780815dce8d6e24999 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 09:56:18 +0200 Subject: refactor(lsp): use vim.is_callable() --- runtime/lua/vim/lsp/handlers.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 4672d94105..11818b6a89 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -257,7 +257,7 @@ M[ms.textDocument_references] = function(_, result, ctx, config) vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) api.nvim_command('lopen') elseif config.on_list then - assert(type(config.on_list) == 'function', 'on_list is not a function') + assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list({ title = title, items = items, context = ctx }) else vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) @@ -290,7 +290,7 @@ local function response_to_list(map_result, entity, title_fn) vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) api.nvim_command('lopen') elseif config.on_list then - assert(type(config.on_list) == 'function', 'on_list is not a function') + assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list({ title = title, items = items, context = ctx }) else vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) @@ -436,7 +436,7 @@ local function location_handler(_, result, ctx, config) local items = util.locations_to_items(result, client.offset_encoding) if config.on_list then - assert(type(config.on_list) == 'function', 'on_list is not a function') + assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list({ title = title, items = items }) return end -- cgit From e14e75099883e6904cf3575b612f3820cd122938 Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Tue, 7 May 2024 14:09:27 -0400 Subject: fix(lsp): rename LspProgress data.result => data.params #28632 Rename the field `result` to `params` in the `data` table for `LspProgress` autocmds. This aligns with LspNotify. The previous name was chosen because the initial handler implementation mistakenly had a parameter name `result` instead of `params` for the `$/progress` LSP "notification" handler. However, `params` would be a more appropriate name that is more consistent with the underlying LSP type (`ProgressParams`). See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress --- runtime/lua/vim/lsp/handlers.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 4672d94105..620eebeeb4 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -22,16 +22,16 @@ M[ms.workspace_executeCommand] = function(_, _, _, _) end --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress ----@param result lsp.ProgressParams +---@param params lsp.ProgressParams ---@param ctx lsp.HandlerContext -M[ms.dollar_progress] = function(_, result, ctx) +M[ms.dollar_progress] = function(_, params, ctx) local client = vim.lsp.get_client_by_id(ctx.client_id) if not client then err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update') return vim.NIL end local kind = nil - local value = result.value + local value = params.value if type(value) == 'table' then kind = value.kind @@ -39,21 +39,21 @@ M[ms.dollar_progress] = function(_, result, ctx) -- So that consumers always have it available, even if they consume a -- subset of the full sequence if kind == 'begin' then - client.progress.pending[result.token] = value.title + client.progress.pending[params.token] = value.title else - value.title = client.progress.pending[result.token] + value.title = client.progress.pending[params.token] if kind == 'end' then - client.progress.pending[result.token] = nil + client.progress.pending[params.token] = nil end end end - client.progress:push(result) + client.progress:push(params) api.nvim_exec_autocmds('LspProgress', { pattern = kind, modeline = false, - data = { client_id = ctx.client_id, result = result }, + data = { client_id = ctx.client_id, params = params }, }) end -- cgit From 6ffc209a8a84c6d627dde39d27283d98a451b105 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 09:58:07 +0200 Subject: refactor(lsp): move repeated table construction into a variable As suggested in https://github.com/neovim/neovim/pull/28483#discussion_r1581712828 --- runtime/lua/vim/lsp/handlers.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 11818b6a89..de146ab1ef 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -253,14 +253,15 @@ M[ms.textDocument_references] = function(_, result, ctx, config) local title = 'References' local items = util.locations_to_items(result, client.offset_encoding) + local list = { title = title, items = items, context = ctx } if config.loclist then - vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) + vim.fn.setloclist(0, {}, ' ', list) api.nvim_command('lopen') elseif config.on_list then assert(vim.is_callable(config.on_list), 'on_list is not a function') - config.on_list({ title = title, items = items, context = ctx }) + config.on_list(list) else - vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) + vim.fn.setqflist({}, ' ', list) api.nvim_command('botright copen') end end @@ -286,14 +287,15 @@ local function response_to_list(map_result, entity, title_fn) local title = title_fn(ctx) local items = map_result(result, ctx.bufnr) + local list = { title = title, items = items, context = ctx } if config.loclist then - vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx }) + vim.fn.setloclist(0, {}, ' ', list) api.nvim_command('lopen') elseif config.on_list then assert(vim.is_callable(config.on_list), 'on_list is not a function') - config.on_list({ title = title, items = items, context = ctx }) + config.on_list(list) else - vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx }) + vim.fn.setqflist({}, ' ', list) api.nvim_command('botright copen') end end -- cgit From cdc0974063fd18199040e783df5826af5786aee3 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 10:02:56 +0200 Subject: docs(lsp): fix type annotations in response_to_list(...) --- runtime/lua/vim/lsp/handlers.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index de146ab1ef..aff98dc5cb 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -273,7 +273,7 @@ end --- --- loclist: (boolean) use the location list (default is to use the quickfix list) --- ----@param map_result function `((resp, bufnr) -> list)` to convert the response +---@param map_result fun(resp, bufnr: integer): table to convert the response ---@param entity string name of the resource used in a `not found` error message ---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title ---@return lsp.Handler -- cgit From 80d108eeee89c095e0c1226a558a690a708abaa1 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 10:04:47 +0200 Subject: refactor(lsp): use vim.cmd instead of api.nvim_command As suggested in https://github.com/neovim/neovim/pull/28483#discussion_r1586878457 and https://github.com/neovim/neovim/pull/28483#discussion_r1586878226 --- runtime/lua/vim/lsp/handlers.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index aff98dc5cb..c639a317cf 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -256,13 +256,13 @@ M[ms.textDocument_references] = function(_, result, ctx, config) local list = { title = title, items = items, context = ctx } if config.loclist then vim.fn.setloclist(0, {}, ' ', list) - api.nvim_command('lopen') + vim.cmd.lopen() elseif config.on_list then assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list(list) else vim.fn.setqflist({}, ' ', list) - api.nvim_command('botright copen') + vim.cmd('botright copen') end end @@ -290,13 +290,13 @@ local function response_to_list(map_result, entity, title_fn) local list = { title = title, items = items, context = ctx } if config.loclist then vim.fn.setloclist(0, {}, ' ', list) - api.nvim_command('lopen') + vim.cmd.lopen() elseif config.on_list then assert(vim.is_callable(config.on_list), 'on_list is not a function') config.on_list(list) else vim.fn.setqflist({}, ' ', list) - api.nvim_command('botright copen') + vim.cmd('botright copen') end end end @@ -447,7 +447,7 @@ local function location_handler(_, result, ctx, config) return end vim.fn.setqflist({}, ' ', { title = title, items = items }) - api.nvim_command('botright copen') + vim.cmd('botright copen') end --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration @@ -557,7 +557,7 @@ local function make_call_hierarchy_handler(direction) end end vim.fn.setqflist({}, ' ', { title = 'LSP call hierarchy', items = items }) - api.nvim_command('botright copen') + vim.cmd('botright copen') end end @@ -596,7 +596,7 @@ local function make_type_hierarchy_handler() }) end vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items }) - api.nvim_command('botright copen') + vim.cmd('botright copen') end end -- cgit From b0cc85c00504dc5530ab5d6c5fa03f3fa96d5a1a Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 10:10:44 +0200 Subject: docs(lsp): document vim.lsp.ListOpts.loclist --- runtime/lua/vim/lsp/buf.lua | 9 ++++----- runtime/lua/vim/lsp/handlers.lua | 5 +---- 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 820ff9c2ca..e05acff3df 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -62,14 +62,13 @@ end --- vim.lsp.buf.references(nil, { on_list = on_list }) --- ``` --- ---- If you prefer loclist do something like this: +--- If you prefer loclist instead of qflist: --- ```lua ---- local function on_list(options) ---- vim.fn.setloclist(0, {}, ' ', options) ---- vim.cmd.lopen() ---- end +--- vim.lsp.buf.definition({ loclist = true }) +--- vim.lsp.buf.references(nil, { loclist = true }) --- ``` --- @field on_list? fun(t: vim.lsp.LocationOpts.OnList) +--- @field loclist? boolean --- @class vim.lsp.LocationOpts.OnList --- @field items table[] Structured like |setqflist-what| diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index c639a317cf..205de68101 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -268,10 +268,7 @@ end --- Return a function that converts LSP responses to list items and opens the list --- ---- The returned function has an optional {config} parameter that accepts a table ---- with the following keys: ---- ---- loclist: (boolean) use the location list (default is to use the quickfix list) +--- The returned function has an optional {config} parameter that accepts |vim.lsp.ListOpts| --- ---@param map_result fun(resp, bufnr: integer): table to convert the response ---@param entity string name of the resource used in a `not found` error message -- cgit From 5c40f3e86a81f78f821b8c75dafc3ce2ce67e1c5 Mon Sep 17 00:00:00 2001 From: tom-anders <13141438+tom-anders@users.noreply.github.com> Date: Sat, 4 May 2024 10:13:08 +0200 Subject: feat(lsp): support vim.lsp.ListOpts.loclist in location_handler() --- runtime/lua/vim/lsp/handlers.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 205de68101..eec16d7298 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -443,8 +443,13 @@ local function location_handler(_, result, ctx, config) util.jump_to_location(result[1], client.offset_encoding, config.reuse_win) return end - vim.fn.setqflist({}, ' ', { title = title, items = items }) - vim.cmd('botright copen') + if config.loclist then + vim.fn.setloclist(0, {}, ' ', { title = title, items = items }) + vim.cmd.lopen() + else + vim.fn.setqflist({}, ' ', { title = title, items = items }) + vim.cmd('botright copen') + end end --- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration -- cgit From d3fa88b70f6f96ec4bc505968afec1dedb3b450b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 8 May 2024 18:39:18 -0500 Subject: vim-patch:9.1.0396: filetype: jj files are not recognized (#28672) Problem: jj files are not recognized Solution: recognize '*.jjdescription' files as jj filetype (Gregory Anders) See: https://github.com/martinvonz/jj closes: vim/vim#14733 https://github.com/vim/vim/commit/6a4ea471d28107c4078e106ace1bdc0c54bf946b --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 3b7ea03c2d..1ea1bbccf6 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -570,6 +570,7 @@ local extension = { jsx = 'javascriptreact', clp = 'jess', jgr = 'jgraph', + jjdescription = 'jj', j73 = 'jovial', jov = 'jovial', jovial = 'jovial', -- cgit From c1a95d9653f39c5e118d030270e4b77ebd20139e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 May 2024 13:47:30 +0100 Subject: fix(lsp): disable didChangeWatchedFiles on Linux Problem: The file watcher backends for Linux have too many limitations and doesn't work reliably. Solution: disable didChangeWatchedFiles on Linux Ref: #27807, #28058, #23291, #26520 --- runtime/lua/vim/lsp/protocol.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 599f02425e..419c2ff644 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -10,6 +10,8 @@ local function get_value_set(tbl) return value_set end +local sysname = vim.uv.os_uname().sysname + -- Protocol for the Microsoft Language Server Protocol (mslsp) local protocol = {} @@ -835,7 +837,10 @@ function protocol.make_client_capabilities() refreshSupport = true, }, didChangeWatchedFiles = { - dynamicRegistration = true, + -- TODO(lewis6991): do not advertise didChangeWatchedFiles on Linux + -- or BSD since all the current backends are too limited. + -- Ref: #27807, #28058, #23291, #26520 + dynamicRegistration = sysname == 'Darwin' or sysname == 'Windows_NT', relativePatternSupport = true, }, inlayHint = { -- cgit From a9fd17e23289c4a74edbb6f02abde14ab4eac05c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 10 May 2024 18:30:15 +0200 Subject: vim-patch:9.1.0401: filetype: zsh module files are not recognized Problem: filetype: zsh module files are not recognized Solution: Detect '*.mdh' and '*.epro' as C filetype, '*.mdd' as zsh filetype, determine zsh-modules '*.pro' from from it's content (Wu, Zhenyu) closes: vim/vim#14737 https://github.com/vim/vim/commit/887a38cee78c472fe406da60751fbba4a6ec19dd Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 3 +++ runtime/lua/vim/filetype/detect.lua | 14 ++++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1ea1bbccf6..4d3d219415 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -248,6 +248,8 @@ local extension = { bzl = 'bzl', bazel = 'bzl', BUILD = 'bzl', + mdh = 'c', + epro = 'c', qc = 'c', cabal = 'cabal', cairo = 'cairo', @@ -1206,6 +1208,7 @@ local extension = { zsh = 'zsh', zunit = 'zsh', ['zsh-theme'] = 'zsh', + mdd = 'zsh', vala = 'vala', web = detect.web, pl = detect.pl, diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 05b4ffc223..ba86d8de5a 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1141,12 +1141,14 @@ end --- Distinguish between "default", Prolog and Cproto prototype file. --- @type vim.filetype.mapfn function M.proto(_, bufnr) - -- Cproto files have a comment in the first line and a function prototype in - -- the second line, it always ends in ";". Indent files may also have - -- comments, thus we can't match comments to see the difference. - -- IDL files can have a single ';' in the second line, require at least one - -- character before the ';'. - if getline(bufnr, 2):find('.;$') then + if getline(bufnr, 2):find('/%* Generated automatically %*/') then + return 'c' + elseif getline(bufnr, 2):find('.;$') then + -- Cproto files have a comment in the first line and a function prototype in + -- the second line, it always ends in ";". Indent files may also have + -- comments, thus we can't match comments to see the difference. + -- IDL files can have a single ';' in the second line, require at least one + -- character before the ';'. return 'cpp' end -- Recognize Prolog by specific text in the first non-empty line; -- cgit From a6873450b96ff3c6021997c1d18cb189d4466a36 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 10 May 2024 18:36:07 +0200 Subject: vim-patch:9.1.0402: filetype: mdd files detected as zsh filetype Problem: filetype: mdd files detected as zsh filetype Solution: detect '*.mdd' files as sh filetype, add links to reference documentation (Wu, Zhenyu) closes: vim/vim#14741 https://github.com/vim/vim/commit/63f2a5b8adfb570792b9a7cbfff1c350913bbe3e Co-authored-by: Wu, Zhenyu --- runtime/lua/vim/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 4d3d219415..d6f680b195 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -952,6 +952,7 @@ local extension = { env = detect.sh, ksh = detect.ksh, sh = detect.sh, + mdd = 'sh', sieve = 'sieve', siv = 'sieve', sig = detect.sig, @@ -1208,7 +1209,6 @@ local extension = { zsh = 'zsh', zunit = 'zsh', ['zsh-theme'] = 'zsh', - mdd = 'zsh', vala = 'vala', web = detect.web, pl = detect.pl, -- cgit From 8f0a166da4cd919947ef1ed634d350ef602acc63 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 20 Apr 2024 23:21:08 +0200 Subject: refactor(api): rename nvim_win_remove_ns Problem: nvim_win_remove_ns does not follow `help dev-naming` API naming conventions. Solution: Rename it. --- runtime/lua/vim/_meta/api.lua | 25 +++++++++++++------------ runtime/lua/vim/highlight.lua | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 64c67be076..dbd3c61634 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -656,8 +656,8 @@ function vim.api.nvim_buf_line_count(buffer) end --- • url: A URL to associate with this extmark. In the TUI, the --- OSC 8 control sequence is used to generate a clickable --- hyperlink to this URL. ---- • scoped: boolean that indicates that the extmark should only ---- be displayed in the namespace scope. (experimental) +--- • scoped: boolean (EXPERIMENTAL) enables "scoping" for the +--- extmark. See `nvim_win_add_ns()` --- @return integer function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end @@ -2114,10 +2114,11 @@ function vim.api.nvim_tabpage_set_var(tabpage, name, value) end --- @param win integer Window handle, must already belong to {tabpage} function vim.api.nvim_tabpage_set_win(tabpage, win) end ---- Adds the namespace scope to the window. +--- Scopes a namespace to the a window, so extmarks in the namespace will be +--- active only in the given window. --- --- @param window integer Window handle, or 0 for current window ---- @param ns_id integer the namespace to add +--- @param ns_id integer Namespace --- @return boolean function vim.api.nvim_win_add_ns(window, ns_id) end @@ -2137,6 +2138,13 @@ function vim.api.nvim_win_call(window, fun) end --- hidden, even if 'hidden' is not set. function vim.api.nvim_win_close(window, force) end +--- Unscopes a namespace (un-binds it from the given scope). +--- +--- @param window integer Window handle, or 0 for current window +--- @param ns_id integer the namespace to remove +--- @return boolean +function vim.api.nvim_win_del_ns(window, ns_id) end + --- Removes a window-scoped (w:) variable --- --- @param window integer Window handle, or 0 for current window @@ -2173,7 +2181,7 @@ function vim.api.nvim_win_get_cursor(window) end --- @return integer function vim.api.nvim_win_get_height(window) end ---- Gets all the namespaces scopes associated with a window. +--- Gets the namespace scopes for a given window. --- --- @param window integer Window handle, or 0 for current window --- @return integer[] @@ -2232,13 +2240,6 @@ function vim.api.nvim_win_hide(window) end --- @return boolean function vim.api.nvim_win_is_valid(window) end ---- Removes the namespace scope from the window. ---- ---- @param window integer Window handle, or 0 for current window ---- @param ns_id integer the namespace to remove ---- @return boolean -function vim.api.nvim_win_remove_ns(window, ns_id) end - --- Sets the current buffer in a window, without side effects --- --- @param window integer Window handle, or 0 for current window diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 781fabe5fb..da3d7fd361 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -141,7 +141,7 @@ function M.on_yank(opts) yank_timer = nil yank_cancel = nil pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1) - pcall(vim.api.nvim_win_remove_ns, winid, yank_ns) + pcall(vim.api.nvim_win_del_ns, winid, yank_ns) end yank_timer = vim.defer_fn(yank_cancel, timeout) -- cgit From 97c7646501d5cd6f57c57ce30acca89c5b8573ff Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 12 May 2024 23:12:25 +0200 Subject: refactor(api): nvim_win_xx_ns are EXPERIMENTAL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The nvim_win_xx_ns function family introduced in ba0370b1d718d473d0ef51c35d88b98ba220082b needs more bake-time. Currently it's narrowly defined for windows, but other scopes ("buffer") and features are likely in the future. Solution: - Rename the API with double-underscore to mark it as EXPERIMENTAL. TODO/FUTURE: - Rename and change the signature to support more than just "window" scope, and for other flexibility. - Open question: we could choose either: - "store scopes on namespaces", or - "store namespaces on scopes (w:/b:/…)" --- runtime/lua/vim/_meta/api.lua | 53 +++++++++++++++++++++++++------------------ runtime/lua/vim/highlight.lua | 4 ++-- 2 files changed, 33 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index dbd3c61634..6edf2a5a96 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -144,6 +144,36 @@ function vim.api.nvim__stats() end --- @return any function vim.api.nvim__unpack(str) end +--- @private +--- EXPERIMENTAL: this API will change in the future. +--- +--- Scopes a namespace to the a window, so extmarks in the namespace will be +--- active only in the given window. +--- +--- @param window integer Window handle, or 0 for current window +--- @param ns_id integer Namespace +--- @return boolean +function vim.api.nvim__win_add_ns(window, ns_id) end + +--- @private +--- EXPERIMENTAL: this API will change in the future. +--- +--- Unscopes a namespace (un-binds it from the given scope). +--- +--- @param window integer Window handle, or 0 for current window +--- @param ns_id integer the namespace to remove +--- @return boolean +function vim.api.nvim__win_del_ns(window, ns_id) end + +--- @private +--- EXPERIMENTAL: this API will change in the future. +--- +--- Gets the namespace scopes for a given window. +--- +--- @param window integer Window handle, or 0 for current window +--- @return integer[] +function vim.api.nvim__win_get_ns(window) end + --- Adds a highlight to buffer. --- --- Useful for plugins that dynamically generate highlights to a buffer (like @@ -657,7 +687,7 @@ function vim.api.nvim_buf_line_count(buffer) end --- OSC 8 control sequence is used to generate a clickable --- hyperlink to this URL. --- • scoped: boolean (EXPERIMENTAL) enables "scoping" for the ---- extmark. See `nvim_win_add_ns()` +--- extmark. See `nvim__win_add_ns()` --- @return integer function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end @@ -2114,14 +2144,6 @@ function vim.api.nvim_tabpage_set_var(tabpage, name, value) end --- @param win integer Window handle, must already belong to {tabpage} function vim.api.nvim_tabpage_set_win(tabpage, win) end ---- Scopes a namespace to the a window, so extmarks in the namespace will be ---- active only in the given window. ---- ---- @param window integer Window handle, or 0 for current window ---- @param ns_id integer Namespace ---- @return boolean -function vim.api.nvim_win_add_ns(window, ns_id) end - --- Calls a function with window as temporary current window. --- --- @param window integer Window handle, or 0 for current window @@ -2138,13 +2160,6 @@ function vim.api.nvim_win_call(window, fun) end --- hidden, even if 'hidden' is not set. function vim.api.nvim_win_close(window, force) end ---- Unscopes a namespace (un-binds it from the given scope). ---- ---- @param window integer Window handle, or 0 for current window ---- @param ns_id integer the namespace to remove ---- @return boolean -function vim.api.nvim_win_del_ns(window, ns_id) end - --- Removes a window-scoped (w:) variable --- --- @param window integer Window handle, or 0 for current window @@ -2181,12 +2196,6 @@ function vim.api.nvim_win_get_cursor(window) end --- @return integer function vim.api.nvim_win_get_height(window) end ---- Gets the namespace scopes for a given window. ---- ---- @param window integer Window handle, or 0 for current window ---- @return integer[] -function vim.api.nvim_win_get_ns(window) end - --- Gets the window number --- --- @param window integer Window handle, or 0 for current window diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index da3d7fd361..f278bd357f 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -129,7 +129,7 @@ function M.on_yank(opts) yank_cancel() end - vim.api.nvim_win_add_ns(winid, yank_ns) + vim.api.nvim__win_add_ns(winid, yank_ns) M.range(bufnr, yank_ns, higroup, "'[", "']", { regtype = event.regtype, inclusive = event.inclusive, @@ -141,7 +141,7 @@ function M.on_yank(opts) yank_timer = nil yank_cancel = nil pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1) - pcall(vim.api.nvim_win_del_ns, winid, yank_ns) + pcall(vim.api.nvim__win_del_ns, winid, yank_ns) end yank_timer = vim.defer_fn(yank_cancel, timeout) -- cgit From b6fdde5224250ec5dc3d5dcfec32d62a887173ff Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Sun, 12 May 2024 18:12:03 -0400 Subject: fix(treesitter): text alignment in checkhealth vim.treesitter Problem: The column width 10 for parser name (lang) is too short. For example, `markdown_inline` has 15 characters, which results in a slight misalignment with other lines. e.g. it looked like: ``` - OK Parser: markdown ABI: 14, path: .../parser/markdown.so - OK Parser: markdown_inline ABI: 14, path: .../parser/markdown_inline.so - OK Parser: php ABI: 14, path: .../parser/php.so ``` Solution: Use column width 20. As of now, the longest name among those available in nvim-treesitter has length 18 (`haskell_persistent`). e.g.: ``` - OK Parser: markdown ABI: 14, path: .../parser/markdown.so - OK Parser: markdown_inline ABI: 14, path: .../parser/markdown_inline.so - OK Parser: php ABI: 14, path: .../parser/php.so ``` --- runtime/lua/vim/treesitter/health.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index a9b066d158..ed3616ef46 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -24,7 +24,7 @@ function M.check() else local lang = ts.language.inspect(parsername) health.ok( - string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) + string.format('Parser: %-20s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) end end -- cgit From e3ec974324bd73b63f54503480a4e48d1887f8d9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 13 May 2024 05:00:39 -0700 Subject: refactor(lua): remove deprecated features #28725 --- runtime/lua/vim/iter.lua | 20 -------------------- runtime/lua/vim/lsp/inlay_hint.lua | 26 +++----------------------- runtime/lua/vim/shared.lua | 6 ------ 3 files changed, 3 insertions(+), 49 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index f887a9070b..5f1e3b6386 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1008,26 +1008,6 @@ function ListIter:enumerate() return self end ----@deprecated -function Iter:nextback() - error('Iter:nextback() was renamed to Iter:pop()') -end - ----@deprecated -function Iter:peekback() - error('Iter:peekback() was renamed to Iter:rpeek()') -end - ----@deprecated -function Iter:skipback() - error('Iter:skipback() was renamed to Iter:rskip()') -end - ----@deprecated -function Iter:nthback() - error('Iter:nthback() was removed, use Iter:nth() with negative index') -end - --- Creates a new Iter object from a table or other |iterable|. --- ---@param src table|function Table or iterator to drain values from diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index d983357a2c..effc69c67e 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -372,20 +372,9 @@ api.nvim_set_decoration_provider(namespace, { --- @return boolean --- @since 12 function M.is_enabled(filter) - ---@type integer - local bufnr - if type(filter) == 'number' then - vim.deprecate( - 'vim.lsp.inlay_hint.is_enabled(bufnr:number)', - 'vim.lsp.inlay_hint.is_enabled(filter:table)', - '0.10-dev' - ) - bufnr = filter - else - vim.validate({ filter = { filter, 'table', true } }) - filter = filter or {} - bufnr = filter.bufnr - end + vim.validate({ filter = { filter, 'table', true } }) + filter = filter or {} + local bufnr = filter.bufnr vim.validate({ bufnr = { bufnr, 'number', true } }) if bufnr == nil then @@ -414,15 +403,6 @@ end --- @param filter vim.lsp.inlay_hint.enable.Filter? --- @since 12 function M.enable(enable, filter) - if type(enable) == 'number' or type(filter) == 'boolean' then - vim.deprecate( - 'vim.lsp.inlay_hint.enable(bufnr:number, enable:boolean)', - 'vim.lsp.inlay_hint.enable(enable:boolean, filter:table)', - '0.10-dev' - ) - error('see :help vim.lsp.inlay_hint.enable() for updated parameters') - end - vim.validate({ enable = { enable, 'boolean', true }, filter = { filter, 'table', true } }) enable = enable == nil or enable filter = filter or {} diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 1c8059adab..4d753d727a 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -601,12 +601,6 @@ function vim.spairs(t) t end ---- @deprecated -function vim.tbl_isarray() - vim.deprecate('vim.tbl_isarray', 'vim.isarray', '0.10-dev') - error('vim.tbl_isarray was renamed to vim.isarray') -end - --- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous). --- --- If the indexes start from 1 and are contiguous then the array is also a list. |vim.islist()| -- cgit From 8bb67d64e20d2e0d3e1892cb89d9bb8f558471e4 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Mon, 13 May 2024 17:26:57 +0200 Subject: perf(fs): normalize path only once in fs.dir Re-normalizing a path after a `joinpath` isn't necessary. Calling `normalize` on each child directory had quite a bit of impact when traversing a large directory. A simple test showed: Before: ~144ms After: ~80ms running the following logic against a dir with 4367 child folders and 25826 files: local files = {} local start = uv.hrtime() for name, type in vim.fs.dir(path, { depth = max_depth }) do table.insert(files, { name, type }) end local duration = uv.hrtime() - start Relates to https://github.com/neovim/neovim/issues/23291 --- runtime/lua/vim/fs.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 766dc09691..e70175bcbd 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -120,8 +120,9 @@ function M.dir(path, opts) skip = { opts.skip, { 'function' }, true }, }) + path = M.normalize(path) if not opts.depth or opts.depth == 1 then - local fs = vim.uv.fs_scandir(M.normalize(path)) + local fs = vim.uv.fs_scandir(path) return function() if not fs then return @@ -137,7 +138,7 @@ function M.dir(path, opts) --- @type string, integer local dir0, level = unpack(table.remove(dirs, 1)) local dir = level == 1 and dir0 or M.joinpath(path, dir0) - local fs = vim.uv.fs_scandir(M.normalize(dir)) + local fs = vim.uv.fs_scandir(dir) while fs do local name, t = vim.uv.fs_scandir_next(fs) if not name then -- cgit From 2f4792943aa92223fadd472f20449cd13707ff7a Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sun, 12 May 2024 21:09:06 +0200 Subject: perf(lsp): only joinpath for dirs in watchdirs Doesn't have a huge impact, but showed up in profile output using `require("jit.p").start("i1", "/tmp/profile")` before: 31% joinpath 25% fs.lua:0 13% normalize 13% skip 8% _watchfunc 5% gsplit 3% spairs after: 34% skip 29% fs.lua:0 12% joinpath 7% normalize 5% _watchfunc 5% spairs --- runtime/lua/vim/_watch.lua | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 23c810099e..02b3f536c2 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -200,11 +200,13 @@ function M.watchdirs(path, opts, callback) local max_depth = 100 for name, type in vim.fs.dir(path, { depth = max_depth }) do - local filepath = vim.fs.joinpath(path, name) - if type == 'directory' and not skip(filepath, opts) then - local handle = assert(uv.new_fs_event()) - handles[filepath] = handle - handle:start(filepath, {}, create_on_change(filepath)) + if type == 'directory' then + local filepath = vim.fs.joinpath(path, name) + if not skip(filepath, opts) then + local handle = assert(uv.new_fs_event()) + handles[filepath] = handle + handle:start(filepath, {}, create_on_change(filepath)) + end end end -- cgit From abd2352bd8b896417af75ac85caad9a7f849841b Mon Sep 17 00:00:00 2001 From: Jongwook Choi Date: Tue, 14 May 2024 09:33:03 -0400 Subject: feat(lsp): update LSP protocol 3.18 typings to date (#28730) Make the LSP protocol typings up-to-date with LSP protocol (upcoming) version 3.18, before and in preparation for the Nvim 0.10.0 release. --- runtime/lua/vim/lsp/_meta/protocol.lua | 187 +++++++++++++++++++-------------- 1 file changed, 106 insertions(+), 81 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua index a5da5ac6b7..9a11972007 100644 --- a/runtime/lua/vim/lsp/_meta/protocol.lua +++ b/runtime/lua/vim/lsp/_meta/protocol.lua @@ -2534,8 +2534,14 @@ error('Cannot require a meta file') ---@proposed ---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions --- +---Text document specific server capabilities. +--- +---@since 3.18.0 +---@proposed +---@field textDocument? lsp._anonym12.textDocument +--- ---Workspace specific server capabilities. ----@field workspace? lsp._anonym12.workspace +---@field workspace? lsp._anonym14.workspace --- ---Experimental server capabilities. ---@field experimental? lsp.LSPAny @@ -2598,8 +2604,10 @@ error('Cannot require a meta file') ---appears in the user interface. ---@field source? string --- ----The diagnostic's message. It usually appears in the user interface ----@field message string +---The diagnostic's message. It usually appears in the user interface. +--- +---@since 3.18.0 - support for `MarkupContent`. This is guarded by the client capability `textDocument.diagnostic.markupMessageSupport`. +---@field message string|lsp.MarkupContent --- ---Additional metadata about the diagnostic. --- @@ -2684,7 +2692,7 @@ error('Cannot require a meta file') ---capabilities. --- ---@since 3.17.0 ----@field completionItem? lsp._anonym13.completionItem +---@field completionItem? lsp._anonym15.completionItem ---Hover options. ---@class lsp.HoverOptions: lsp.WorkDoneProgressOptions @@ -2811,6 +2819,8 @@ error('Cannot require a meta file') ---errors are currently presented to the user for the given range. There is no guarantee ---that these accurately reflect the error state of the resource. The primary parameter ---to compute code actions is the provided range. +--- +---Note that the client should check the `textDocument.diagnostic.markupMessageSupport` server capability before sending diagnostics with markup messages to a server. ---@field diagnostics lsp.Diagnostic[] --- ---Requested kind of actions to return. @@ -3146,7 +3156,7 @@ error('Cannot require a meta file') ---@class lsp.NotebookDocumentSyncOptions --- ---The notebooks to be synced ----@field notebookSelector (lsp._anonym14.notebookSelector|lsp._anonym16.notebookSelector)[] +---@field notebookSelector (lsp._anonym16.notebookSelector|lsp._anonym18.notebookSelector)[] --- ---Whether save notification should be forwarded to ---the server. Will only be honored if mode === `notebook`. @@ -3514,7 +3524,7 @@ error('Cannot require a meta file') ---anymore since the information is outdated). --- ---@since 3.17.0 ----@field staleRequestSupport? lsp._anonym18.staleRequestSupport +---@field staleRequestSupport? lsp._anonym20.staleRequestSupport --- ---Client capabilities specific to regular expressions. --- @@ -3590,7 +3600,7 @@ error('Cannot require a meta file') ---create file, rename file and delete file changes. --- ---@since 3.16.0 ----@field changeAnnotationSupport? lsp._anonym19.changeAnnotationSupport +---@field changeAnnotationSupport? lsp._anonym21.changeAnnotationSupport ---@class lsp.DidChangeConfigurationClientCapabilities --- @@ -3617,20 +3627,20 @@ error('Cannot require a meta file') ---@field dynamicRegistration? boolean --- ---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. ----@field symbolKind? lsp._anonym20.symbolKind +---@field symbolKind? lsp._anonym22.symbolKind --- ---The client supports tags on `SymbolInformation`. ---Clients supporting tags have to handle unknown tags gracefully. --- ---@since 3.16.0 ----@field tagSupport? lsp._anonym21.tagSupport +---@field tagSupport? lsp._anonym23.tagSupport --- ---The client support partial workspace symbols. The client will send the ---request `workspaceSymbol/resolve` to the server to resolve additional ---properties. --- ---@since 3.17.0 ----@field resolveSupport? lsp._anonym22.resolveSupport +---@field resolveSupport? lsp._anonym24.resolveSupport ---The client capabilities of a {@link ExecuteCommandRequest}. ---@class lsp.ExecuteCommandClientCapabilities @@ -3775,9 +3785,9 @@ error('Cannot require a meta file') --- ---The client supports the following `CompletionItem` specific ---capabilities. ----@field completionItem? lsp._anonym23.completionItem +---@field completionItem? lsp._anonym25.completionItem --- ----@field completionItemKind? lsp._anonym27.completionItemKind +---@field completionItemKind? lsp._anonym29.completionItemKind --- ---Defines how the client handles whitespace and indentation ---when accepting a completion item that uses multi line @@ -3794,7 +3804,7 @@ error('Cannot require a meta file') ---capabilities. --- ---@since 3.17.0 ----@field completionList? lsp._anonym28.completionList +---@field completionList? lsp._anonym30.completionList ---@class lsp.HoverClientCapabilities --- @@ -3813,7 +3823,7 @@ error('Cannot require a meta file') --- ---The client supports the following `SignatureInformation` ---specific properties. ----@field signatureInformation? lsp._anonym29.signatureInformation +---@field signatureInformation? lsp._anonym31.signatureInformation --- ---The client supports to send additional context information for a ---`textDocument/signatureHelp` request. A client that opts into @@ -3891,7 +3901,7 @@ error('Cannot require a meta file') --- ---Specific capabilities for the `SymbolKind` in the ---`textDocument/documentSymbol` request. ----@field symbolKind? lsp._anonym31.symbolKind +---@field symbolKind? lsp._anonym33.symbolKind --- ---The client supports hierarchical document symbols. ---@field hierarchicalDocumentSymbolSupport? boolean @@ -3901,7 +3911,7 @@ error('Cannot require a meta file') ---Clients supporting tags have to handle unknown tags gracefully. --- ---@since 3.16.0 ----@field tagSupport? lsp._anonym32.tagSupport +---@field tagSupport? lsp._anonym34.tagSupport --- ---The client supports an additional label presented in the UI when ---registering a document symbol provider. @@ -3920,7 +3930,7 @@ error('Cannot require a meta file') ---set the request can only return `Command` literals. --- ---@since 3.8.0 ----@field codeActionLiteralSupport? lsp._anonym33.codeActionLiteralSupport +---@field codeActionLiteralSupport? lsp._anonym35.codeActionLiteralSupport --- ---Whether code action supports the `isPreferred` property. --- @@ -3943,7 +3953,7 @@ error('Cannot require a meta file') ---properties via a separate `codeAction/resolve` request. --- ---@since 3.16.0 ----@field resolveSupport? lsp._anonym35.resolveSupport +---@field resolveSupport? lsp._anonym37.resolveSupport --- ---Whether the client honors the change annotations in ---text edits and resource operations returned via the @@ -4051,12 +4061,12 @@ error('Cannot require a meta file') ---Specific options for the folding range kind. --- ---@since 3.17.0 ----@field foldingRangeKind? lsp._anonym36.foldingRangeKind +---@field foldingRangeKind? lsp._anonym38.foldingRangeKind --- ---Specific options for the folding range. --- ---@since 3.17.0 ----@field foldingRange? lsp._anonym37.foldingRange +---@field foldingRange? lsp._anonym39.foldingRange ---@class lsp.SelectionRangeClientCapabilities --- @@ -4075,7 +4085,7 @@ error('Cannot require a meta file') ---Clients supporting tags have to handle unknown tags gracefully. --- ---@since 3.15.0 ----@field tagSupport? lsp._anonym38.tagSupport +---@field tagSupport? lsp._anonym40.tagSupport --- ---Whether the client interprets the version property of the ---`textDocument/publishDiagnostics` notification's parameter. @@ -4119,7 +4129,7 @@ error('Cannot require a meta file') ---`request.range` are both set to true but the server only provides a ---range provider the client might not render a minimap correctly or might ---even decide to not show any semantic tokens at all. ----@field requests lsp._anonym39.requests +---@field requests lsp._anonym41.requests --- ---The token types that the client supports. ---@field tokenTypes string[] @@ -4202,7 +4212,7 @@ error('Cannot require a meta file') --- ---Indicates which properties a client can resolve lazily on an inlay ---hint. ----@field resolveSupport? lsp._anonym42.resolveSupport +---@field resolveSupport? lsp._anonym44.resolveSupport ---Client capabilities specific to diagnostic pull requests. --- @@ -4216,6 +4226,9 @@ error('Cannot require a meta file') --- ---Whether the clients supports related documents for document diagnostic pulls. ---@field relatedDocumentSupport? boolean +--- +---Whether the client supports `MarkupContent` in diagnostic messages. +---@field markupMessageSupport? boolean ---Client capabilities specific to inline completions. --- @@ -4244,7 +4257,7 @@ error('Cannot require a meta file') ---@class lsp.ShowMessageRequestClientCapabilities --- ---Capabilities specific to the `MessageActionItem` type. ----@field messageActionItem? lsp._anonym43.messageActionItem +---@field messageActionItem? lsp._anonym45.messageActionItem ---Client capabilities for the showDocument request. --- @@ -4671,7 +4684,7 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport ----@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym44.PrepareRenameResult|lsp._anonym45.PrepareRenameResult +---@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym46.PrepareRenameResult|lsp._anonym47.PrepareRenameResult ---A document selector is the combination of one or many document filters. --- @@ -4692,7 +4705,7 @@ error('Cannot require a meta file') ---An event describing a change to a text document. If only a text is provided ---it is considered to be the full content of the document. ----@alias lsp.TextDocumentContentChangeEvent lsp._anonym46.TextDocumentContentChangeEvent|lsp._anonym47.TextDocumentContentChangeEvent +---@alias lsp.TextDocumentContentChangeEvent lsp._anonym48.TextDocumentContentChangeEvent|lsp._anonym49.TextDocumentContentChangeEvent ---MarkedString can be used to render human readable text. It is either a markdown string ---or a code-block that provides a language and a code snippet. The language identifier @@ -4706,7 +4719,7 @@ error('Cannot require a meta file') --- ---Note that markdown strings will be sanitized - that means html will be escaped. ---@deprecated use MarkupContent instead. ----@alias lsp.MarkedString string|lsp._anonym48.MarkedString +---@alias lsp.MarkedString string|lsp._anonym50.MarkedString ---A document filter describes a top level text document or ---a notebook cell document. @@ -4739,14 +4752,14 @@ error('Cannot require a meta file') ---\@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` --- ---@since 3.17.0 ----@alias lsp.TextDocumentFilter lsp._anonym49.TextDocumentFilter|lsp._anonym50.TextDocumentFilter|lsp._anonym51.TextDocumentFilter +---@alias lsp.TextDocumentFilter lsp._anonym51.TextDocumentFilter|lsp._anonym52.TextDocumentFilter|lsp._anonym53.TextDocumentFilter ---A notebook document filter denotes a notebook document by ---different properties. The properties will be match ---against the notebook's URI (same as with documents) --- ---@since 3.17.0 ----@alias lsp.NotebookDocumentFilter lsp._anonym52.NotebookDocumentFilter|lsp._anonym53.NotebookDocumentFilter|lsp._anonym54.NotebookDocumentFilter +---@alias lsp.NotebookDocumentFilter lsp._anonym54.NotebookDocumentFilter|lsp._anonym55.NotebookDocumentFilter|lsp._anonym56.NotebookDocumentFilter ---The glob pattern to watch relative to the base path. Glob patterns can have the following syntax: ---- `*` to match one or more characters in a path segment @@ -4856,7 +4869,19 @@ error('Cannot require a meta file') ---The client's version as defined by the client. ---@field version? string ----@class lsp._anonym12.workspace +---@class lsp._anonym13.textDocument.diagnostic +--- +---Whether the server supports `MarkupContent` in diagnostic messages. +---@field markupMessageSupport? boolean + +---@class lsp._anonym12.textDocument +--- +---Capabilities specific to the diagnostic pull model. +--- +---@since 3.18.0 +---@field diagnostic? lsp._anonym13.textDocument.diagnostic + +---@class lsp._anonym14.workspace --- ---The server supports workspace folder. --- @@ -4868,7 +4893,7 @@ error('Cannot require a meta file') ---@since 3.16.0 ---@field fileOperations? lsp.FileOperationOptions ----@class lsp._anonym13.completionItem +---@class lsp._anonym15.completionItem --- ---The server has support for completion item label ---details (see also `CompletionItemLabelDetails`) when @@ -4877,11 +4902,11 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field labelDetailsSupport? boolean ----@class lsp._anonym15.notebookSelector.cells +---@class lsp._anonym17.notebookSelector.cells --- ---@field language string ----@class lsp._anonym14.notebookSelector +---@class lsp._anonym16.notebookSelector --- ---The notebook to be synced If a string ---value is provided it matches against the @@ -4889,13 +4914,13 @@ error('Cannot require a meta file') ---@field notebook string|lsp.NotebookDocumentFilter --- ---The cells of the matching notebook to be synced. ----@field cells? lsp._anonym15.notebookSelector.cells[] +---@field cells? lsp._anonym17.notebookSelector.cells[] ----@class lsp._anonym17.notebookSelector.cells +---@class lsp._anonym19.notebookSelector.cells --- ---@field language string ----@class lsp._anonym16.notebookSelector +---@class lsp._anonym18.notebookSelector --- ---The notebook to be synced If a string ---value is provided it matches against the @@ -4903,9 +4928,9 @@ error('Cannot require a meta file') ---@field notebook? string|lsp.NotebookDocumentFilter --- ---The cells of the matching notebook to be synced. ----@field cells lsp._anonym17.notebookSelector.cells[] +---@field cells lsp._anonym19.notebookSelector.cells[] ----@class lsp._anonym18.staleRequestSupport +---@class lsp._anonym20.staleRequestSupport --- ---The client will actively cancel the request. ---@field cancel boolean @@ -4915,14 +4940,14 @@ error('Cannot require a meta file') ---response with error code `ContentModified` ---@field retryOnContentModified string[] ----@class lsp._anonym19.changeAnnotationSupport +---@class lsp._anonym21.changeAnnotationSupport --- ---Whether the client groups edits with equal labels into tree nodes, ---for instance all edits labelled with "Changes in Strings" would ---be a tree node. ---@field groupsOnLabel? boolean ----@class lsp._anonym20.symbolKind +---@class lsp._anonym22.symbolKind --- ---The symbol kind values the client supports. When this ---property exists the client also guarantees that it will @@ -4934,32 +4959,32 @@ error('Cannot require a meta file') ---the initial version of the protocol. ---@field valueSet? lsp.SymbolKind[] ----@class lsp._anonym21.tagSupport +---@class lsp._anonym23.tagSupport --- ---The tags supported by the client. ---@field valueSet lsp.SymbolTag[] ----@class lsp._anonym22.resolveSupport +---@class lsp._anonym24.resolveSupport --- ---The properties that a client can resolve lazily. Usually ---`location.range` ---@field properties string[] ----@class lsp._anonym24.completionItem.tagSupport +---@class lsp._anonym26.completionItem.tagSupport --- ---The tags supported by the client. ---@field valueSet lsp.CompletionItemTag[] ----@class lsp._anonym25.completionItem.resolveSupport +---@class lsp._anonym27.completionItem.resolveSupport --- ---The properties that a client can resolve lazily. ---@field properties string[] ----@class lsp._anonym26.completionItem.insertTextModeSupport +---@class lsp._anonym28.completionItem.insertTextModeSupport --- ---@field valueSet lsp.InsertTextMode[] ----@class lsp._anonym23.completionItem +---@class lsp._anonym25.completionItem --- ---Client supports snippets as insert text. --- @@ -4988,7 +5013,7 @@ error('Cannot require a meta file') ---a resolve call. --- ---@since 3.15.0 ----@field tagSupport? lsp._anonym24.completionItem.tagSupport +---@field tagSupport? lsp._anonym26.completionItem.tagSupport --- ---Client support insert replace edit to control different behavior if a ---completion item is inserted in the text or should replace text. @@ -5001,14 +5026,14 @@ error('Cannot require a meta file') ---and `details` could be resolved lazily. --- ---@since 3.16.0 ----@field resolveSupport? lsp._anonym25.completionItem.resolveSupport +---@field resolveSupport? lsp._anonym27.completionItem.resolveSupport --- ---The client supports the `insertTextMode` property on ---a completion item to override the whitespace handling mode ---as defined by the client (see `insertTextMode`). --- ---@since 3.16.0 ----@field insertTextModeSupport? lsp._anonym26.completionItem.insertTextModeSupport +---@field insertTextModeSupport? lsp._anonym28.completionItem.insertTextModeSupport --- ---The client has support for completion item label ---details (see also `CompletionItemLabelDetails`). @@ -5016,7 +5041,7 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field labelDetailsSupport? boolean ----@class lsp._anonym27.completionItemKind +---@class lsp._anonym29.completionItemKind --- ---The completion item kind values the client supports. When this ---property exists the client also guarantees that it will @@ -5028,7 +5053,7 @@ error('Cannot require a meta file') ---the initial version of the protocol. ---@field valueSet? lsp.CompletionItemKind[] ----@class lsp._anonym28.completionList +---@class lsp._anonym30.completionList --- ---The client supports the following itemDefaults on ---a completion list. @@ -5040,7 +5065,7 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field itemDefaults? string[] ----@class lsp._anonym30.signatureInformation.parameterInformation +---@class lsp._anonym32.signatureInformation.parameterInformation --- ---The client supports processing label offsets instead of a ---simple label string. @@ -5048,14 +5073,14 @@ error('Cannot require a meta file') ---@since 3.14.0 ---@field labelOffsetSupport? boolean ----@class lsp._anonym29.signatureInformation +---@class lsp._anonym31.signatureInformation --- ---Client supports the following content formats for the documentation ---property. The order describes the preferred format of the client. ---@field documentationFormat? lsp.MarkupKind[] --- ---Client capabilities specific to parameter information. ----@field parameterInformation? lsp._anonym30.signatureInformation.parameterInformation +---@field parameterInformation? lsp._anonym32.signatureInformation.parameterInformation --- ---The client supports the `activeParameter` property on `SignatureInformation` ---literal. @@ -5070,7 +5095,7 @@ error('Cannot require a meta file') ---@since 3.18.0 ---@field noActiveParameterSupport? boolean ----@class lsp._anonym31.symbolKind +---@class lsp._anonym33.symbolKind --- ---The symbol kind values the client supports. When this ---property exists the client also guarantees that it will @@ -5082,12 +5107,12 @@ error('Cannot require a meta file') ---the initial version of the protocol. ---@field valueSet? lsp.SymbolKind[] ----@class lsp._anonym32.tagSupport +---@class lsp._anonym34.tagSupport --- ---The tags supported by the client. ---@field valueSet lsp.SymbolTag[] ----@class lsp._anonym34.codeActionLiteralSupport.codeActionKind +---@class lsp._anonym36.codeActionLiteralSupport.codeActionKind --- ---The code action kind values the client supports. When this ---property exists the client also guarantees that it will @@ -5095,18 +5120,18 @@ error('Cannot require a meta file') ---to a default value when unknown. ---@field valueSet lsp.CodeActionKind[] ----@class lsp._anonym33.codeActionLiteralSupport +---@class lsp._anonym35.codeActionLiteralSupport --- ---The code action kind is support with the following value ---set. ----@field codeActionKind lsp._anonym34.codeActionLiteralSupport.codeActionKind +---@field codeActionKind lsp._anonym36.codeActionLiteralSupport.codeActionKind ----@class lsp._anonym35.resolveSupport +---@class lsp._anonym37.resolveSupport --- ---The properties that a client can resolve lazily. ---@field properties string[] ----@class lsp._anonym36.foldingRangeKind +---@class lsp._anonym38.foldingRangeKind --- ---The folding range kind values the client supports. When this ---property exists the client also guarantees that it will @@ -5114,7 +5139,7 @@ error('Cannot require a meta file') ---to a default value when unknown. ---@field valueSet? lsp.FoldingRangeKind[] ----@class lsp._anonym37.foldingRange +---@class lsp._anonym39.foldingRange --- ---If set, the client signals that it supports setting collapsedText on ---folding ranges to display custom labels instead of the default text. @@ -5122,52 +5147,52 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field collapsedText? boolean ----@class lsp._anonym38.tagSupport +---@class lsp._anonym40.tagSupport --- ---The tags supported by the client. ---@field valueSet lsp.DiagnosticTag[] ----@class lsp._anonym40.requests.range +---@class lsp._anonym42.requests.range ----@class lsp._anonym41.requests.full +---@class lsp._anonym43.requests.full --- ---The client will send the `textDocument/semanticTokens/full/delta` request if ---the server provides a corresponding handler. ---@field delta? boolean ----@class lsp._anonym39.requests +---@class lsp._anonym41.requests --- ---The client will send the `textDocument/semanticTokens/range` request if ---the server provides a corresponding handler. ----@field range? boolean|lsp._anonym40.requests.range +---@field range? boolean|lsp._anonym42.requests.range --- ---The client will send the `textDocument/semanticTokens/full` request if ---the server provides a corresponding handler. ----@field full? boolean|lsp._anonym41.requests.full +---@field full? boolean|lsp._anonym43.requests.full ----@class lsp._anonym42.resolveSupport +---@class lsp._anonym44.resolveSupport --- ---The properties that a client can resolve lazily. ---@field properties string[] ----@class lsp._anonym43.messageActionItem +---@class lsp._anonym45.messageActionItem --- ---Whether the client supports additional attributes which ---are preserved and send back to the server in the ---request's response. ---@field additionalPropertiesSupport? boolean ----@class lsp._anonym44.PrepareRenameResult +---@class lsp._anonym46.PrepareRenameResult --- ---@field range lsp.Range --- ---@field placeholder string ----@class lsp._anonym45.PrepareRenameResult +---@class lsp._anonym47.PrepareRenameResult --- ---@field defaultBehavior boolean ----@class lsp._anonym46.TextDocumentContentChangeEvent +---@class lsp._anonym48.TextDocumentContentChangeEvent --- ---The range of the document that changed. ---@field range lsp.Range @@ -5180,18 +5205,18 @@ error('Cannot require a meta file') ---The new text for the provided range. ---@field text string ----@class lsp._anonym47.TextDocumentContentChangeEvent +---@class lsp._anonym49.TextDocumentContentChangeEvent --- ---The new text of the whole document. ---@field text string ----@class lsp._anonym48.MarkedString +---@class lsp._anonym50.MarkedString --- ---@field language string --- ---@field value string ----@class lsp._anonym49.TextDocumentFilter +---@class lsp._anonym51.TextDocumentFilter --- ---A language id, like `typescript`. ---@field language string @@ -5202,7 +5227,7 @@ error('Cannot require a meta file') ---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples. ---@field pattern? string ----@class lsp._anonym50.TextDocumentFilter +---@class lsp._anonym52.TextDocumentFilter --- ---A language id, like `typescript`. ---@field language? string @@ -5213,7 +5238,7 @@ error('Cannot require a meta file') ---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples. ---@field pattern? string ----@class lsp._anonym51.TextDocumentFilter +---@class lsp._anonym53.TextDocumentFilter --- ---A language id, like `typescript`. ---@field language? string @@ -5224,7 +5249,7 @@ error('Cannot require a meta file') ---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples. ---@field pattern string ----@class lsp._anonym52.NotebookDocumentFilter +---@class lsp._anonym54.NotebookDocumentFilter --- ---The type of the enclosing notebook. ---@field notebookType string @@ -5235,7 +5260,7 @@ error('Cannot require a meta file') ---A glob pattern. ---@field pattern? string ----@class lsp._anonym53.NotebookDocumentFilter +---@class lsp._anonym55.NotebookDocumentFilter --- ---The type of the enclosing notebook. ---@field notebookType? string @@ -5246,7 +5271,7 @@ error('Cannot require a meta file') ---A glob pattern. ---@field pattern? string ----@class lsp._anonym54.NotebookDocumentFilter +---@class lsp._anonym56.NotebookDocumentFilter --- ---The type of the enclosing notebook. ---@field notebookType? string -- cgit From 6818ba271cb43b1430f019b832d7e26671e0f5f4 Mon Sep 17 00:00:00 2001 From: Maria José Solano Date: Tue, 14 May 2024 07:08:13 -0700 Subject: fix(health): clients may not support watchfiles #28710 --- runtime/lua/vim/lsp/health.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 797a1097f9..a79ae76eb9 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -51,6 +51,29 @@ end local function check_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() + if + --- @param client vim.lsp.Client + vim.iter(clients):all(function(client) + local has_capability = vim.tbl_get( + client.capabilities, + 'workspace', + 'didChangeWatchedFiles', + 'dynamicRegistration' + ) + local has_dynamic_capability = + client.dynamic_capabilities:get(vim.lsp.protocol.Methods.workspace_didChangeWatchedFiles) + return has_capability == nil + or has_dynamic_capability == nil + or client.workspace_folders == nil + end) + then + report_info('file watching "(workspace/didChangeWatchedFiles)" disabled on all clients') + return + end + local watchfunc = vim.lsp._watchfiles._watchfunc assert(watchfunc) local watchfunc_name --- @type string -- cgit From 6a264e08974bcb1b91f891eb65ef374f350d2827 Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Tue, 14 May 2024 07:14:43 -0700 Subject: fix(treesitter): allow optional directive captures (#28664) --- runtime/lua/vim/treesitter/query.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e68acac929..36c78b7f1d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -529,6 +529,9 @@ local directive_handlers = { ['offset!'] = function(match, _, _, pred, metadata) local capture_id = pred[2] --[[@as integer]] local nodes = match[capture_id] + if not nodes or #nodes == 0 then + return + end assert(#nodes == 1, '#offset! does not support captures on multiple nodes') local node = nodes[1] @@ -562,6 +565,9 @@ local directive_handlers = { assert(type(id) == 'number') local nodes = match[id] + if not nodes or #nodes == 0 then + return + end assert(#nodes == 1, '#gsub! does not support captures on multiple nodes') local node = nodes[1] local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' @@ -584,6 +590,9 @@ local directive_handlers = { assert(type(capture_id) == 'number') local nodes = match[capture_id] + if not nodes or #nodes == 0 then + return + end assert(#nodes == 1, '#trim! does not support captures on multiple nodes') local node = nodes[1] -- cgit From 5eee633c97055fc8c7617f2914835f2860b92d9c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 14 May 2024 19:38:22 +0200 Subject: fix(lsp): don't start additional client if attach failed (#28744) If a client for a server was already running and lsp.start was called in an unloaded buffer it started another client instead of bailing out. --- runtime/lua/vim/lsp.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 325c30ca38..e2af317823 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -251,6 +251,8 @@ function lsp.start(config, opts) if reuse_client(client, config) then if lsp.buf_attach_client(bufnr, client.id) then return client.id + else + return nil end end end -- cgit From 7acf39ddab8ebdb63ebf78ec980149d20783fd4b Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Wed, 15 May 2024 01:18:33 +0200 Subject: docs: misc (#28609) Closes https://github.com/neovim/neovim/issues/28484. Closes https://github.com/neovim/neovim/issues/28719. Co-authored-by: Chris Co-authored-by: Gregory Anders Co-authored-by: Jake B <16889000+jakethedev@users.noreply.github.com> Co-authored-by: Jonathan Raines Co-authored-by: Yi Ming Co-authored-by: Zane Dufour Co-authored-by: zeertzjq --- runtime/lua/vim/filetype.lua | 2 +- runtime/lua/vim/iter.lua | 3 ++- runtime/lua/vim/lsp.lua | 6 +++--- runtime/lua/vim/lsp/buf.lua | 4 ++-- runtime/lua/vim/lsp/inlay_hint.lua | 5 +++-- runtime/lua/vim/snippet.lua | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d6f680b195..0ae0f9ca92 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2312,7 +2312,6 @@ end --- vim.filetype.add { --- pattern = { --- ['.*'] = { ---- priority = -math.huge, --- function(path, bufnr) --- local content = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1] or '' --- if vim.regex([[^#!.*\\]]):match_str(content) ~= nil then @@ -2321,6 +2320,7 @@ end --- return 'drawing' --- end --- end, +--- { priority = -math.huge }, --- }, --- }, --- } diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 5f1e3b6386..06415773bc 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -466,7 +466,8 @@ end --- -- Get the "maximum" item of an iterable. --- vim.iter({ -99, -4, 3, 42, 0, 0, 7 }) --- :fold({}, function(acc, v) ---- acc.max = math.max(v, acc.max or v) return acc +--- acc.max = math.max(v, acc.max or v) +--- return acc --- end) --> { max = 42 } --- ``` --- diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index e2af317823..8103ed4d21 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -897,12 +897,12 @@ end ---@param bufnr (integer) Buffer handle, or 0 for current. ---@param method (string) LSP method name ---@param params (table|nil) Parameters to send to the server ----@param handler fun(results: table) (function) +---@param handler fun(results: table) (function) --- Handler called after all requests are completed. Server results are passed as --- a `client_id:result` map. ---@return function cancel Function that cancels all requests. function lsp.buf_request_all(bufnr, method, params, handler) - local results = {} --- @type table + local results = {} --- @type table local result_count = 0 local expected_result_count = 0 @@ -940,7 +940,7 @@ end ---@param params table? Parameters to send to the server ---@param timeout_ms integer? Maximum time in milliseconds to wait for a result. --- (default: `1000`) ----@return table? result Map of client_id:request_result. +---@return table? result Map of client_id:request_result. ---@return string? err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil. function lsp.buf_request_sync(bufnr, method, params, timeout_ms) local request_results ---@type table diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index e05acff3df..49833eaeec 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -503,7 +503,7 @@ function M.typehierarchy(kind) --- Merge results from multiple clients into a single table. Client-ID is preserved. --- - --- @param results table + --- @param results table --- @return [integer, lsp.TypeHierarchyItem][] local function merge_results(results) local merged_results = {} @@ -521,7 +521,7 @@ function M.typehierarchy(kind) local bufnr = api.nvim_get_current_buf() local params = util.make_position_params() - --- @param results table + --- @param results table vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results) local merged_results = merge_results(results) if #merged_results == 0 then diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index effc69c67e..8d22859a80 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -98,7 +98,7 @@ function M.on_inlayhint(err, result, ctx, _) api.nvim__redraw({ buf = bufnr, valid = true }) end ---- |lsp-handler| for the method `textDocument/inlayHint/refresh` +--- |lsp-handler| for the method `workspace/inlayHint/refresh` ---@param ctx lsp.HandlerContext ---@private function M.on_refresh(err, _, ctx, _) @@ -368,6 +368,7 @@ api.nvim_set_decoration_provider(namespace, { end, }) +--- Query whether inlay hint is enabled in the {filter}ed scope --- @param filter vim.lsp.inlay_hint.enable.Filter --- @return boolean --- @since 12 @@ -391,7 +392,7 @@ end --- Buffer number, or 0 for current buffer, or nil for all. --- @field bufnr integer? ---- Enables or disables inlay hints for a buffer. +--- Enables or disables inlay hints for the {filter}ed scope. --- --- To "toggle", pass the inverse of `is_enabled()`: --- diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 8fe03b3882..2d95d4203d 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -539,7 +539,7 @@ end --- --- ```lua --- vim.keymap.set({ 'i', 's' }, '', function() ---- if vim.snippet.jumpable(1) then +--- if vim.snippet.active({ direction = 1 }) then --- return 'lua vim.snippet.jump(1)' --- else --- return '' -- cgit From dcdefd042840c7a6db5dce2963ac23c45a5287da Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 14 May 2024 17:29:28 +0100 Subject: perf(loader): use a quicker version of vim.fs.normalize Problem: vim.fs.normalize() normalizes too much vim.loader and is slow. Solution: Make it faster by doing less. This reduces the times spent in vim.fs.normalize in vim.loader from ~13ms -> 1-2ms. Numbers from a relative benchmark: - Skipping `vim.validate()`: 285ms -> 230ms - Skipping `path_resolve_dot()`: 285ms -> 60ms - Skipping `double_slash`: 60ms -> 35ms --- runtime/lua/vim/fs.lua | 37 ++++++++++++++++++++++++++----------- runtime/lua/vim/loader.lua | 2 +- 2 files changed, 27 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index e70175bcbd..58e79db0b4 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -488,6 +488,8 @@ end --- (default: `true`) --- @field expand_env? boolean --- +--- @field package _fast? boolean +--- --- Path is a Windows path. --- (default: `true` in Windows, `false` otherwise) --- @field win? boolean @@ -527,11 +529,13 @@ end function M.normalize(path, opts) opts = opts or {} - vim.validate({ - path = { path, { 'string' } }, - expand_env = { opts.expand_env, { 'boolean' }, true }, - win = { opts.win, { 'boolean' }, true }, - }) + if not opts._fast then + vim.validate({ + path = { path, { 'string' } }, + expand_env = { opts.expand_env, { 'boolean' }, true }, + win = { opts.win, { 'boolean' }, true }, + }) + end local win = opts.win == nil and iswin or not not opts.win local os_sep_local = win and '\\' or '/' @@ -555,11 +559,17 @@ function M.normalize(path, opts) path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) end - -- Convert path separator to `/` - path = path:gsub(os_sep_local, '/') + if win then + -- Convert path separator to `/` + path = path:gsub(os_sep_local, '/') + end -- Check for double slashes at the start of the path because they have special meaning - local double_slash = vim.startswith(path, '//') and not vim.startswith(path, '///') + local double_slash = false + if not opts._fast then + double_slash = vim.startswith(path, '//') and not vim.startswith(path, '///') + end + local prefix = '' if win then @@ -576,10 +586,15 @@ function M.normalize(path, opts) prefix = prefix:gsub('/+', '/') end - -- Resolve `.` and `..` components and remove extraneous slashes from path, then recombine prefix - -- and path. Preserve leading double slashes as they indicate UNC paths and DOS device paths in + if not opts._fast then + -- Resolve `.` and `..` components and remove extraneous slashes from path, then recombine prefix + -- and path. + path = path_resolve_dot(path) + end + + -- Preserve leading double slashes as they indicate UNC paths and DOS device paths in -- Windows and have implementation-defined behavior in POSIX. - path = (double_slash and '/' or '') .. prefix .. path_resolve_dot(path) + path = (double_slash and '/' or '') .. prefix .. path -- Change empty path to `.` if path == '' then diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index d3d8948654..ea77a22416 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -85,7 +85,7 @@ function Loader.get_hash(path) end local function normalize(path) - return fs.normalize(path, { expand_env = false }) + return fs.normalize(path, { expand_env = false, _fast = true }) end --- Gets the rtp excluding after directories. -- cgit From 14a5813c207716613daecf4ca9f69e3a3795596a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 15 May 2024 10:19:16 +0100 Subject: perf(vim.fs.normalize): use iterator ~10% faster. --- runtime/lua/vim/fs.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 58e79db0b4..ce533ad0a9 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -455,11 +455,9 @@ end --- @return string Resolved path. local function path_resolve_dot(path) local is_path_absolute = vim.startswith(path, '/') - -- Split the path into components and process them - local path_components = vim.split(path, '/') local new_path_components = {} - for _, component in ipairs(path_components) do + for component in vim.gsplit(path, '/') do if component == '.' or component == '' then -- luacheck: ignore 542 -- Skip `.` components and empty components elseif component == '..' then -- cgit From cdd87222c86c5b2274a13d36f23de0637462e317 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 15 May 2024 13:13:47 +0100 Subject: perf(lua): avoid spairs in vim.validate happy path Problem: `vim.validate` is too slow, mainly because of `vim.spairs`. Solution: Collect all errors in via `pairs`, and sort the errors via `spairs`. --- runtime/lua/vim/shared.lua | 112 ++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 47 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 4d753d727a..200ac44e86 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -796,6 +796,61 @@ do return type(val) == t or (t == 'callable' and vim.is_callable(val)) end + --- @param param_name string + --- @param spec vim.validate.Spec + --- @return string? + local function is_param_valid(param_name, spec) + if type(spec) ~= 'table' then + return string.format('opt[%s]: expected table, got %s', param_name, type(spec)) + end + + local val = spec[1] -- Argument value + local types = spec[2] -- Type name, or callable + local optional = (true == spec[3]) + + if type(types) == 'string' then + types = { types } + end + + if vim.is_callable(types) then + -- Check user-provided validation function + local valid, optional_message = types(val) + if not valid then + local error_message = + string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val)) + if optional_message ~= nil then + error_message = string.format('%s. Info: %s', error_message, optional_message) + end + + return error_message + end + elseif type(types) == 'table' then + local success = false + for i, t in ipairs(types) do + local t_name = type_names[t] + if not t_name then + return string.format('invalid type name: %s', t) + end + types[i] = t_name + + if (optional and val == nil) or _is_type(val, t_name) then + success = true + break + end + end + if not success then + return string.format( + '%s: expected %s, got %s', + param_name, + table.concat(types, '|'), + type(val) + ) + end + else + return string.format('invalid type name: %s', tostring(types)) + end + end + --- @param opt table --- @return boolean, string? local function is_valid(opt) @@ -803,56 +858,19 @@ do return false, string.format('opt: expected table, got %s', type(opt)) end - for param_name, spec in vim.spairs(opt) do - if type(spec) ~= 'table' then - return false, string.format('opt[%s]: expected table, got %s', param_name, type(spec)) - end - - local val = spec[1] -- Argument value - local types = spec[2] -- Type name, or callable - local optional = (true == spec[3]) + local report --- @type table? - if type(types) == 'string' then - types = { types } + for param_name, spec in pairs(opt) do + local msg = is_param_valid(param_name, spec) + if msg then + report = report or {} + report[param_name] = msg end + end - if vim.is_callable(types) then - -- Check user-provided validation function - local valid, optional_message = types(val) - if not valid then - local error_message = - string.format('%s: expected %s, got %s', param_name, (spec[3] or '?'), tostring(val)) - if optional_message ~= nil then - error_message = error_message .. string.format('. Info: %s', optional_message) - end - - return false, error_message - end - elseif type(types) == 'table' then - local success = false - for i, t in ipairs(types) do - local t_name = type_names[t] - if not t_name then - return false, string.format('invalid type name: %s', t) - end - types[i] = t_name - - if (optional and val == nil) or _is_type(val, t_name) then - success = true - break - end - end - if not success then - return false, - string.format( - '%s: expected %s, got %s', - param_name, - table.concat(types, '|'), - type(val) - ) - end - else - return false, string.format('invalid type name: %s', tostring(types)) + if report then + for _, msg in vim.spairs(report) do -- luacheck: ignore + return false, msg end end -- cgit From 01b6bff7e9bc751be20ce7bb68e7ebe3037441de Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 2 May 2024 15:57:21 +0200 Subject: docs: news Set dev_xx.txt help files to use "flow" layout. --- runtime/lua/vim/_meta/lpeg.lua | 7 +++---- runtime/lua/vim/_meta/re.lua | 8 ++++---- runtime/lua/vim/lsp/inlay_hint.lua | 3 ++- runtime/lua/vim/snippet.lua | 5 ++--- runtime/lua/vim/treesitter/languagetree.lua | 5 ++++- 5 files changed, 15 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua index 1ce40f3340..73b3375c82 100644 --- a/runtime/lua/vim/_meta/lpeg.lua +++ b/runtime/lua/vim/_meta/lpeg.lua @@ -6,11 +6,10 @@ error('Cannot require a meta file') -- with types being renamed to include the vim namespace and with some descriptions made less verbose. --- @brief
help
---- LPeg is a pattern-matching library for Lua, based on
---- Parsing Expression Grammars (https://bford.info/packrat/) (PEGs).
+--- LPeg is a pattern-matching library for Lua, based on Parsing Expression
+--- Grammars (PEGs). https://bford.info/packrat/
 ---
----                                                                     *lua-lpeg*
----                                                             *vim.lpeg.Pattern*
+---                                                  *lua-lpeg* *vim.lpeg.Pattern*
 --- The LPeg library for parsing expression grammars is included as `vim.lpeg`
 --- (https://www.inf.puc-rio.br/~roberto/lpeg/).
 ---
diff --git a/runtime/lua/vim/_meta/re.lua b/runtime/lua/vim/_meta/re.lua
index 9721e6f8c8..d16751fbbf 100644
--- a/runtime/lua/vim/_meta/re.lua
+++ b/runtime/lua/vim/_meta/re.lua
@@ -8,11 +8,11 @@ error('Cannot require a meta file')
 -- See 'lpeg.html' for license
 
 --- @brief
---- The `vim.re` module provides a conventional regex-like syntax for pattern usage
---- within LPeg |vim.lpeg|.
+--- The `vim.re` module provides a conventional regex-like syntax for pattern usage within LPeg
+--- |vim.lpeg|. (Unrelated to |vim.regex| which provides Vim |regexp| from Lua.)
 ---
---- See https://www.inf.puc-rio.br/~roberto/lpeg/re.html for the original
---- documentation including regex syntax and more concrete examples.
+--- See https://www.inf.puc-rio.br/~roberto/lpeg/re.html for the original documentation including
+--- regex syntax and examples.
 
 --- Compiles the given {string} and returns an equivalent LPeg pattern. The given string may define
 --- either an expression or a grammar. The optional {defs} table provides extra Lua values to be used
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index 8d22859a80..f98496456b 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -136,7 +136,8 @@ end
 --- local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
 ---
 --- local client = vim.lsp.get_client_by_id(hint.client_id)
---- resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
+--- local resp = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0)
+--- local resolved_hint = assert(resp and resp.result, resp.err)
 --- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
 ---
 --- location = resolved_hint.label[1].location
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
index 2d95d4203d..3d8f73f362 100644
--- a/runtime/lua/vim/snippet.lua
+++ b/runtime/lua/vim/snippet.lua
@@ -532,10 +532,9 @@ end
 
 --- @alias vim.snippet.Direction -1 | 1
 
---- Jumps within the active snippet in the given direction.
---- If the jump isn't possible, the function call does nothing.
+--- Jumps to the next (or previous) placeholder in the current snippet, if possible.
 ---
---- You can use this function to navigate a snippet as follows:
+--- For example, map `` to jump while a snippet is active:
 ---
 --- ```lua
 --- vim.keymap.set({ 'i', 's' }, '', function()
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index e618f29f8f..270d869e43 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -225,7 +225,10 @@ function LanguageTree:_log(...)
   self._logger('nvim', table.concat(msg, ' '))
 end
 
---- Invalidates this parser and all its children
+--- Invalidates this parser and its children.
+---
+--- Should only be called when the tracked state of the LanguageTree is not valid against the parse
+--- tree in treesitter. Doesn't clear filesystem cache. Called often, so needs to be fast.
 ---@param reload boolean|nil
 function LanguageTree:invalidate(reload)
   self._valid = false
-- 
cgit 


From 618e34ca095739935ac436fec58bb2a223ea3dc1 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Thu, 16 May 2024 14:29:56 +0800
Subject: vim-patch:5faeb60480c6 (#28768)

runtime(doc): clarify {special} argument for shellescape()

closes: vim/vim#14770

https://github.com/vim/vim/commit/5faeb60480c6efba5c0468c01275120b6ace5a09

N/A patch:
vim-patch:c0e038b59f84

Co-authored-by: Enno 
---
 runtime/lua/vim/_meta/vimfn.lua | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index dc25b0dd40..853f354275 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -8293,10 +8293,11 @@ function vim.fn.sha256(string) end
 --- Otherwise encloses {string} in single-quotes and replaces all
 --- "'" with "'\''".
 ---
---- If {special} is a |non-zero-arg|:
---- - Special items such as "!", "%", "#" and "" will be
----   preceded by a backslash. The backslash will be removed again
----   by the |:!| command.
+--- The {special} argument adds additional escaping of keywords
+--- used in Vim commands. If it is a |non-zero-arg|:
+--- - Special items such as "!", "%", "#" and "" (as listed
+---   in |expand()|) will be preceded by a backslash.
+---   The backslash will be removed again by the |:!| command.
 --- - The  character is escaped.
 ---
 --- If 'shell' contains "csh" in the tail:
-- 
cgit 


From b5c3687b6ddae8952510bdbbfa8be577de7edf05 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Thu, 16 May 2024 11:37:46 +0200
Subject: docs: misc (#28761)

Co-authored-by: Florian Zeitz 
---
 runtime/lua/vim/diagnostic.lua | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 67ce331891..1992a4000d 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -1985,15 +1985,9 @@ function M.setloclist(opts)
   set_list(true, opts)
 end
 
---- @deprecated use `vim.diagnostic.enabled(…, false)`
+--- @deprecated use `vim.diagnostic.enable(false, …)`
 function M.disable(bufnr, namespace)
-  vim.deprecate(
-    'vim.diagnostic.disable()',
-    'vim.diagnostic.enabled(false, …)',
-    '0.12',
-    nil,
-    false
-  )
+  vim.deprecate('vim.diagnostic.disable()', 'vim.diagnostic.enable(false, …)', '0.12', nil, false)
   M.enable(false, { bufnr = bufnr, ns_id = namespace })
 end
 
-- 
cgit 


From 4b029163345333a2c6975cd0dace6613b036ae47 Mon Sep 17 00:00:00 2001
From: vanaigr 
Date: Thu, 16 May 2024 09:57:58 -0500
Subject: perf(treesitter): use child_containing_descendant() in has-ancestor?
 (#28512)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Problem: `has-ancestor?` is O(n²) for the depth of the tree since it iterates over each of the node's ancestors (bottom-up), and each ancestor takes O(n) time.
This happens because tree-sitter's nodes don't store their parent nodes, and the tree is searched (top-down) each time a new parent is requested.

Solution: Make use of new `ts_node_child_containing_descendant()` in tree-sitter v0.22.6 (which is now the minimum required version) to rewrite the `has-ancestor?` predicate in C to become O(n).

For a sample file, decreases the time taken by `has-ancestor?` from 360ms to 6ms.
---
 runtime/lua/vim/treesitter/_meta.lua |  1 +
 runtime/lua/vim/treesitter/query.lua | 13 ++-----------
 2 files changed, 3 insertions(+), 11 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 34a51e42f6..177699a207 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -20,6 +20,7 @@ error('Cannot require a meta file')
 ---@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
 ---@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
 ---@field parent fun(self: TSNode): TSNode?
+---@field child_containing_descendant fun(self: TSNode, descendant: TSNode): TSNode?
 ---@field next_sibling fun(self: TSNode): TSNode?
 ---@field prev_sibling fun(self: TSNode): TSNode?
 ---@field next_named_sibling fun(self: TSNode): TSNode?
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 36c78b7f1d..ef5c2143a7 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -457,17 +457,8 @@ local predicate_handlers = {
     end
 
     for _, node in ipairs(nodes) do
-      local ancestor_types = {} --- @type table
-      for _, type in ipairs({ unpack(predicate, 3) }) do
-        ancestor_types[type] = true
-      end
-
-      local cur = node:parent()
-      while cur do
-        if ancestor_types[cur:type()] then
-          return true
-        end
-        cur = cur:parent()
+      if node:__has_ancestor(predicate) then
+        return true
       end
     end
     return false
-- 
cgit 


From a664246171569209698c0b17b1d7af831f6603d2 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Thu, 16 May 2024 15:22:46 +0200
Subject: feat: remove deprecated features

Remove following functions:
- vim.lsp.util.extract_completion_items
- vim.lsp.util.get_progress_messages
- vim.lsp.util.parse_snippet()
- vim.lsp.util.text_document_completion_list_to_complete_items
- LanguageTree:for_each_child
- health#report_error
- health#report_info
- health#report_ok
- health#report_start
- health#report_warn
- vim.health.report_error
- vim.health.report_info
- vim.health.report_ok
- vim.health.report_start
- vim.health.report_warn
---
 runtime/lua/vim/health.lua                  |  47 ----------
 runtime/lua/vim/lsp/handlers.lua            |   3 +-
 runtime/lua/vim/lsp/util.lua                | 127 ----------------------------
 runtime/lua/vim/treesitter/languagetree.lua |  18 ----
 4 files changed, 2 insertions(+), 193 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index d72a32b541..69a53485a4 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -185,53 +185,6 @@ function M.error(msg, ...)
   collect_output(input)
 end
 
---- @param type string
-local function deprecate(type)
-  local before = string.format('vim.health.report_%s()', type)
-  local after = string.format('vim.health.%s()', type)
-  local message = vim.deprecate(before, after, '0.11')
-  if message then
-    M.warn(message)
-  end
-  vim.cmd.redraw()
-  vim.print('Running healthchecks...')
-end
-
---- @deprecated
---- @param name string
-function M.report_start(name)
-  deprecate('start')
-  M.start(name)
-end
-
---- @deprecated
---- @param msg string
-function M.report_info(msg)
-  deprecate('info')
-  M.info(msg)
-end
-
---- @deprecated
---- @param msg string
-function M.report_ok(msg)
-  deprecate('ok')
-  M.ok(msg)
-end
-
---- @deprecated
---- @param msg string
-function M.report_warn(msg, ...)
-  deprecate('warn')
-  M.warn(msg, ...)
-end
-
---- @deprecated
---- @param msg string
-function M.report_error(msg, ...)
-  deprecate('error')
-  M.error(msg, ...)
-end
-
 function M.provider_disabled(provider)
   local loaded_var = 'loaded_' .. provider .. '_provider'
   local v = vim.g[loaded_var]
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 7018b9f61b..f9d394642c 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -3,6 +3,7 @@ local protocol = require('vim.lsp.protocol')
 local ms = protocol.Methods
 local util = require('vim.lsp.util')
 local api = vim.api
+local completion = require('vim.lsp._completion')
 
 --- @type table
 local M = {}
@@ -353,7 +354,7 @@ M[ms.textDocument_completion] = function(_, result, _, _)
   local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
   local prefix = line_to_cursor:sub(textMatch + 1)
 
-  local matches = util.text_document_completion_list_to_complete_items(result, prefix)
+  local matches = completion._lsp_to_complete_items(result, prefix)
   vim.fn.complete(textMatch + 1, matches)
 end
 
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index fc99f54d03..5a229a1169 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,5 +1,4 @@
 local protocol = require('vim.lsp.protocol')
-local snippet = require('vim.lsp._snippet_grammar')
 local validate = vim.validate
 local api = vim.api
 local list_extend = vim.list_extend
@@ -343,68 +342,6 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding)
   return col
 end
 
---- Process and return progress reports from lsp server
----@private
----@deprecated Use vim.lsp.status() or access client.progress directly
-function M.get_progress_messages()
-  vim.deprecate('vim.lsp.util.get_progress_messages()', 'vim.lsp.status()', '0.11')
-  local new_messages = {}
-  local progress_remove = {}
-
-  for _, client in ipairs(vim.lsp.get_clients()) do
-    local groups = {}
-    for progress in client.progress do
-      local value = progress.value
-      if type(value) == 'table' and value.kind then
-        local group = groups[progress.token]
-        if not group then
-          group = {
-            done = false,
-            progress = true,
-            title = 'empty title',
-          }
-          groups[progress.token] = group
-        end
-        group.title = value.title or group.title
-        group.cancellable = value.cancellable or group.cancellable
-        if value.kind == 'end' then
-          group.done = true
-        end
-        group.message = value.message or group.message
-        group.percentage = value.percentage or group.percentage
-      end
-    end
-
-    for _, group in pairs(groups) do
-      table.insert(new_messages, group)
-    end
-
-    local messages = client.messages
-    local data = messages
-    for token, ctx in pairs(data.progress) do
-      local new_report = {
-        name = data.name,
-        title = ctx.title or 'empty title',
-        message = ctx.message,
-        percentage = ctx.percentage,
-        done = ctx.done,
-        progress = true,
-      }
-      table.insert(new_messages, new_report)
-
-      if ctx.done then
-        table.insert(progress_remove, { client = client, token = token })
-      end
-    end
-  end
-
-  for _, item in ipairs(progress_remove) do
-    item.client.messages.progress[item.token] = nil
-  end
-
-  return new_messages
-end
-
 --- Applies a list of text edits to a buffer.
 ---@param text_edits table list of `TextEdit` objects
 ---@param bufnr integer Buffer id
@@ -541,38 +478,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
   end
 end
 
--- local valid_windows_path_characters = "[^<>:\"/\\|?*]"
--- local valid_unix_path_characters = "[^/]"
--- https://github.com/davidm/lua-glob-pattern
--- https://stackoverflow.com/questions/1976007/what-characters-are-forbidden-in-windows-and-linux-directory-names
--- function M.glob_to_regex(glob)
--- end
-
---- Can be used to extract the completion items from a
---- `textDocument/completion` request, which may return one of
---- `CompletionItem[]`, `CompletionList` or null.
----
---- Note that this method doesn't apply `itemDefaults` to `CompletionList`s, and hence the returned
---- results might be incorrect.
----
----@deprecated
----@param result table The result of a `textDocument/completion` request
----@return lsp.CompletionItem[] List of completion items
----@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
-function M.extract_completion_items(result)
-  vim.deprecate('vim.lsp.util.extract_completion_items()', nil, '0.11')
-  if type(result) == 'table' and result.items then
-    -- result is a `CompletionList`
-    return result.items
-  elseif result ~= nil then
-    -- result is `CompletionItem[]`
-    return result
-  else
-    -- result is `null`
-    return {}
-  end
-end
-
 --- Applies a `TextDocumentEdit`, which is a list of changes to a single
 --- document.
 ---
@@ -615,38 +520,6 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
   M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding)
 end
 
---- Parses snippets in a completion entry.
----
----@deprecated
----@param input string unparsed snippet
----@return string parsed snippet
-function M.parse_snippet(input)
-  vim.deprecate('vim.lsp.util.parse_snippet()', nil, '0.11')
-  local ok, parsed = pcall(function()
-    return snippet.parse(input)
-  end)
-  if not ok then
-    return input
-  end
-
-  return tostring(parsed)
-end
-
---- Turns the result of a `textDocument/completion` request into vim-compatible
---- |complete-items|.
----
----@deprecated
----@param result table The result of a `textDocument/completion` call, e.g.
---- from |vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`,
---- `CompletionList` or `null`
----@param prefix (string) the prefix to filter the completion items
----@return table[] items
----@see complete-items
-function M.text_document_completion_list_to_complete_items(result, prefix)
-  vim.deprecate('vim.lsp.util.text_document_completion_list_to_complete_items()', nil, '0.11')
-  return vim.lsp._completion._lsp_to_complete_items(result, prefix)
-end
-
 local function path_components(path)
   return vim.split(path, '/', { plain = true })
 end
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 270d869e43..b0812123b9 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -463,24 +463,6 @@ function LanguageTree:parse(range)
   return self._trees
 end
 
----@deprecated Misleading name. Use `LanguageTree:children()` (non-recursive) instead,
----            add recursion yourself if needed.
---- Invokes the callback for each |LanguageTree| and its children recursively
----
----@param fn fun(tree: vim.treesitter.LanguageTree, lang: string)
----@param include_self? boolean Whether to include the invoking tree in the results
-function LanguageTree:for_each_child(fn, include_self)
-  vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11')
-  if include_self then
-    fn(self, self._lang)
-  end
-
-  for _, child in pairs(self._children) do
-    --- @diagnostic disable-next-line:deprecated
-    child:for_each_child(fn, true)
-  end
-end
-
 --- Invokes the callback for each |LanguageTree| recursively.
 ---
 --- Note: This includes the invoking tree's child trees as well.
-- 
cgit 


From 50749f8df89d7a74ea17d51b28e737e043ac6c51 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Thu, 16 May 2024 17:42:13 +0100
Subject: fix: extend the life of vim.tbl_flatten to 0.13

`vim.iter(t):flatten():totable()` doesn't handle nil so isn't a good
enough replacement.
---
 runtime/lua/vim/shared.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 200ac44e86..e9e4326057 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -553,7 +553,7 @@ end
 ---@param t table List-like table
 ---@return table Flattened copy of the given list-like table
 function vim.tbl_flatten(t)
-  vim.deprecate('vim.tbl_flatten', 'vim.iter(…):flatten():totable()', '0.12')
+  vim.deprecate('vim.tbl_flatten', 'vim.iter(…):flatten():totable()', '0.13')
   local result = {}
   --- @param _t table
   local function _tbl_flatten(_t)
-- 
cgit 


From 4c0d18c19773327dcd771d1da7805690e3e41255 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Fri, 17 May 2024 14:17:25 -0500
Subject: fix(vim.iter): enable optimizations for arrays (lists with holes)
 (#28781)

The optimizations that vim.iter uses for array-like tables don't require
that the source table has no holes. The only thing that needs to change
is the determination if a table is "list-like": rather than requiring
consecutive, integer keys, we can simply test for (positive) integer
keys only, and remove any holes in the original array when we make a
copy for the iterator.
---
 runtime/lua/vim/iter.lua | 121 +++++++++++++++++++++++------------------------
 1 file changed, 59 insertions(+), 62 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 06415773bc..1093759efe 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -7,6 +7,7 @@
 --- `vim.iter()`:
 ---
 --- - List tables (arrays, |lua-list|) yield only the value of each element.
+---   - Holes (nil values) are allowed.
 ---   - Use |Iter:enumerate()| to also pass the index to the next stage.
 ---   - Or initialize with ipairs(): `vim.iter(ipairs(…))`.
 --- - Non-list tables (|lua-dict|) yield both the key and value of each element.
@@ -80,13 +81,13 @@ end
 
 --- Special case implementations for iterators on list tables.
 ---@nodoc
----@class ListIter : Iter
+---@class ArrayIter : Iter
 ---@field _table table Underlying table data
 ---@field _head number Index to the front of a table iterator
 ---@field _tail number Index to the end of a table iterator (exclusive)
-local ListIter = {}
-ListIter.__index = setmetatable(ListIter, Iter)
-ListIter.__call = function(self)
+local ArrayIter = {}
+ArrayIter.__index = setmetatable(ArrayIter, Iter)
+ArrayIter.__call = function(self)
   return self:next()
 end
 
@@ -110,36 +111,34 @@ end
 
 local function sanitize(t)
   if type(t) == 'table' and getmetatable(t) == packedmt then
-    -- Remove length tag
+    -- Remove length tag and metatable
     t.n = nil
+    setmetatable(t, nil)
   end
   return t
 end
 
---- Flattens a single list-like table. Errors if it attempts to flatten a
+--- Flattens a single array-like table. Errors if it attempts to flatten a
 --- dict-like table
----@param v table table which should be flattened
+---@param t table table which should be flattened
 ---@param max_depth number depth to which the table should be flattened
 ---@param depth number current iteration depth
 ---@param result table output table that contains flattened result
 ---@return table|nil flattened table if it can be flattened, otherwise nil
-local function flatten(v, max_depth, depth, result)
-  if depth < max_depth and type(v) == 'table' then
-    local i = 0
-    for _ in pairs(v) do
-      i = i + 1
-
-      if v[i] == nil then
+local function flatten(t, max_depth, depth, result)
+  if depth < max_depth and type(t) == 'table' then
+    for k, v in pairs(t) do
+      if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
         -- short-circuit: this is not a list like table
         return nil
       end
 
-      if flatten(v[i], max_depth, depth + 1, result) == nil then
+      if flatten(v, max_depth, depth + 1, result) == nil then
         return nil
       end
     end
-  else
-    result[#result + 1] = v
+  elseif t ~= nil then
+    result[#result + 1] = t
   end
 
   return result
@@ -198,7 +197,7 @@ function Iter:filter(f)
 end
 
 ---@private
-function ListIter:filter(f)
+function ArrayIter:filter(f)
   local inc = self._head < self._tail and 1 or -1
   local n = self._head
   for i = self._head, self._tail - inc, inc do
@@ -233,11 +232,11 @@ end
 ---@return Iter
 ---@diagnostic disable-next-line:unused-local
 function Iter:flatten(depth) -- luacheck: no unused args
-  error('flatten() requires a list-like table')
+  error('flatten() requires an array-like table')
 end
 
 ---@private
-function ListIter:flatten(depth)
+function ArrayIter:flatten(depth)
   depth = depth or 1
   local inc = self._head < self._tail and 1 or -1
   local target = {}
@@ -247,7 +246,7 @@ function ListIter:flatten(depth)
 
     -- exit early if we try to flatten a dict-like table
     if flattened == nil then
-      error('flatten() requires a list-like table')
+      error('flatten() requires an array-like table')
     end
 
     for _, v in pairs(flattened) do
@@ -327,7 +326,7 @@ function Iter:map(f)
 end
 
 ---@private
-function ListIter:map(f)
+function ArrayIter:map(f)
   local inc = self._head < self._tail and 1 or -1
   local n = self._head
   for i = self._head, self._tail - inc, inc do
@@ -360,7 +359,7 @@ function Iter:each(f)
 end
 
 ---@private
-function ListIter:each(f)
+function ArrayIter:each(f)
   local inc = self._head < self._tail and 1 or -1
   for i = self._head, self._tail - inc, inc do
     f(unpack(self._table[i]))
@@ -371,7 +370,7 @@ end
 --- Collect the iterator into a table.
 ---
 --- The resulting table depends on the initial source in the iterator pipeline.
---- List-like tables and function iterators will be collected into a list-like
+--- Array-like tables and function iterators will be collected into an array-like
 --- table. If multiple values are returned from the final stage in the iterator
 --- pipeline, each value will be included in a table.
 ---
@@ -388,7 +387,7 @@ end
 --- -- { { 'a', 1 }, { 'c', 3 } }
 --- ```
 ---
---- The generated table is a list-like table with consecutive, numeric indices.
+--- The generated table is an array-like table with consecutive, numeric indices.
 --- To create a map-like table with arbitrary keys, use |Iter:fold()|.
 ---
 ---
@@ -408,12 +407,12 @@ function Iter:totable()
 end
 
 ---@private
-function ListIter:totable()
-  if self.next ~= ListIter.next or self._head >= self._tail then
+function ArrayIter:totable()
+  if self.next ~= ArrayIter.next or self._head >= self._tail then
     return Iter.totable(self)
   end
 
-  local needs_sanitize = getmetatable(self._table[1]) == packedmt
+  local needs_sanitize = getmetatable(self._table[self._head]) == packedmt
 
   -- Reindex and sanitize.
   local len = self._tail - self._head
@@ -493,7 +492,7 @@ function Iter:fold(init, f)
 end
 
 ---@private
-function ListIter:fold(init, f)
+function ArrayIter:fold(init, f)
   local acc = init
   local inc = self._head < self._tail and 1 or -1
   for i = self._head, self._tail - inc, inc do
@@ -525,7 +524,7 @@ function Iter:next()
 end
 
 ---@private
-function ListIter:next()
+function ArrayIter:next()
   if self._head ~= self._tail then
     local v = self._table[self._head]
     local inc = self._head < self._tail and 1 or -1
@@ -548,11 +547,11 @@ end
 ---
 ---@return Iter
 function Iter:rev()
-  error('rev() requires a list-like table')
+  error('rev() requires an array-like table')
 end
 
 ---@private
-function ListIter:rev()
+function ArrayIter:rev()
   local inc = self._head < self._tail and 1 or -1
   self._head, self._tail = self._tail - inc, self._head - inc
   return self
@@ -576,11 +575,11 @@ end
 ---
 ---@return any
 function Iter:peek()
-  error('peek() requires a list-like table')
+  error('peek() requires an array-like table')
 end
 
 ---@private
-function ListIter:peek()
+function ArrayIter:peek()
   if self._head ~= self._tail then
     return self._table[self._head]
   end
@@ -657,11 +656,11 @@ end
 ---@return any
 ---@diagnostic disable-next-line: unused-local
 function Iter:rfind(f) -- luacheck: no unused args
-  error('rfind() requires a list-like table')
+  error('rfind() requires an array-like table')
 end
 
 ---@private
-function ListIter:rfind(f)
+function ArrayIter:rfind(f)
   if type(f) ~= 'function' then
     local val = f
     f = function(v)
@@ -709,10 +708,10 @@ function Iter:take(n)
 end
 
 ---@private
-function ListIter:take(n)
-  local inc = self._head < self._tail and 1 or -1
+function ArrayIter:take(n)
+  local inc = self._head < self._tail and n or -n
   local cmp = self._head < self._tail and math.min or math.max
-  self._tail = cmp(self._tail, self._head + n * inc)
+  self._tail = cmp(self._tail, self._head + inc)
   return self
 end
 
@@ -730,11 +729,11 @@ end
 ---
 ---@return any
 function Iter:pop()
-  error('pop() requires a list-like table')
+  error('pop() requires an array-like table')
 end
 
 --- @nodoc
-function ListIter:pop()
+function ArrayIter:pop()
   if self._head ~= self._tail then
     local inc = self._head < self._tail and 1 or -1
     self._tail = self._tail - inc
@@ -760,11 +759,11 @@ end
 ---
 ---@return any
 function Iter:rpeek()
-  error('rpeek() requires a list-like table')
+  error('rpeek() requires an array-like table')
 end
 
 ---@nodoc
-function ListIter:rpeek()
+function ArrayIter:rpeek()
   if self._head ~= self._tail then
     local inc = self._head < self._tail and 1 or -1
     return self._table[self._tail - inc]
@@ -793,7 +792,7 @@ function Iter:skip(n)
 end
 
 ---@private
-function ListIter:skip(n)
+function ArrayIter:skip(n)
   local inc = self._head < self._tail and n or -n
   self._head = self._head + inc
   if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
@@ -818,11 +817,11 @@ end
 ---@return Iter
 ---@diagnostic disable-next-line: unused-local
 function Iter:rskip(n) -- luacheck: no unused args
-  error('rskip() requires a list-like table')
+  error('rskip() requires an array-like table')
 end
 
 ---@private
-function ListIter:rskip(n)
+function ArrayIter:rskip(n)
   local inc = self._head < self._tail and n or -n
   self._tail = self._tail - inc
   if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
@@ -870,11 +869,11 @@ end
 ---@return Iter
 ---@diagnostic disable-next-line: unused-local
 function Iter:slice(first, last) -- luacheck: no unused args
-  error('slice() requires a list-like table')
+  error('slice() requires an array-like table')
 end
 
 ---@private
-function ListIter:slice(first, last)
+function ArrayIter:slice(first, last)
   return self:skip(math.max(0, first - 1)):rskip(math.max(0, self._tail - last - 1))
 end
 
@@ -955,7 +954,7 @@ function Iter:last()
 end
 
 ---@private
-function ListIter:last()
+function ArrayIter:last()
   local inc = self._head < self._tail and 1 or -1
   local v = self._table[self._tail - inc]
   self._head = self._tail
@@ -1000,7 +999,7 @@ function Iter:enumerate()
 end
 
 ---@private
-function ListIter:enumerate()
+function ArrayIter:enumerate()
   local inc = self._head < self._tail and 1 or -1
   for i = self._head, self._tail - inc, inc do
     local v = self._table[i]
@@ -1030,17 +1029,14 @@ function Iter.new(src, ...)
 
     local t = {}
 
-    -- O(n): scan the source table to decide if it is a list (consecutive integer indices 1…n).
-    local count = 0
-    for _ in pairs(src) do
-      count = count + 1
-      local v = src[count]
-      if v == nil then
+    -- O(n): scan the source table to decide if it is an array (only positive integer indices).
+    for k, v in pairs(src) do
+      if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then
         return Iter.new(pairs(src))
       end
-      t[count] = v
+      t[#t + 1] = v
     end
-    return ListIter.new(t)
+    return ArrayIter.new(t)
   end
 
   if type(src) == 'function' then
@@ -1068,17 +1064,18 @@ function Iter.new(src, ...)
   return it
 end
 
---- Create a new ListIter
+--- Create a new ArrayIter
 ---
----@param t table List-like table. Caller guarantees that this table is a valid list.
+---@param t table Array-like table. Caller guarantees that this table is a valid array. Can have
+---               holes (nil values).
 ---@return Iter
 ---@private
-function ListIter.new(t)
+function ArrayIter.new(t)
   local it = {}
   it._table = t
   it._head = 1
   it._tail = #t + 1
-  setmetatable(it, ListIter)
+  setmetatable(it, ArrayIter)
   return it
 end
 
-- 
cgit 


From 0f4f7d32ce5d6d3b751b0b01455770f3b72531b9 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Sat, 18 May 2024 18:35:26 +0200
Subject: refactor!: remove `nvim` and `provider` module for checkhealth

The namespacing for healthchecks for neovim modules is inconsistent and
confusing. The completion for `:checkhealth` with `--clean` gives

```
nvim
provider.clipboard
provider.node
provider.perl
provider.python
provider.ruby
vim.lsp
vim.treesitter
```

There are now three top-level module names for nvim: `nvim`, `provider`
and `vim` with no signs of stopping. The `nvim` name is especially
confusing as it does not contain all neovim checkhealths, which makes it
almost a decoy healthcheck.

The confusion only worsens if you add plugins to the mix:

```
lazy
mason
nvim
nvim-treesitter
provider.clipboard
provider.node
provider.perl
provider.python
provider.ruby
telescope
vim.lsp
vim.treesitter
```

Another problem with the current approach is that it's not easy to run
nvim-only healthchecks since they don't share the same namespace. The
current approach would be to run `:che nvim vim.* provider.*` and would
also require the user to know these are the neovim modules.

Instead, use this alternative structure:

```
vim.health
vim.lsp
vim.provider.clipboard
vim.provider.node
vim.provider.perl
vim.provider.python
vim.provider.ruby
vim.treesitter
```

and

```
lazy
mason
nvim-treesitter
telescope
vim.health
vim.lsp
vim.provider.clipboard
vim.provider.node
vim.provider.perl
vim.provider.python
vim.provider.ruby
vim.treesitter
```

Now, the entries are properly sorted and running nvim-only healthchecks
requires running only `:che vim.*`.
---
 runtime/lua/vim/health/health.lua             | 409 +++++++++++++++++++++
 runtime/lua/vim/provider/clipboard/health.lua |  39 ++
 runtime/lua/vim/provider/node/health.lua      | 109 ++++++
 runtime/lua/vim/provider/perl/health.lua      |  90 +++++
 runtime/lua/vim/provider/python/health.lua    | 499 ++++++++++++++++++++++++++
 runtime/lua/vim/provider/ruby/health.lua      |  69 ++++
 6 files changed, 1215 insertions(+)
 create mode 100644 runtime/lua/vim/health/health.lua
 create mode 100644 runtime/lua/vim/provider/clipboard/health.lua
 create mode 100644 runtime/lua/vim/provider/node/health.lua
 create mode 100644 runtime/lua/vim/provider/perl/health.lua
 create mode 100644 runtime/lua/vim/provider/python/health.lua
 create mode 100644 runtime/lua/vim/provider/ruby/health.lua

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua
new file mode 100644
index 0000000000..5bc03199ee
--- /dev/null
+++ b/runtime/lua/vim/health/health.lua
@@ -0,0 +1,409 @@
+local M = {}
+local health = require('vim.health')
+
+local shell_error = function()
+  return vim.v.shell_error ~= 0
+end
+
+local suggest_faq = 'https://github.com/neovim/neovim/blob/master/BUILD.md#building'
+
+local function check_runtime()
+  health.start('Runtime')
+  -- Files from an old installation.
+  local bad_files = {
+    ['plugin/health.vim'] = false,
+    ['autoload/health/nvim.vim'] = false,
+    ['autoload/health/provider.vim'] = false,
+    ['autoload/man.vim'] = false,
+    ['plugin/man.vim'] = false,
+    ['queries/help/highlights.scm'] = false,
+    ['queries/help/injections.scm'] = false,
+    ['scripts.vim'] = false,
+    ['syntax/syncolor.vim'] = false,
+  }
+  local bad_files_msg = ''
+  for k, _ in pairs(bad_files) do
+    local path = ('%s/%s'):format(vim.env.VIMRUNTIME, k)
+    if vim.uv.fs_stat(path) then
+      bad_files[k] = true
+      bad_files_msg = ('%s%s\n'):format(bad_files_msg, path)
+    end
+  end
+
+  local ok = (bad_files_msg == '')
+  local info = ok and health.ok or health.info
+  info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME))
+  if not ok then
+    health.error(
+      string.format(
+        'Found old files in $VIMRUNTIME (this can cause weird behavior):\n%s',
+        bad_files_msg
+      ),
+      { 'Delete the $VIMRUNTIME directory (or uninstall Nvim), then reinstall Nvim.' }
+    )
+  end
+end
+
+local function check_config()
+  health.start('Configuration')
+  local ok = true
+
+  local init_lua = vim.fn.stdpath('config') .. '/init.lua'
+  local init_vim = vim.fn.stdpath('config') .. '/init.vim'
+  local vimrc = vim.env.MYVIMRC and vim.fn.expand(vim.env.MYVIMRC) or init_lua
+
+  if vim.fn.filereadable(vimrc) == 0 and vim.fn.filereadable(init_vim) == 0 then
+    ok = false
+    local has_vim = vim.fn.filereadable(vim.fn.expand('~/.vimrc')) == 1
+    health.warn(
+      ('%s user config file: %s'):format(
+        -1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable',
+        vimrc
+      ),
+      { has_vim and ':help nvim-from-vim' or ':help config' }
+    )
+  end
+
+  -- If $VIM is empty we don't care. Else make sure it is valid.
+  if vim.env.VIM and vim.fn.filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') == 0 then
+    ok = false
+    health.error('$VIM is invalid: ' .. vim.env.VIM)
+  end
+
+  if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then
+    ok = false
+    health.warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', {
+      "Use the 'guicursor' option to configure cursor shape. :help 'guicursor'",
+      'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402',
+    })
+  end
+
+  if vim.v.ctype == 'C' then
+    ok = false
+    health.error(
+      'Locale does not support UTF-8. Unicode characters may not display correctly.'
+        .. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format(
+          vim.env.LANG,
+          vim.env.LC_ALL,
+          vim.env.LC_CTYPE
+        ),
+      {
+        'If using tmux, try the -u option.',
+        'Ensure that your terminal/shell/tmux/etc inherits the environment, or set $LANG explicitly.',
+        'Configure your system locale.',
+      }
+    )
+  end
+
+  if vim.o.paste == 1 then
+    ok = false
+    health.error(
+      "'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.",
+      {
+        'Remove `set paste` from your init.vim, if applicable.',
+        'Check `:verbose set paste?` to see if a plugin or script set the option.',
+      }
+    )
+  end
+
+  local writeable = true
+  local shadaopt = vim.fn.split(vim.o.shada, ',')
+  local shadafile = (
+    vim.o.shada == '' and vim.o.shada
+    or vim.fn.substitute(vim.fn.matchstr(shadaopt[#shadaopt], '^n.\\+'), '^n', '', '')
+  )
+  shadafile = (
+    vim.o.shadafile == ''
+      and (shadafile == '' and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fn.expand(
+        shadafile
+      ))
+    or (vim.o.shadafile == 'NONE' and '' or vim.o.shadafile)
+  )
+  if shadafile ~= '' and vim.fn.glob(shadafile) == '' then
+    -- Since this may be the first time Nvim has been run, try to create a shada file.
+    if not pcall(vim.cmd.wshada) then
+      writeable = false
+    end
+  end
+  if
+    not writeable
+    or (
+      shadafile ~= ''
+      and (vim.fn.filereadable(shadafile) == 0 or vim.fn.filewritable(shadafile) ~= 1)
+    )
+  then
+    ok = false
+    health.error(
+      'shada file is not '
+        .. ((not writeable or vim.fn.filereadable(shadafile) == 1) and 'writeable' or 'readable')
+        .. ':\n'
+        .. shadafile
+    )
+  end
+
+  if ok then
+    health.ok('no issues found')
+  end
+end
+
+local function check_performance()
+  health.start('Performance')
+
+  -- Check buildtype
+  local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]])
+  if buildtype == '' then
+    health.error('failed to get build type from :version')
+  elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then
+    health.ok(buildtype)
+  else
+    health.info(buildtype)
+    health.warn('Non-optimized debug build. Nvim will be slower.', {
+      'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.',
+      suggest_faq,
+    })
+  end
+
+  -- check for slow shell invocation
+  local slow_cmd_time = 1.5
+  local start_time = vim.fn.reltime()
+  vim.fn.system('echo')
+  local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time))
+  if elapsed_time > slow_cmd_time then
+    health.warn(
+      'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).'
+    )
+  end
+end
+
+-- Load the remote plugin manifest file and check for unregistered plugins
+local function check_rplugin_manifest()
+  health.start('Remote Plugins')
+
+  local existing_rplugins = {}
+  for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do
+    existing_rplugins[item.path] = 'python3'
+  end
+
+  local require_update = false
+  local handle_path = function(path)
+    local python_glob = vim.fn.glob(path .. '/rplugin/python*', true, true)
+    if vim.tbl_isempty(python_glob) then
+      return
+    end
+
+    local python_dir = python_glob[1]
+    local python_version = vim.fs.basename(python_dir)
+
+    local scripts = vim.fn.glob(python_dir .. '/*.py', true, true)
+    vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true))
+
+    for _, script in ipairs(scripts) do
+      local contents = vim.fn.join(vim.fn.readfile(script))
+      if vim.regex([[\<\%(from\|import\)\s\+neovim\>]]):match_str(contents) then
+        if vim.regex([[[\/]__init__\.py$]]):match_str(script) then
+          script = vim.fn.tr(vim.fn.fnamemodify(script, ':h'), '\\', '/')
+        end
+        if not existing_rplugins[script] then
+          local msg = vim.fn.printf('"%s" is not registered.', vim.fs.basename(path))
+          if python_version == 'pythonx' then
+            if vim.fn.has('python3') == 0 then
+              msg = msg .. ' (python3 not available)'
+            end
+          elseif vim.fn.has(python_version) == 0 then
+            msg = msg .. vim.fn.printf(' (%s not available)', python_version)
+          else
+            require_update = true
+          end
+
+          health.warn(msg)
+        end
+
+        break
+      end
+    end
+  end
+
+  for _, path in ipairs(vim.fn.map(vim.split(vim.o.runtimepath, ','), 'resolve(v:val)')) do
+    handle_path(path)
+  end
+
+  if require_update then
+    health.warn('Out of date', { 'Run `:UpdateRemotePlugins`' })
+  else
+    health.ok('Up to date')
+  end
+end
+
+local function check_tmux()
+  if not vim.env.TMUX or vim.fn.executable('tmux') == 0 then
+    return
+  end
+
+  local get_tmux_option = function(option)
+    local cmd = 'tmux show-option -qvg ' .. option -- try global scope
+    local out = vim.fn.system(vim.fn.split(cmd))
+    local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
+    if shell_error() then
+      health.error('command failed: ' .. cmd .. '\n' .. out)
+      return 'error'
+    elseif val == '' then
+      cmd = 'tmux show-option -qvgs ' .. option -- try session scope
+      out = vim.fn.system(vim.fn.split(cmd))
+      val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
+      if shell_error() then
+        health.error('command failed: ' .. cmd .. '\n' .. out)
+        return 'error'
+      end
+    end
+    return val
+  end
+
+  health.start('tmux')
+
+  -- check escape-time
+  local suggestions =
+    { 'set escape-time in ~/.tmux.conf:\nset-option -sg escape-time 10', suggest_faq }
+  local tmux_esc_time = get_tmux_option('escape-time')
+  if tmux_esc_time ~= 'error' then
+    if tmux_esc_time == '' then
+      health.error('`escape-time` is not set', suggestions)
+    elseif tonumber(tmux_esc_time) > 300 then
+      health.error('`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', suggestions)
+    else
+      health.ok('escape-time: ' .. tmux_esc_time)
+    end
+  end
+
+  -- check focus-events
+  local tmux_focus_events = get_tmux_option('focus-events')
+  if tmux_focus_events ~= 'error' then
+    if tmux_focus_events == '' or tmux_focus_events ~= 'on' then
+      health.warn(
+        "`focus-events` is not enabled. |'autoread'| may not work.",
+        { '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' }
+      )
+    else
+      health.ok('focus-events: ' .. tmux_focus_events)
+    end
+  end
+
+  -- check default-terminal and $TERM
+  health.info('$TERM: ' .. vim.env.TERM)
+  local cmd = 'tmux show-option -qvg default-terminal'
+  local out = vim.fn.system(vim.fn.split(cmd))
+  local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
+  if tmux_default_term == '' then
+    cmd = 'tmux show-option -qvgs default-terminal'
+    out = vim.fn.system(vim.fn.split(cmd))
+    tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
+  end
+
+  if shell_error() then
+    health.error('command failed: ' .. cmd .. '\n' .. out)
+  elseif tmux_default_term ~= vim.env.TERM then
+    health.info('default-terminal: ' .. tmux_default_term)
+    health.error(
+      '$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.',
+      { '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' }
+    )
+  elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then
+    health.error(
+      '$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.',
+      {
+        'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"',
+        suggest_faq,
+      }
+    )
+  end
+
+  -- check for RGB capabilities
+  local info = vim.fn.system({ 'tmux', 'show-messages', '-T' })
+  local has_setrgbb = vim.fn.stridx(info, ' setrgbb: (string)') ~= -1
+  local has_setrgbf = vim.fn.stridx(info, ' setrgbf: (string)') ~= -1
+  if not has_setrgbb or not has_setrgbf then
+    health.warn(
+      "True color support could not be detected. |'termguicolors'| won't work properly.",
+      {
+        "Add the following to your tmux configuration file, replacing XXX by the value of $TERM outside of tmux:\nset-option -a terminal-features 'XXX:RGB'",
+        "For older tmux versions use this instead:\nset-option -a terminal-overrides 'XXX:Tc'",
+      }
+    )
+  end
+end
+
+local function check_terminal()
+  if vim.fn.executable('infocmp') == 0 then
+    return
+  end
+
+  health.start('terminal')
+  local cmd = 'infocmp -L'
+  local out = vim.fn.system(vim.fn.split(cmd))
+  local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*')
+  local kdch1_entry = vim.fn.matchstr(out, 'key_dc=[^,[:space:]]*')
+
+  if
+    shell_error()
+    and (
+      vim.fn.has('win32') == 0
+      or vim.fn.matchstr(
+          out,
+          [[infocmp: couldn't open terminfo file .\+\%(conemu\|vtpcon\|win32con\)]]
+        )
+        == ''
+    )
+  then
+    health.error('command failed: ' .. cmd .. '\n' .. out)
+  else
+    health.info(
+      vim.fn.printf(
+        'key_backspace (kbs) terminfo entry: `%s`',
+        (kbs_entry == '' and '? (not found)' or kbs_entry)
+      )
+    )
+
+    health.info(
+      vim.fn.printf(
+        'key_dc (kdch1) terminfo entry: `%s`',
+        (kbs_entry == '' and '? (not found)' or kdch1_entry)
+      )
+    )
+  end
+
+  for _, env_var in ipairs({
+    'XTERM_VERSION',
+    'VTE_VERSION',
+    'TERM_PROGRAM',
+    'COLORTERM',
+    'SSH_TTY',
+  }) do
+    if vim.env[env_var] then
+      health.info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var]))
+    end
+  end
+end
+
+local function check_external_tools()
+  health.start('External Tools')
+
+  if vim.fn.executable('rg') == 1 then
+    local rg = vim.fn.exepath('rg')
+    local cmd = 'rg -V'
+    local out = vim.fn.system(vim.fn.split(cmd))
+    health.ok(('%s (%s)'):format(vim.trim(out), rg))
+  else
+    health.warn('ripgrep not available')
+  end
+end
+
+function M.check()
+  check_config()
+  check_runtime()
+  check_performance()
+  check_rplugin_manifest()
+  check_terminal()
+  check_tmux()
+  check_external_tools()
+end
+
+return M
diff --git a/runtime/lua/vim/provider/clipboard/health.lua b/runtime/lua/vim/provider/clipboard/health.lua
new file mode 100644
index 0000000000..e44f7d32cc
--- /dev/null
+++ b/runtime/lua/vim/provider/clipboard/health.lua
@@ -0,0 +1,39 @@
+local health = vim.health
+
+local M = {}
+
+function M.check()
+  health.start('Clipboard (optional)')
+
+  if
+    os.getenv('TMUX')
+    and vim.fn.executable('tmux') == 1
+    and vim.fn.executable('pbpaste') == 1
+    and not health.cmd_ok('pbpaste')
+  then
+    local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+')
+    local advice = {
+      'Install tmux 2.6+.  https://superuser.com/q/231130',
+      'or use tmux with reattach-to-user-namespace.  https://superuser.com/a/413233',
+    }
+    health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice)
+  end
+
+  local clipboard_tool = vim.fn['provider#clipboard#Executable']()
+  if vim.g.clipboard ~= nil and clipboard_tool == '' then
+    local error_message = vim.fn['provider#clipboard#Error']()
+    health.error(
+      error_message,
+      "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."
+    )
+  elseif clipboard_tool:find('^%s*$') then
+    health.warn(
+      'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.',
+      ':help clipboard'
+    )
+  else
+    health.ok('Clipboard tool found: ' .. clipboard_tool)
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/provider/node/health.lua b/runtime/lua/vim/provider/node/health.lua
new file mode 100644
index 0000000000..471c625388
--- /dev/null
+++ b/runtime/lua/vim/provider/node/health.lua
@@ -0,0 +1,109 @@
+local health = vim.health
+local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+
+local M = {}
+
+function M.check()
+  health.start('Node.js provider (optional)')
+
+  if health.provider_disabled('node') then
+    return
+  end
+
+  if
+    vim.fn.executable('node') == 0
+    or (
+      vim.fn.executable('npm') == 0
+      and vim.fn.executable('yarn') == 0
+      and vim.fn.executable('pnpm') == 0
+    )
+  then
+    health.warn(
+      '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.',
+      'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.'
+    )
+    return
+  end
+
+  -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or ''
+  local ok, node_v = health.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)
+    -- Skip further checks, they are nonsense if nodejs is too old.
+    return
+  end
+  if vim.fn['provider#node#can_inspect']() == 0 then
+    health.warn(
+      'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.'
+    )
+  end
+
+  local node_detect_table = vim.fn['provider#node#Detect']()
+  local host = node_detect_table[1]
+  if host:find('^%s*$') then
+    health.warn('Missing "neovim" npm (or yarn, pnpm) package.', {
+      'Run in shell: npm install -g neovim',
+      'Run in shell (if you use yarn): yarn global add neovim',
+      'Run in shell (if you use pnpm): pnpm install -g neovim',
+      'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim',
+    })
+    return
+  end
+  health.info('Nvim node.js host: ' .. host)
+
+  local manager = 'npm'
+  if vim.fn.executable('yarn') == 1 then
+    manager = 'yarn'
+  elseif vim.fn.executable('pnpm') == 1 then
+    manager = 'pnpm'
+  end
+
+  local latest_npm_cmd = (
+    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, ' '))
+  if not ok or latest_npm:find('^%s$') then
+    health.error(
+      'Failed to run: ' .. latest_npm_cmd,
+      { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+    )
+    return
+  end
+
+  local pcall_ok, pkg_data = pcall(vim.json.decode, latest_npm)
+  if not pcall_ok then
+    return 'error: ' .. latest_npm
+  end
+  local latest_npm_subtable = pkg_data['dist-tags'] or {}
+  latest_npm = latest_npm_subtable['latest'] or 'unable to parse'
+
+  local current_npm_cmd = { 'node', host, '--version' }
+  local current_npm
+  ok, current_npm = health.cmd_ok(current_npm_cmd)
+  if not ok then
+    health.error(
+      'Failed to run: ' .. table.concat(current_npm_cmd, ' '),
+      { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') }
+    )
+    return
+  end
+
+  if latest_npm ~= 'unable to parse' and vim.version.lt(current_npm, latest_npm) then
+    local message = 'Package "neovim" is out-of-date. Installed: '
+      .. current_npm:gsub('%\n$', '')
+      .. ', latest: '
+      .. latest_npm:gsub('%\n$', '')
+
+    health.warn(message, {
+      'Run in shell: npm install -g neovim',
+      'Run in shell (if you use yarn): yarn global add neovim',
+      'Run in shell (if you use pnpm): pnpm install -g neovim',
+    })
+  else
+    health.ok('Latest "neovim" npm/yarn/pnpm package is installed: ' .. current_npm)
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/provider/perl/health.lua b/runtime/lua/vim/provider/perl/health.lua
new file mode 100644
index 0000000000..535093d793
--- /dev/null
+++ b/runtime/lua/vim/provider/perl/health.lua
@@ -0,0 +1,90 @@
+local health = vim.health
+
+local M = {}
+
+function M.check()
+  health.start('Perl provider (optional)')
+
+  if health.provider_disabled('perl') then
+    return
+  end
+
+  local perl_exec, perl_warnings = vim.provider.perl.detect()
+
+  if not perl_exec then
+    health.warn(assert(perl_warnings), {
+      'See :help provider-perl for more information.',
+      'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim',
+    })
+    health.warn('No usable perl executable found')
+    return
+  end
+
+  health.info('perl executable: ' .. perl_exec)
+
+  -- 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', '' })
+  if not ok then
+    return { perl_exec, '"App::cpanminus" module is not installed' }
+  end
+
+  local latest_cpan_cmd = {
+    perl_exec,
+    '-MApp::cpanminus::fatscript',
+    '-e',
+    '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)
+  if not ok or latest_cpan:find('^%s*$') then
+    health.error(
+      'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '),
+      { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+    )
+    return
+  elseif latest_cpan[1] == '!' then
+    local cpanm_errs = vim.split(latest_cpan, '!')
+    if cpanm_errs[1]:find("Can't write to ") then
+      local advice = {}
+      for i = 2, #cpanm_errs do
+        advice[#advice + 1] = cpanm_errs[i]
+      end
+
+      health.warn(cpanm_errs[1], advice)
+      -- Last line is the package info
+      latest_cpan = cpanm_errs[#cpanm_errs]
+    else
+      health.error('Unknown warning from command: ' .. latest_cpan_cmd, cpanm_errs)
+      return
+    end
+  end
+  latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]])
+  if latest_cpan:find('^%s*$') then
+    health.error('Cannot parse version number from cpanm output: ' .. latest_cpan)
+    return
+  end
+
+  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)
+  if not ok then
+    health.error(
+      'Failed to run: ' .. table.concat(current_cpan_cmd, ' '),
+      { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') }
+    )
+    return
+  end
+
+  if vim.version.lt(current_cpan, latest_cpan) then
+    local message = 'Module "Neovim::Ext" is out-of-date. Installed: '
+      .. current_cpan
+      .. ', latest: '
+      .. latest_cpan
+    health.warn(message, 'Run in shell: cpanm -n Neovim::Ext')
+  else
+    health.ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan)
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/provider/python/health.lua b/runtime/lua/vim/provider/python/health.lua
new file mode 100644
index 0000000000..a5bd738063
--- /dev/null
+++ b/runtime/lua/vim/provider/python/health.lua
@@ -0,0 +1,499 @@
+local health = vim.health
+local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+
+local M = {}
+
+local function is(path, ty)
+  if not path then
+    return false
+  end
+  local stat = vim.loop.fs_stat(path)
+  if not stat then
+    return false
+  end
+  return stat.type == ty
+end
+
+-- Resolves Python executable path by invoking and checking `sys.executable`.
+local function python_exepath(invocation)
+  local p = vim.system({ invocation, '-c', 'import sys; sys.stdout.write(sys.executable)' }):wait()
+  assert(p.code == 0, p.stderr)
+  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.
+local function check_for_pyenv()
+  local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv'))
+
+  if pyenv_path == '' then
+    return { '', '' }
+  end
+
+  health.info('pyenv: Path: ' .. pyenv_path)
+
+  local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '')
+
+  if pyenv_root == '' then
+    pyenv_root = vim.fn.system({ pyenv_path, 'root' })
+    health.info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.')
+  end
+
+  if not is(pyenv_root, 'directory') then
+    local message = string.format(
+      'pyenv: Root does not exist: %s. Ignoring pyenv for all following checks.',
+      pyenv_root
+    )
+    health.warn(message)
+    return { '', '' }
+  end
+
+  health.info('pyenv: Root: ' .. pyenv_root)
+
+  return { pyenv_path, pyenv_root }
+end
+
+-- Check the Python interpreter's usability.
+local function check_bin(bin)
+  if not is(bin, 'file') and (not iswin or not is(bin .. '.exe', 'file')) then
+    health.error('"' .. bin .. '" was not found.')
+    return false
+  elseif vim.fn.executable(bin) == 0 then
+    health.error('"' .. bin .. '" is not executable.')
+    return false
+  end
+  return true
+end
+
+-- Fetch the contents of a URL.
+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 })
+    if rc ~= 0 then
+      return 'curl error with ' .. url .. ': ' .. rc
+    else
+      return out
+    end
+  elseif vim.fn.executable('python') == 1 then
+    local script = "try:\n\
+          from urllib.request import urlopen\n\
+          except ImportError:\n\
+          from urllib2 import urlopen\n\
+          response = urlopen('" .. url .. "')\n\
+          print(response.read().decode('utf8'))\n"
+    local out, rc = health.system({ 'python', '-c', script })
+    if out == '' and rc ~= 0 then
+      return 'python urllib.request error: ' .. rc
+    else
+      return out
+    end
+  end
+
+  local message = 'missing `curl` '
+
+  if has_curl then
+    message = message .. '(with HTTPS support) '
+  end
+  message = message .. 'and `python`, cannot make web request'
+
+  return message
+end
+
+-- 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
+      return 'error: ' .. pypi_response
+    end
+
+    local pypi_element = pypi_data['info'] or {}
+    pypi_version = pypi_element['version'] or 'unable to parse'
+  end
+  return pypi_version
+end
+
+local function is_bad_response(s)
+  local lower = s:lower()
+  return vim.startswith(lower, 'unable')
+    or vim.startswith(lower, 'error')
+    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}
+-- }
+local function version_info(python)
+  local pypi_version = latest_pypi_version()
+
+  local python_version, rc = health.system({
+    python,
+    '-c',
+    'import sys; print(".".join(str(x) for x in sys.version_info[:3]))',
+  })
+
+  if rc ~= 0 or python_version == '' then
+    python_version = 'unable to parse ' .. python .. ' response'
+  end
+
+  local nvim_path
+  nvim_path, rc = health.system({
+    python,
+    '-c',
+    'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)',
+  })
+  if rc ~= 0 or nvim_path == '' then
+    return { python_version, 'unable to load neovim Python module', pypi_version, nvim_path }
+  end
+
+  -- Assuming that multiple versions of a package are installed, sort them
+  -- numerically in descending order.
+  local function compare(metapath1, metapath2)
+    local a = vim.fn.matchstr(vim.fn.fnamemodify(metapath1, ':p:h:t'), [[[0-9.]\+]])
+    local b = vim.fn.matchstr(vim.fn.fnamemodify(metapath2, ':p:h:t'), [[[0-9.]\+]])
+    if a == b then
+      return 0
+    elseif a > b then
+      return 1
+    else
+      return -1
+    end
+  end
+
+  -- Try to get neovim.VERSION (added in 0.1.11dev).
+  local nvim_version
+  nvim_version, rc = health.system({
+    python,
+    '-c',
+    'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))',
+  }, { stderr = true, ignore_error = true })
+  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))
+    metas = table.sort(metas, compare)
+
+    if metas and next(metas) ~= nil then
+      for line in io.lines(metas[1]) do
+        local version = line:match('^Version: (%S+)')
+        if version then
+          nvim_version = version
+          break
+        end
+      end
+    end
+  end
+
+  local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]])
+  local version_status = 'unknown; ' .. nvim_path_base
+  if is_bad_response(nvim_version) and is_bad_response(pypi_version) then
+    if vim.version.lt(nvim_version, pypi_version) then
+      version_status = 'outdated; from ' .. nvim_path_base
+    else
+      version_status = 'up to date'
+    end
+  end
+
+  return { python_version, nvim_version, pypi_version, version_status }
+end
+
+function M.check()
+  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 = {}
+
+  if health.provider_disabled(pyname) then
+    return
+  end
+
+  local pyenv_table = check_for_pyenv()
+  local pyenv = pyenv_table[1]
+  local pyenv_root = pyenv_table[2]
+
+  if vim.g[host_prog_var] then
+    local message = string.format('Using: g:%s = "%s"', host_prog_var, vim.g[host_prog_var])
+    health.info(message)
+  end
+
+  local pythonx_warnings
+  pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim')
+
+  if not pyname then
+    health.warn(
+      'No Python executable found that can `import neovim`. '
+        .. 'Using the first available executable for diagnostics.'
+    )
+  elseif vim.g[host_prog_var] then
+    python_exe = pyname
+  end
+
+  -- No Python executable could `import neovim`, or host_prog_var was used.
+  if pythonx_warnings then
+    health.warn(pythonx_warnings, {
+      'See :help provider-python for more information.',
+      'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim',
+    })
+  elseif pyname and pyname ~= '' and python_exe == '' then
+    if not vim.g[host_prog_var] then
+      local message = string.format(
+        '`g:%s` is not set. Searching for %s in the environment.',
+        host_prog_var,
+        pyname
+      )
+      health.info(message)
+    end
+
+    if pyenv ~= '' then
+      python_exe = health.system({ pyenv, 'which', pyname }, { stderr = true })
+      if python_exe == '' then
+        health.warn('pyenv could not find ' .. pyname .. '.')
+      end
+    end
+
+    if python_exe == '' then
+      python_exe = vim.fn.exepath(pyname)
+
+      if os.getenv('PATH') then
+        local path_sep = iswin and ';' or ':'
+        local paths = vim.split(os.getenv('PATH') or '', path_sep)
+
+        for _, path in ipairs(paths) do
+          local path_bin = vim.fs.normalize(path .. '/' .. pyname)
+          if
+            path_bin ~= vim.fs.normalize(python_exe)
+            and vim.tbl_contains(python_multiple, path_bin)
+            and vim.fn.executable(path_bin) == 1
+          then
+            python_multiple[#python_multiple + 1] = path_bin
+          end
+        end
+
+        if vim.tbl_count(python_multiple) > 0 then
+          -- This is worth noting since the user may install something
+          -- that changes $PATH, like homebrew.
+          local message = string.format(
+            'Multiple %s executables found. Set `g:%s` to avoid surprises.',
+            pyname,
+            host_prog_var
+          )
+          health.info(message)
+        end
+
+        if python_exe:find('shims') then
+          local message = string.format('`%s` appears to be a pyenv shim.', python_exe)
+          local advice = string.format(
+            '`pyenv` is not in $PATH, your pyenv installation is broken. Set `g:%s` to avoid surprises.',
+            host_prog_var
+          )
+          health.warn(message, advice)
+        end
+      end
+    end
+  end
+
+  if python_exe ~= '' and not vim.g[host_prog_var] then
+    if
+      venv == ''
+      and pyenv ~= ''
+      and pyenv_root ~= ''
+      and vim.startswith(vim.fn.resolve(python_exe), pyenv_root .. '/')
+    then
+      local advice = string.format(
+        'Create a virtualenv specifically for Nvim using pyenv, and set `g:%s`.  This will avoid the need to install the pynvim module in each version/virtualenv.',
+        host_prog_var
+      )
+      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
+
+      if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then
+        local advice = string.format(
+          'Create a virtualenv specifically for Nvim and use `g:%s`.  This will avoid the need to install the pynvim module in each virtualenv.',
+          host_prog_var
+        )
+        health.warn('Your virtualenv is not set up optimally.', advice)
+      end
+    end
+  end
+
+  if pyname and python_exe == '' and pyname ~= '' then
+    -- An error message should have already printed.
+    health.error('`' .. pyname .. '` was not found.')
+  elseif python_exe ~= '' and not check_bin(python_exe) then
+    python_exe = ''
+  end
+
+  -- Diagnostic output
+  health.info('Executable: ' .. (python_exe == '' and 'Not found' or python_exe))
+  if vim.tbl_count(python_multiple) > 0 then
+    for _, path_bin in ipairs(python_multiple) do
+      health.info('Other python executable: ' .. path_bin)
+    end
+  end
+
+  if python_exe == '' then
+    -- No Python executable can import 'neovim'. Check if any Python executable
+    -- can import 'pynvim'. If so, that Python failed to import 'neovim' as
+    -- well, which is most probably due to a failed pip upgrade:
+    -- https://github.com/neovim/neovim/wiki/Following-HEAD#20181118
+    local pynvim_exe = vim.provider.python.detect_by_module('pynvim')
+    if pynvim_exe then
+      local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": '
+        .. pynvim_exe
+      local advice = {
+        'Use that Python version to reinstall "pynvim" and optionally "neovim".',
+        pynvim_exe .. ' -m pip uninstall pynvim neovim',
+        pynvim_exe .. ' -m pip install pynvim',
+        pynvim_exe .. ' -m pip install neovim  # only if needed by third-party software',
+      }
+      health.error(message, advice)
+    end
+  else
+    local version_info_table = version_info(python_exe)
+    local pyversion = version_info_table[1]
+    local current = version_info_table[2]
+    local latest = version_info_table[3]
+    local status = version_info_table[4]
+
+    if not vim.version.range('~3'):has(pyversion) then
+      health.warn('Unexpected Python version. This could lead to confusing error messages.')
+    end
+
+    health.info('Python version: ' .. pyversion)
+
+    if is_bad_response(status) then
+      health.info('pynvim version: ' .. current .. ' (' .. status .. ')')
+    else
+      health.info('pynvim version: ' .. current)
+    end
+
+    if is_bad_response(current) then
+      health.error(
+        'pynvim is not installed.\nError: ' .. current,
+        'Run in shell: ' .. python_exe .. ' -m pip install pynvim'
+      )
+    end
+
+    if is_bad_response(latest) then
+      health.warn('Could not contact PyPI to get latest version.')
+      health.error('HTTP request failed: ' .. latest)
+    elseif is_bad_response(status) then
+      health.warn('Latest pynvim is NOT installed: ' .. latest)
+    elseif not is_bad_response(current) then
+      health.ok('Latest pynvim is installed.')
+    end
+  end
+
+  health.start('Python virtualenv')
+  if not virtual_env then
+    health.ok('no $VIRTUAL_ENV')
+    return
+  end
+  local errors = {}
+  -- Keep hints as dict keys in order to discard duplicates.
+  local hints = {}
+  -- 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.
+  local bin_dir = iswin and 'Scripts' or 'bin'
+  local venv_bins = vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true)
+  venv_bins = vim.tbl_filter(function(v)
+    -- XXX: Remove irrelevant executables found in bin/.
+    return not v:match('python%-config')
+  end, venv_bins)
+  if vim.tbl_count(venv_bins) > 0 then
+    for _, venv_bin in pairs(venv_bins) do
+      venv_bin = vim.fs.normalize(venv_bin)
+      local py_bin_basename = vim.fs.basename(venv_bin)
+      local nvim_py_bin = python_exepath(vim.fn.exepath(py_bin_basename))
+      local subshell_py_bin = python_exepath(py_bin_basename)
+      if venv_bin ~= nvim_py_bin then
+        errors[#errors + 1] = '$PATH yields this '
+          .. py_bin_basename
+          .. ' executable: '
+          .. nvim_py_bin
+        local hint = '$PATH ambiguities arise if the virtualenv is not '
+          .. 'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, '
+          .. 'check that invoking Python from the command line launches the correct one, '
+          .. 'then relaunch Nvim.'
+        hints[hint] = true
+      end
+      if venv_bin ~= subshell_py_bin then
+        errors[#errors + 1] = '$PATH in subshells yields this '
+          .. py_bin_basename
+          .. ' executable: '
+          .. subshell_py_bin
+        local hint = '$PATH ambiguities in subshells typically are '
+          .. 'caused by your shell config overriding the $PATH previously set by the '
+          .. 'virtualenv. Either prevent them from doing so, or use this workaround: '
+          .. 'https://vi.stackexchange.com/a/34996'
+        hints[hint] = true
+      end
+    end
+  else
+    errors[#errors + 1] = 'no Python executables found in the virtualenv '
+      .. bin_dir
+      .. ' directory.'
+  end
+
+  local msg = '$VIRTUAL_ENV is set to: ' .. virtual_env
+  if vim.tbl_count(errors) > 0 then
+    if vim.tbl_count(venv_bins) > 0 then
+      msg = string.format(
+        '%s\nAnd its %s directory contains: %s',
+        msg,
+        bin_dir,
+        table.concat(
+          vim.tbl_map(function(v)
+            return vim.fs.basename(v)
+          end, venv_bins),
+          ', '
+        )
+      )
+    end
+    local conj = '\nBut '
+    for _, err in ipairs(errors) do
+      msg = msg .. conj .. err
+      conj = '\nAnd '
+    end
+    msg = msg .. '\nSo invoking Python may lead to unexpected results.'
+    health.warn(msg, vim.tbl_keys(hints))
+  else
+    health.info(msg)
+    health.info(
+      'Python version: '
+        .. health.system(
+          'python -c "import platform, sys; sys.stdout.write(platform.python_version())"'
+        )
+    )
+    health.ok('$VIRTUAL_ENV provides :!python.')
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/provider/ruby/health.lua b/runtime/lua/vim/provider/ruby/health.lua
new file mode 100644
index 0000000000..31c2fe3201
--- /dev/null
+++ b/runtime/lua/vim/provider/ruby/health.lua
@@ -0,0 +1,69 @@
+local health = vim.health
+local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+
+local M = {}
+
+function M.check()
+  health.start('Ruby provider (optional)')
+
+  if health.provider_disabled('ruby') then
+    return
+  end
+
+  if vim.fn.executable('ruby') == 0 or vim.fn.executable('gem') == 0 then
+    health.warn(
+      '`ruby` and `gem` must be in $PATH.',
+      'Install Ruby and verify that `ruby` and `gem` commands work.'
+    )
+    return
+  end
+  health.info('Ruby: ' .. health.system({ 'ruby', '-v' }))
+
+  local host, _ = vim.provider.ruby.detect()
+  if (not host) or host:find('^%s*$') then
+    health.warn('`neovim-ruby-host` not found.', {
+      'Run `gem install neovim` to ensure the neovim RubyGem is installed.',
+      'Run `gem environment` to ensure the gem bin directory is in $PATH.',
+      'If you are using rvm/rbenv/chruby, try "rehashing".',
+      'See :help g:ruby_host_prog for non-standard gem installations.',
+      'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim',
+    })
+    return
+  end
+  health.info('Host: ' .. host)
+
+  local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$')
+  local ok, latest_gem = health.cmd_ok(vim.split(latest_gem_cmd, ' '))
+  if not ok or latest_gem:find('^%s*$') then
+    health.error(
+      'Failed to run: ' .. latest_gem_cmd,
+      { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+    )
+    return
+  end
+  local gem_split = vim.split(latest_gem, [[neovim (\|, \|)$]])
+  latest_gem = gem_split[1] or 'not found'
+
+  local current_gem_cmd = { host, '--version' }
+  local current_gem
+  ok, current_gem = health.cmd_ok(current_gem_cmd)
+  if not ok then
+    health.error(
+      'Failed to run: ' .. table.concat(current_gem_cmd, ' '),
+      { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') }
+    )
+    return
+  end
+
+  if vim.version.lt(current_gem, latest_gem) then
+    local message = 'Gem "neovim" is out-of-date. Installed: '
+      .. current_gem
+      .. ', latest: '
+      .. latest_gem
+    health.warn(message, 'Run in shell: gem update neovim')
+  else
+    health.ok('Latest "neovim" gem is installed: ' .. current_gem)
+  end
+end
+
+return M
-- 
cgit 


From 9a43ec13e6f1c7b52bab3304d855c680cc3ed7bd Mon Sep 17 00:00:00 2001
From: Jongwook Choi 
Date: Fri, 17 May 2024 19:16:09 -0400
Subject: fix(diagnostic): show backtrace for deprecation warnings

Problem: On nvim 11.0-dev, deprecation warnings due to an use of
hard-deprecated APIs such as:
- `vim.diagnostic.disable()`
- `vim.diagnostic.is_disabled()`
etc. are not accompanied by backtrace information. It makes difficult
for users to figure out which lines or which plugins are still using
deprecated APIs.

Solution: use `backtrace = true` in vim.deprecate() call.
---
 runtime/lua/vim/diagnostic.lua | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 1992a4000d..b5146e5beb 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -1281,7 +1281,7 @@ M.handlers.signs = {
             'vim.diagnostic.config()',
             '0.12',
             nil,
-            false
+            false -- suppress backtrace
           )
 
           if not opts.signs.text then
@@ -1596,7 +1596,7 @@ end
 
 --- @deprecated use `vim.diagnostic.is_enabled()`
 function M.is_disabled(bufnr, namespace)
-  vim.deprecate('vim.diagnostic.is_disabled()', 'vim.diagnostic.is_enabled()', '0.12', nil, false)
+  vim.deprecate('vim.diagnostic.is_disabled()', 'vim.diagnostic.is_enabled()', '0.12')
   return not M.is_enabled { bufnr = bufnr or 0, ns_id = namespace }
 end
 
@@ -1987,7 +1987,7 @@ end
 
 --- @deprecated use `vim.diagnostic.enable(false, …)`
 function M.disable(bufnr, namespace)
-  vim.deprecate('vim.diagnostic.disable()', 'vim.diagnostic.enable(false, …)', '0.12', nil, false)
+  vim.deprecate('vim.diagnostic.disable()', 'vim.diagnostic.enable(false, …)', '0.12')
   M.enable(false, { bufnr = bufnr, ns_id = namespace })
 end
 
@@ -2011,9 +2011,7 @@ function M.enable(enable, filter)
     vim.deprecate(
       'vim.diagnostic.enable(buf:number, namespace:number)',
       'vim.diagnostic.enable(enable:boolean, filter:table)',
-      '0.12',
-      nil,
-      false
+      '0.12'
     )
 
     vim.validate({
-- 
cgit 


From b16b287b8f36a8e25b27f3009d7fdd0d39348894 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 19 May 2024 11:36:11 +0200
Subject: vim-patch:9.1.0421: filetype: hyprlang files are not recognized

Problem:  filetype: hyprlang files are not recognized
Solution: recognize 'hypr{land,paper,idle,lock}.conf' files
          as 'hyprlang' filetype, add hyprlang ftplugin
          (Riley Bruins)

closes: vim/vim#14803

https://github.com/vim/vim/commit/5f1b115afd92544ce64d563da0d8ee9844abb10a

Co-authored-by: Riley Bruins 
---
 runtime/lua/vim/filetype.lua | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 0ae0f9ca92..ba4667129e 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1431,6 +1431,10 @@ local filename = {
   ['/etc/host.conf'] = 'hostconf',
   ['/etc/hosts.allow'] = 'hostsaccess',
   ['/etc/hosts.deny'] = 'hostsaccess',
+  ['hyprland.conf'] = 'hyprlang',
+  ['hyprpaper.conf'] = 'hyprlang',
+  ['hypridle.conf'] = 'hyprlang',
+  ['hyprlock.conf'] = 'hyprlang',
   ['/.icewm/menu'] = 'icemenu',
   ['.indent.pro'] = 'indent',
   indentrc = 'indent',
-- 
cgit 


From 70c0b03e61194766ec5b59e43ff2e98826d03048 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 20 May 2024 14:21:18 +0200
Subject: vim-patch:9.1.0424: filetype: slint files are not recognized

Problem:  filetype: slint files are not recognized
Solution: Detect '*.slint' files as slint filetype,
          include basic sling filetype plugin
          (Riley Bruins)

closes: vim/vim#14808

https://github.com/vim/vim/commit/aa3104b07a3e5e7cc41310f1fbfb00f71ef801a2

Co-authored-by: Riley Bruins 
---
 runtime/lua/vim/filetype.lua | 1 +
 1 file changed, 1 insertion(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index ba4667129e..97e69c0ca8 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -970,6 +970,7 @@ local extension = {
   cdf = 'skill',
   sl = 'slang',
   ice = 'slice',
+  slint = 'slint',
   score = 'slrnsc',
   sol = 'solidity',
   smali = 'smali',
-- 
cgit 


From 5c2616846aa4d85ba96adefea98ed50f46f7a291 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 20 May 2024 14:24:46 +0200
Subject: vim-patch:9.1.0425: filetype: purescript files are not recognized

Problem:  filetype: purescript files are not recognized
Solution: recognize '*.purs' files as purescript filetype,
          include basic purescript filetype plugin
          (Riley Bruins)

Reference: https://github.com/purescript/documentation/blob/master/language/Syntax.md#comments

closes: vim/vim#14813

https://github.com/vim/vim/commit/155583a5c317881e60828e3972383436ac197ee8

Co-authored-by: Riley Bruins 
---
 runtime/lua/vim/filetype.lua | 1 +
 1 file changed, 1 insertion(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 97e69c0ca8..b158cd83ba 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -845,6 +845,7 @@ local extension = {
   psf = 'psf',
   psl = 'psl',
   pug = 'pug',
+  purs = 'purescript',
   arr = 'pyret',
   pxd = 'pyrex',
   pyx = 'pyrex',
-- 
cgit 


From d89144626e7429d9c499875ed426a6223f9013be Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 20 May 2024 06:15:58 +0800
Subject: vim-patch:9.1.0394: Cannot get a list of positions describing a
 region

Problem:  Cannot get a list of positions describing a region
          (Justin M. Keyes, after v9.1.0120)
Solution: Add the getregionpos() function
          (Shougo Matsushita)

fixes: vim/vim#14609
closes: vim/vim#14617

https://github.com/vim/vim/commit/b4757e627e6c83d1c8e5535d4887a82d6a5efdd0

Co-authored-by: Shougo Matsushita 
Co-authored-by: Justin M. Keyes 
---
 runtime/lua/vim/_meta/vimfn.lua | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index 853f354275..fbcdd52116 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -3581,6 +3581,28 @@ function vim.fn.getreginfo(regname) end
 --- @return string[]
 function vim.fn.getregion(pos1, pos2, opts) end
 
+--- Same as |getregion()|, but returns a list of positions
+--- describing the buffer text segments bound by {pos1} and
+--- {pos2}.
+--- The segments are a pair of positions for every line: >
+---   [[{start_pos}, {end_pos}], ...]
+--- <
+--- The position is a |List| with four numbers:
+---     [bufnum, lnum, col, off]
+--- "bufnum" is the buffer number.
+--- "lnum" and "col" are the position in the buffer.  The first
+--- column is 1.
+--- The "off" number is zero, unless 'virtualedit' is used.  Then
+--- it is the offset in screen columns from the start of the
+--- character.  E.g., a position within a  or after the last
+--- character.
+---
+--- @param pos1 table
+--- @param pos2 table
+--- @param opts? table
+--- @return integer[][][]
+function vim.fn.getregionpos(pos1, pos2, opts) end
+
 --- The result is a String, which is type of register {regname}.
 --- The value will be one of:
 ---     "v"      for |charwise| text
-- 
cgit 


From e0259b9466a0dd62b74d4aa195b3c5e6c7a183d0 Mon Sep 17 00:00:00 2001
From: zeertzjq 
Date: Mon, 20 May 2024 20:50:32 +0800
Subject: vim-patch:9.1.0423: getregionpos() wrong with blockwise mode and
 multibyte

Problem:  getregionpos() wrong with blockwise mode and multibyte.
Solution: Use textcol and textlen instead of start_vcol and end_vcol.
          Handle coladd properly (zeertzjq).

Also remove unnecessary buflist_findnr() in add_regionpos_range(), as
getregionpos() has already switched buffer.

closes: vim/vim#14805

https://github.com/vim/vim/commit/c95e64f41f7f6d1bdc95b047ae9b369743c8637b
---
 runtime/lua/vim/_meta/vimfn.lua | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index fbcdd52116..dee65a40c7 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -3592,10 +3592,12 @@ function vim.fn.getregion(pos1, pos2, opts) end
 --- "bufnum" is the buffer number.
 --- "lnum" and "col" are the position in the buffer.  The first
 --- column is 1.
---- The "off" number is zero, unless 'virtualedit' is used.  Then
---- it is the offset in screen columns from the start of the
---- character.  E.g., a position within a  or after the last
---- character.
+--- If the "off" number of a starting position is non-zero, it is
+--- the offset in screen columns from the start of the character.
+--- E.g., a position within a  or after the last character.
+--- If the "off" number of an ending position is non-zero, it is
+--- the character's number of cells included in the selection,
+--- otherwise the whole character is included.
 ---
 --- @param pos1 table
 --- @param pos2 table
-- 
cgit 


From 8263ed46706671e6a9a21cbb5f9555dd42ff8085 Mon Sep 17 00:00:00 2001
From: Ilia Choly 
Date: Tue, 21 May 2024 12:16:53 -0400
Subject: fix(lsp): add textDocument/documentLink to capability map (#28838)

---
 runtime/lua/vim/lsp.lua | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 8103ed4d21..8751cff902 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -64,6 +64,8 @@ lsp._request_name_to_capability = {
   [ms.textDocument_inlayHint] = { 'inlayHintProvider' },
   [ms.textDocument_diagnostic] = { 'diagnosticProvider' },
   [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' },
+  [ms.textDocument_documentLink] = { 'documentLinkProvider' },
+  [ms.documentLink_resolve] = { 'documentLinkProvider', 'resolveProvider' },
 }
 
 -- TODO improve handling of scratch buffers with LSP attached.
-- 
cgit 


From d9a2acdab3bfd33584ad468f4d559b0e94f84471 Mon Sep 17 00:00:00 2001
From: Mango The Fourth <40720523+MangoIV@users.noreply.github.com>
Date: Tue, 21 May 2024 18:23:37 +0200
Subject: fix(lsp): hide layout in codelenses in virtual text (#28794) (#28807)

Problem: layout i.e. whitespace that is part of codelenses is currently
displayed as weird symbols and large amounts of spaces

Solution: replace all consecutive whitespace symbols with a single space
character when trying to display codelenses as virtual text
---
 runtime/lua/vim/lsp/codelens.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index a2a0c15b93..c85bb6aa32 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -164,7 +164,7 @@ function M.display(lenses, bufnr, client_id)
       return a.range.start.character < b.range.start.character
     end)
     for j, lens in ipairs(line_lenses) do
-      local text = lens.command and lens.command.title or 'Unresolved lens ...'
+      local text = (lens.command and lens.command.title or 'Unresolved lens ...'):gsub('%s+', ' ')
       table.insert(chunks, { text, 'LspCodeLens' })
       if j < num_line_lenses then
         table.insert(chunks, { ' | ', 'LspCodeLensSeparator' })
-- 
cgit 


From a108852b008b2cb1fe38b1f66cffd78b33bd9a70 Mon Sep 17 00:00:00 2001
From: Riley Bruins 
Date: Tue, 21 May 2024 09:25:54 -0700
Subject: fix(lsp): semantic token functions allow "0" bufnr #28849

aligns with ":help dev-patterns"
---
 runtime/lua/vim/lsp/semantic_tokens.lua | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index be2d6ee0ae..ef2502b12e 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -570,9 +570,9 @@ local M = {}
 --- client.server_capabilities.semanticTokensProvider = nil
 --- ```
 ---
----@param bufnr integer
----@param client_id integer
----@param opts? table Optional keyword arguments
+---@param bufnr (integer) Buffer number, or `0` for current buffer
+---@param client_id (integer) The ID of the |vim.lsp.Client|
+---@param opts? (table) Optional keyword arguments
 ---  - debounce (integer, default: 200): Debounce token requests
 ---        to the server by the given number in milliseconds
 function M.start(bufnr, client_id, opts)
@@ -581,6 +581,10 @@ function M.start(bufnr, client_id, opts)
     client_id = { client_id, 'n', false },
   })
 
+  if bufnr == 0 then
+    bufnr = api.nvim_get_current_buf()
+  end
+
   opts = opts or {}
   assert(
     (not opts.debounce or type(opts.debounce) == 'number'),
@@ -626,14 +630,18 @@ end
 --- of `start()`, so you should only need this function to manually disengage the semantic
 --- token engine without fully detaching the LSP client from the buffer.
 ---
----@param bufnr integer
----@param client_id integer
+---@param bufnr (integer) Buffer number, or `0` for current buffer
+---@param client_id (integer) The ID of the |vim.lsp.Client|
 function M.stop(bufnr, client_id)
   vim.validate({
     bufnr = { bufnr, 'n', false },
     client_id = { client_id, 'n', false },
   })
 
+  if bufnr == 0 then
+    bufnr = api.nvim_get_current_buf()
+  end
+
   local highlighter = STHighlighter.active[bufnr]
   if not highlighter then
     return
@@ -741,12 +749,15 @@ end
 --- mark will be deleted by the semantic token engine when appropriate; for
 --- example, when the LSP sends updated tokens. This function is intended for
 --- use inside |LspTokenUpdate| callbacks.
----@param token (table) a semantic token, found as `args.data.token` in |LspTokenUpdate|.
----@param bufnr (integer) the buffer to highlight
+---@param token (table) A semantic token, found as `args.data.token` in |LspTokenUpdate|
+---@param bufnr (integer) The buffer to highlight, or `0` for current buffer
 ---@param client_id (integer) The ID of the |vim.lsp.Client|
 ---@param hl_group (string) Highlight group name
 ---@param opts? vim.lsp.semantic_tokens.highlight_token.Opts  Optional parameters:
 function M.highlight_token(token, bufnr, client_id, hl_group, opts)
+  if bufnr == 0 then
+    bufnr = api.nvim_get_current_buf()
+  end
   local highlighter = STHighlighter.active[bufnr]
   if not highlighter then
     return
-- 
cgit 


From 879d17ea8d62c199ea0c91c5f37a4f25495be7ce Mon Sep 17 00:00:00 2001
From: Ilia Choly 
Date: Tue, 21 May 2024 14:02:48 -0400
Subject: fix(lsp): detach all clients on_reload to force buf_state reload
 (#28875)

Problem:  The changetracking state can de-sync when reloading a buffer
          with more than one LSP client attached.
Solution: Fully detach all clients from the buffer to force buf_state to
          be re-created.
---
 runtime/lua/vim/lsp.lua | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 8751cff902..6f12bc457b 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -556,12 +556,15 @@ local function buf_attach(bufnr)
     end,
 
     on_reload = function()
+      local clients = lsp.get_clients({ bufnr = bufnr })
       local params = { textDocument = { uri = uri } }
-      for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
+      for _, client in ipairs(clients) do
         changetracking.reset_buf(client, bufnr)
         if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
           client.notify(ms.textDocument_didClose, params)
         end
+      end
+      for _, client in ipairs(clients) do
         client:_text_document_did_open_handler(bufnr)
       end
     end,
-- 
cgit 


From 339129ebc9503883a3f060d3eff620d67a9eadaf Mon Sep 17 00:00:00 2001
From: Ilia Choly 
Date: Sun, 19 May 2024 13:03:06 -0400
Subject: refactor(lsp): use supports_method where applicable

---
 runtime/lua/vim/lsp.lua        | 14 +++++++++-----
 runtime/lua/vim/lsp/client.lua |  2 +-
 2 files changed, 10 insertions(+), 6 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 6f12bc457b..ad1794e98d 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -66,6 +66,10 @@ lsp._request_name_to_capability = {
   [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' },
   [ms.textDocument_documentLink] = { 'documentLinkProvider' },
   [ms.documentLink_resolve] = { 'documentLinkProvider', 'resolveProvider' },
+  [ms.textDocument_didClose] = { 'textDocumentSync', 'openClose' },
+  [ms.textDocument_didOpen] = { 'textDocumentSync', 'openClose' },
+  [ms.textDocument_willSave] = { 'textDocumentSync', 'willSave' },
+  [ms.textDocument_willSaveWaitUntil] = { 'textDocumentSync', 'willSaveWaitUntil' },
 }
 
 -- TODO improve handling of scratch buffers with LSP attached.
@@ -522,10 +526,10 @@ local function buf_attach(bufnr)
           },
           reason = protocol.TextDocumentSaveReason.Manual, ---@type integer
         }
-        if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then
+        if client.supports_method(ms.textDocument_willSave) then
           client.notify(ms.textDocument_willSave, params)
         end
-        if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then
+        if client.supports_method(ms.textDocument_willSaveWaitUntil) then
           local result, err =
             client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf)
           if result and result.result then
@@ -560,7 +564,7 @@ local function buf_attach(bufnr)
       local params = { textDocument = { uri = uri } }
       for _, client in ipairs(clients) do
         changetracking.reset_buf(client, bufnr)
-        if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
+        if client.supports_method(ms.textDocument_didClose) then
           client.notify(ms.textDocument_didClose, params)
         end
       end
@@ -573,7 +577,7 @@ local function buf_attach(bufnr)
       local params = { textDocument = { uri = uri } }
       for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
         changetracking.reset_buf(client, bufnr)
-        if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
+        if client.supports_method(ms.textDocument_didClose) then
           client.notify(ms.textDocument_didClose, params)
         end
       end
@@ -665,7 +669,7 @@ function lsp.buf_detach_client(bufnr, client_id)
 
   changetracking.reset_buf(client, bufnr)
 
-  if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
+  if client.supports_method(ms.textDocument_didClose) then
     local uri = vim.uri_from_bufnr(bufnr)
     local params = { textDocument = { uri = uri } }
     client.notify(ms.textDocument_didClose, params)
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index 448f986cd3..8fb5879e9b 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -905,7 +905,7 @@ end
 --- @param bufnr integer Number of the buffer, or 0 for current
 function Client:_text_document_did_open_handler(bufnr)
   changetracking.init(self, bufnr)
-  if not vim.tbl_get(self.server_capabilities, 'textDocumentSync', 'openClose') then
+  if not self.supports_method(ms.textDocument_didOpen) then
     return
   end
   if not api.nvim_buf_is_loaded(bufnr) then
-- 
cgit 


From e8f7025de1d8b7c8bbe747736e4c46dcd6e73133 Mon Sep 17 00:00:00 2001
From: dundargoc 
Date: Wed, 22 May 2024 16:07:45 +0200
Subject: docs: move vim.health documentation to lua.txt

`vim.health` is not a "plugin" but part of our Lua API and the
documentation should reflect that. This also helps make the
documentation maintenance easier as it is now generated.
---
 runtime/lua/vim/health.lua                    | 106 ++++++++++++++++++++++++--
 runtime/lua/vim/provider/clipboard/health.lua |   2 +-
 runtime/lua/vim/provider/node/health.lua      |   8 +-
 runtime/lua/vim/provider/perl/health.lua      |   8 +-
 runtime/lua/vim/provider/python/health.lua    |  16 ++--
 runtime/lua/vim/provider/ruby/health.lua      |   8 +-
 6 files changed, 119 insertions(+), 29 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index 69a53485a4..263a6ec85f 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -1,3 +1,91 @@
+--- @brief
+---
help
+--- health.vim is a minimal framework to help users troubleshoot configuration and
+--- any other environment conditions that a plugin might care about. Nvim ships
+--- with healthchecks for configuration, performance, python support, ruby
+--- support, clipboard support, and more.
+---
+--- To run all healthchecks, use: >vim
+---
+---         :checkhealth
+--- <
+--- Plugin authors are encouraged to write new healthchecks. |health-dev|
+---
+--- Commands                                *health-commands*
+---
+---                                                              *:che* *:checkhealth*
+--- :che[ckhealth]  Run all healthchecks.
+---                                         *E5009*
+---                 Nvim depends on |$VIMRUNTIME|, 'runtimepath' and 'packpath' to
+---                 find the standard "runtime files" for syntax highlighting,
+---                 filetype-specific behavior, and standard plugins (including
+---                 :checkhealth).  If the runtime files cannot be found then
+---                 those features will not work.
+---
+--- :che[ckhealth] {plugins}
+---                 Run healthcheck(s) for one or more plugins. E.g. to run only
+---                 the standard Nvim healthcheck: >vim
+---                         :checkhealth vim.health
+--- <
+---                 To run the healthchecks for the "foo" and "bar" plugins
+---                 (assuming they are on 'runtimepath' and they have implemented
+---                 the Lua `require("foo.health").check()` interface): >vim
+---                         :checkhealth foo bar
+--- <
+---                 To run healthchecks for Lua submodules, use dot notation or
+---                 "*" to refer to all submodules. For example Nvim provides
+---                 `vim.lsp` and `vim.treesitter`:  >vim
+---                         :checkhealth vim.lsp vim.treesitter
+---                         :checkhealth vim*
+--- <
+---
+--- Create a healthcheck                                    *health-dev*
+---
+--- Healthchecks are functions that check the user environment, configuration, or
+--- any other prerequisites that a plugin cares about. Nvim ships with
+--- healthchecks in:
+---         - $VIMRUNTIME/autoload/health/
+---         - $VIMRUNTIME/lua/vim/lsp/health.lua
+---         - $VIMRUNTIME/lua/vim/treesitter/health.lua
+---         - and more...
+---
+--- To add a new healthcheck for your own plugin, simply create a "health.lua"
+--- module on 'runtimepath' that returns a table with a "check()" function. Then
+--- |:checkhealth| will automatically find and invoke the function.
+---
+--- For example if your plugin is named "foo", define your healthcheck module at
+--- one of these locations (on 'runtimepath'):
+---         - lua/foo/health/init.lua
+---         - lua/foo/health.lua
+---
+--- If your plugin also provides a submodule named "bar" for which you want
+--- a separate healthcheck, define the healthcheck at one of these locations:
+---         - lua/foo/bar/health/init.lua
+---         - lua/foo/bar/health.lua
+---
+--- All such health modules must return a Lua table containing a `check()`
+--- function.
+---
+--- Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path
+--- with your plugin name: >lua
+---
+---         local M = {}
+---
+---         M.check = function()
+---           vim.health.start("foo report")
+---           -- make sure setup function parameters are ok
+---           if check_setup() then
+---             vim.health.ok("Setup is correct")
+---           else
+---             vim.health.error("Setup is incorrect")
+---           end
+---           -- do some more checking
+---           -- ...
+---         end
+---
+---         return M
+---
+ local M = {} local s_output = {} ---@type string[] @@ -143,7 +231,9 @@ local function collect_output(output) vim.list_extend(s_output, vim.split(output, '\n')) end ---- Starts a new report. +--- Starts a new report. Most plugins should call this only once, but if +--- you want different sections to appear in your report, call this once +--- per section. --- --- @param name string function M.start(name) @@ -151,7 +241,7 @@ function M.start(name) collect_output(input) end ---- Reports a message in the current section. +--- Reports an informational message. --- --- @param msg string function M.info(msg) @@ -159,7 +249,7 @@ function M.info(msg) collect_output(input) end ---- Reports a successful healthcheck. +--- Reports a "success" message. --- --- @param msg string function M.ok(msg) @@ -167,7 +257,7 @@ function M.ok(msg) collect_output(input) end ---- Reports a health warning. +--- Reports a warning. --- --- @param msg string --- @param ... string|string[] Optional advice @@ -176,7 +266,7 @@ function M.warn(msg, ...) collect_output(input) end ---- Reports a failed healthcheck. +--- Reports an error. --- --- @param msg string --- @param ... string|string[] Optional advice @@ -185,7 +275,7 @@ function M.error(msg, ...) collect_output(input) end -function M.provider_disabled(provider) +function M._provider_disabled(provider) local loaded_var = 'loaded_' .. provider .. '_provider' local v = vim.g[loaded_var] if v == 0 then @@ -225,7 +315,7 @@ local function shellify(cmd) return table.concat(escaped, ' ') end -function M.cmd_ok(cmd) +function M._cmd_ok(cmd) local out = vim.fn.system(cmd) return vim.v.shell_error == 0, out end @@ -238,7 +328,7 @@ end --- - 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) +function M._system(cmd, args) args = args or {} local stdin = args.stdin or '' local stderr = vim.F.if_nil(args.stderr, false) diff --git a/runtime/lua/vim/provider/clipboard/health.lua b/runtime/lua/vim/provider/clipboard/health.lua index e44f7d32cc..0af6a44330 100644 --- a/runtime/lua/vim/provider/clipboard/health.lua +++ b/runtime/lua/vim/provider/clipboard/health.lua @@ -9,7 +9,7 @@ function M.check() os.getenv('TMUX') and vim.fn.executable('tmux') == 1 and vim.fn.executable('pbpaste') == 1 - and not health.cmd_ok('pbpaste') + and not health._cmd_ok('pbpaste') then local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') local advice = { diff --git a/runtime/lua/vim/provider/node/health.lua b/runtime/lua/vim/provider/node/health.lua index 471c625388..b24a9e0061 100644 --- a/runtime/lua/vim/provider/node/health.lua +++ b/runtime/lua/vim/provider/node/health.lua @@ -6,7 +6,7 @@ local M = {} function M.check() health.start('Node.js provider (optional)') - if health.provider_disabled('node') then + if health._provider_disabled('node') then return end @@ -26,7 +26,7 @@ function M.check() 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 = health._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) @@ -63,7 +63,7 @@ function M.check() 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 = health._cmd_ok(vim.split(latest_npm_cmd, ' ')) if not ok or latest_npm:find('^%s$') then health.error( 'Failed to run: ' .. latest_npm_cmd, @@ -81,7 +81,7 @@ function M.check() local current_npm_cmd = { 'node', host, '--version' } local current_npm - ok, current_npm = health.cmd_ok(current_npm_cmd) + ok, current_npm = health._cmd_ok(current_npm_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), diff --git a/runtime/lua/vim/provider/perl/health.lua b/runtime/lua/vim/provider/perl/health.lua index 535093d793..ffc0419b94 100644 --- a/runtime/lua/vim/provider/perl/health.lua +++ b/runtime/lua/vim/provider/perl/health.lua @@ -5,7 +5,7 @@ local M = {} function M.check() health.start('Perl provider (optional)') - if health.provider_disabled('perl') then + if health._provider_disabled('perl') then return end @@ -24,7 +24,7 @@ function M.check() -- 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 = health._cmd_ok({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) if not ok then return { perl_exec, '"App::cpanminus" module is not installed' } end @@ -36,7 +36,7 @@ function M.check() '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 = health._cmd_ok(latest_cpan_cmd) if not ok or latest_cpan:find('^%s*$') then health.error( 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), @@ -67,7 +67,7 @@ function M.check() 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 = health._cmd_ok(current_cpan_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), diff --git a/runtime/lua/vim/provider/python/health.lua b/runtime/lua/vim/provider/python/health.lua index a5bd738063..37ccb42da3 100644 --- a/runtime/lua/vim/provider/python/health.lua +++ b/runtime/lua/vim/provider/python/health.lua @@ -70,7 +70,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 = health._system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) if rc ~= 0 then return 'curl error with ' .. url .. ': ' .. rc else @@ -83,7 +83,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 = health._system({ 'python', '-c', script }) if out == '' and rc ~= 0 then return 'python urllib.request error: ' .. rc else @@ -140,7 +140,7 @@ end local function version_info(python) local pypi_version = latest_pypi_version() - local python_version, rc = health.system({ + local python_version, rc = health._system({ python, '-c', 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', @@ -151,7 +151,7 @@ local function version_info(python) end local nvim_path - nvim_path, rc = health.system({ + nvim_path, rc = health._system({ python, '-c', 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', @@ -176,7 +176,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 = health._system({ python, '-c', 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', @@ -223,7 +223,7 @@ function M.check() local host_prog_var = pyname .. '_host_prog' local python_multiple = {} - if health.provider_disabled(pyname) then + if health._provider_disabled(pyname) then return end @@ -265,7 +265,7 @@ function M.check() end if pyenv ~= '' then - python_exe = health.system({ pyenv, 'which', pyname }, { stderr = true }) + python_exe = health._system({ pyenv, 'which', pyname }, { stderr = true }) if python_exe == '' then health.warn('pyenv could not find ' .. pyname .. '.') end @@ -488,7 +488,7 @@ function M.check() health.info(msg) health.info( 'Python version: ' - .. health.system( + .. health._system( 'python -c "import platform, sys; sys.stdout.write(platform.python_version())"' ) ) diff --git a/runtime/lua/vim/provider/ruby/health.lua b/runtime/lua/vim/provider/ruby/health.lua index 31c2fe3201..80932e1bb0 100644 --- a/runtime/lua/vim/provider/ruby/health.lua +++ b/runtime/lua/vim/provider/ruby/health.lua @@ -6,7 +6,7 @@ local M = {} function M.check() health.start('Ruby provider (optional)') - if health.provider_disabled('ruby') then + if health._provider_disabled('ruby') then return end @@ -17,7 +17,7 @@ function M.check() ) return end - health.info('Ruby: ' .. health.system({ 'ruby', '-v' })) + health.info('Ruby: ' .. health._system({ 'ruby', '-v' })) local host, _ = vim.provider.ruby.detect() if (not host) or host:find('^%s*$') then @@ -33,7 +33,7 @@ function M.check() 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 = health._cmd_ok(vim.split(latest_gem_cmd, ' ')) if not ok or latest_gem:find('^%s*$') then health.error( 'Failed to run: ' .. latest_gem_cmd, @@ -46,7 +46,7 @@ function M.check() local current_gem_cmd = { host, '--version' } local current_gem - ok, current_gem = health.cmd_ok(current_gem_cmd) + ok, current_gem = health._cmd_ok(current_gem_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), -- cgit From 01b4da65c229f05ccb26c55db4e0d30ed9bac10b Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 22 May 2024 20:40:20 +0200 Subject: fix: merge all provider healthchecks into a single health.lua This will help manage the overly granular checkhealth completion to go from ``` vim.health vim.lsp vim.provider.clipboard vim.provider.node vim.provider.perl vim.provider.python vim.provider.ruby vim.treesitter ``` to ``` vim.health vim.lsp vim.provider vim.treesitter ``` --- runtime/lua/vim/provider/clipboard/health.lua | 39 -- runtime/lua/vim/provider/health.lua | 792 ++++++++++++++++++++++++++ runtime/lua/vim/provider/node/health.lua | 109 ---- runtime/lua/vim/provider/perl/health.lua | 90 --- runtime/lua/vim/provider/python/health.lua | 499 ---------------- runtime/lua/vim/provider/ruby/health.lua | 69 --- 6 files changed, 792 insertions(+), 806 deletions(-) delete mode 100644 runtime/lua/vim/provider/clipboard/health.lua create mode 100644 runtime/lua/vim/provider/health.lua delete mode 100644 runtime/lua/vim/provider/node/health.lua delete mode 100644 runtime/lua/vim/provider/perl/health.lua delete mode 100644 runtime/lua/vim/provider/python/health.lua delete mode 100644 runtime/lua/vim/provider/ruby/health.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/provider/clipboard/health.lua b/runtime/lua/vim/provider/clipboard/health.lua deleted file mode 100644 index 0af6a44330..0000000000 --- a/runtime/lua/vim/provider/clipboard/health.lua +++ /dev/null @@ -1,39 +0,0 @@ -local health = vim.health - -local M = {} - -function M.check() - health.start('Clipboard (optional)') - - if - os.getenv('TMUX') - and vim.fn.executable('tmux') == 1 - and vim.fn.executable('pbpaste') == 1 - and not health._cmd_ok('pbpaste') - then - local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') - local advice = { - 'Install tmux 2.6+. https://superuser.com/q/231130', - 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233', - } - health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) - end - - local clipboard_tool = vim.fn['provider#clipboard#Executable']() - if vim.g.clipboard ~= nil and clipboard_tool == '' then - local error_message = vim.fn['provider#clipboard#Error']() - health.error( - error_message, - "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." - ) - elseif clipboard_tool:find('^%s*$') then - health.warn( - 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', - ':help clipboard' - ) - else - health.ok('Clipboard tool found: ' .. clipboard_tool) - end -end - -return M diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua new file mode 100644 index 0000000000..edf813e96f --- /dev/null +++ b/runtime/lua/vim/provider/health.lua @@ -0,0 +1,792 @@ +local health = vim.health +local iswin = vim.loop.os_uname().sysname == 'Windows_NT' + +local M = {} + +local function clipboard() + health.start('Clipboard (optional)') + + if + os.getenv('TMUX') + and vim.fn.executable('tmux') == 1 + and vim.fn.executable('pbpaste') == 1 + and not health._cmd_ok('pbpaste') + then + local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') + local advice = { + 'Install tmux 2.6+. https://superuser.com/q/231130', + 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233', + } + health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) + end + + local clipboard_tool = vim.fn['provider#clipboard#Executable']() + if vim.g.clipboard ~= nil and clipboard_tool == '' then + local error_message = vim.fn['provider#clipboard#Error']() + health.error( + error_message, + "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." + ) + elseif clipboard_tool:find('^%s*$') then + health.warn( + 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.', + ':help clipboard' + ) + else + health.ok('Clipboard tool found: ' .. clipboard_tool) + end +end + +local function node() + health.start('Node.js provider (optional)') + + if health._provider_disabled('node') then + return + end + + if + vim.fn.executable('node') == 0 + or ( + vim.fn.executable('npm') == 0 + and vim.fn.executable('yarn') == 0 + and vim.fn.executable('pnpm') == 0 + ) + then + health.warn( + '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.', + 'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.' + ) + return + end + + -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' + local ok, node_v = health._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) + -- Skip further checks, they are nonsense if nodejs is too old. + return + end + if vim.fn['provider#node#can_inspect']() == 0 then + health.warn( + 'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.' + ) + end + + local node_detect_table = vim.fn['provider#node#Detect']() + local host = node_detect_table[1] + if host:find('^%s*$') then + health.warn('Missing "neovim" npm (or yarn, pnpm) package.', { + 'Run in shell: npm install -g neovim', + 'Run in shell (if you use yarn): yarn global add neovim', + 'Run in shell (if you use pnpm): pnpm install -g neovim', + 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim', + }) + return + end + health.info('Nvim node.js host: ' .. host) + + local manager = 'npm' + if vim.fn.executable('yarn') == 1 then + manager = 'yarn' + elseif vim.fn.executable('pnpm') == 1 then + manager = 'pnpm' + end + + local latest_npm_cmd = ( + 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, ' ')) + if not ok or latest_npm:find('^%s$') then + health.error( + 'Failed to run: ' .. latest_npm_cmd, + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + end + + local pcall_ok, pkg_data = pcall(vim.json.decode, latest_npm) + if not pcall_ok then + return 'error: ' .. latest_npm + end + local latest_npm_subtable = pkg_data['dist-tags'] or {} + latest_npm = latest_npm_subtable['latest'] or 'unable to parse' + + local current_npm_cmd = { 'node', host, '--version' } + local current_npm + ok, current_npm = health._cmd_ok(current_npm_cmd) + if not ok then + health.error( + 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') } + ) + return + end + + if latest_npm ~= 'unable to parse' and vim.version.lt(current_npm, latest_npm) then + local message = 'Package "neovim" is out-of-date. Installed: ' + .. current_npm:gsub('%\n$', '') + .. ', latest: ' + .. latest_npm:gsub('%\n$', '') + + health.warn(message, { + 'Run in shell: npm install -g neovim', + 'Run in shell (if you use yarn): yarn global add neovim', + 'Run in shell (if you use pnpm): pnpm install -g neovim', + }) + else + health.ok('Latest "neovim" npm/yarn/pnpm package is installed: ' .. current_npm) + end +end + +local function perl() + health.start('Perl provider (optional)') + + if health._provider_disabled('perl') then + return + end + + local perl_exec, perl_warnings = vim.provider.perl.detect() + + if not perl_exec then + health.warn(assert(perl_warnings), { + 'See :help provider-perl for more information.', + 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', + }) + health.warn('No usable perl executable found') + return + end + + health.info('perl executable: ' .. perl_exec) + + -- 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', '' }) + if not ok then + return { perl_exec, '"App::cpanminus" module is not installed' } + end + + local latest_cpan_cmd = { + perl_exec, + '-MApp::cpanminus::fatscript', + '-e', + '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) + if not ok or latest_cpan:find('^%s*$') then + health.error( + 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + elseif latest_cpan[1] == '!' then + local cpanm_errs = vim.split(latest_cpan, '!') + if cpanm_errs[1]:find("Can't write to ") then + local advice = {} + for i = 2, #cpanm_errs do + advice[#advice + 1] = cpanm_errs[i] + end + + health.warn(cpanm_errs[1], advice) + -- Last line is the package info + latest_cpan = cpanm_errs[#cpanm_errs] + else + health.error('Unknown warning from command: ' .. latest_cpan_cmd, cpanm_errs) + return + end + end + latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]]) + if latest_cpan:find('^%s*$') then + health.error('Cannot parse version number from cpanm output: ' .. latest_cpan) + return + end + + 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) + if not ok then + health.error( + 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') } + ) + return + end + + if vim.version.lt(current_cpan, latest_cpan) then + local message = 'Module "Neovim::Ext" is out-of-date. Installed: ' + .. current_cpan + .. ', latest: ' + .. latest_cpan + health.warn(message, 'Run in shell: cpanm -n Neovim::Ext') + else + health.ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan) + end +end + +local function is(path, ty) + if not path then + return false + end + local stat = vim.loop.fs_stat(path) + if not stat then + return false + end + return stat.type == ty +end + +-- Resolves Python executable path by invoking and checking `sys.executable`. +local function python_exepath(invocation) + local p = vim.system({ invocation, '-c', 'import sys; sys.stdout.write(sys.executable)' }):wait() + assert(p.code == 0, p.stderr) + 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. +local function check_for_pyenv() + local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) + + if pyenv_path == '' then + return { '', '' } + end + + health.info('pyenv: Path: ' .. pyenv_path) + + local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '') + + if pyenv_root == '' then + pyenv_root = vim.fn.system({ pyenv_path, 'root' }) + health.info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.') + end + + if not is(pyenv_root, 'directory') then + local message = string.format( + 'pyenv: Root does not exist: %s. Ignoring pyenv for all following checks.', + pyenv_root + ) + health.warn(message) + return { '', '' } + end + + health.info('pyenv: Root: ' .. pyenv_root) + + return { pyenv_path, pyenv_root } +end + +-- Check the Python interpreter's usability. +local function check_bin(bin) + if not is(bin, 'file') and (not iswin or not is(bin .. '.exe', 'file')) then + health.error('"' .. bin .. '" was not found.') + return false + elseif vim.fn.executable(bin) == 0 then + health.error('"' .. bin .. '" is not executable.') + return false + end + return true +end + +-- Fetch the contents of a URL. +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 }) + if rc ~= 0 then + return 'curl error with ' .. url .. ': ' .. rc + else + return out + end + elseif vim.fn.executable('python') == 1 then + local script = "try:\n\ + from urllib.request import urlopen\n\ + except ImportError:\n\ + from urllib2 import urlopen\n\ + response = urlopen('" .. url .. "')\n\ + print(response.read().decode('utf8'))\n" + local out, rc = health._system({ 'python', '-c', script }) + if out == '' and rc ~= 0 then + return 'python urllib.request error: ' .. rc + else + return out + end + end + + local message = 'missing `curl` ' + + if has_curl then + message = message .. '(with HTTPS support) ' + end + message = message .. 'and `python`, cannot make web request' + + return message +end + +-- 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 + return 'error: ' .. pypi_response + end + + local pypi_element = pypi_data['info'] or {} + pypi_version = pypi_element['version'] or 'unable to parse' + end + return pypi_version +end + +local function is_bad_response(s) + local lower = s:lower() + return vim.startswith(lower, 'unable') + or vim.startswith(lower, 'error') + 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} +-- } +local function version_info(python) + local pypi_version = latest_pypi_version() + + local python_version, rc = health._system({ + python, + '-c', + 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', + }) + + if rc ~= 0 or python_version == '' then + python_version = 'unable to parse ' .. python .. ' response' + end + + local nvim_path + nvim_path, rc = health._system({ + python, + '-c', + 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', + }) + if rc ~= 0 or nvim_path == '' then + return { python_version, 'unable to load neovim Python module', pypi_version, nvim_path } + end + + -- Assuming that multiple versions of a package are installed, sort them + -- numerically in descending order. + local function compare(metapath1, metapath2) + local a = vim.fn.matchstr(vim.fn.fnamemodify(metapath1, ':p:h:t'), [[[0-9.]\+]]) + local b = vim.fn.matchstr(vim.fn.fnamemodify(metapath2, ':p:h:t'), [[[0-9.]\+]]) + if a == b then + return 0 + elseif a > b then + return 1 + else + return -1 + end + end + + -- Try to get neovim.VERSION (added in 0.1.11dev). + local nvim_version + nvim_version, rc = health._system({ + python, + '-c', + 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', + }, { stderr = true, ignore_error = true }) + 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)) + metas = table.sort(metas, compare) + + if metas and next(metas) ~= nil then + for line in io.lines(metas[1]) do + local version = line:match('^Version: (%S+)') + if version then + nvim_version = version + break + end + end + end + end + + local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]]) + local version_status = 'unknown; ' .. nvim_path_base + if is_bad_response(nvim_version) and is_bad_response(pypi_version) then + if vim.version.lt(nvim_version, pypi_version) then + version_status = 'outdated; from ' .. nvim_path_base + else + version_status = 'up to date' + end + end + + return { python_version, nvim_version, pypi_version, version_status } +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 = {} + + if health._provider_disabled(pyname) then + return + end + + local pyenv_table = check_for_pyenv() + local pyenv = pyenv_table[1] + local pyenv_root = pyenv_table[2] + + if vim.g[host_prog_var] then + local message = string.format('Using: g:%s = "%s"', host_prog_var, vim.g[host_prog_var]) + health.info(message) + end + + local pythonx_warnings + pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') + + if not pyname then + health.warn( + 'No Python executable found that can `import neovim`. ' + .. 'Using the first available executable for diagnostics.' + ) + elseif vim.g[host_prog_var] then + python_exe = pyname + end + + -- No Python executable could `import neovim`, or host_prog_var was used. + if pythonx_warnings then + health.warn(pythonx_warnings, { + 'See :help provider-python for more information.', + 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', + }) + elseif pyname and pyname ~= '' and python_exe == '' then + if not vim.g[host_prog_var] then + local message = string.format( + '`g:%s` is not set. Searching for %s in the environment.', + host_prog_var, + pyname + ) + health.info(message) + end + + if pyenv ~= '' then + python_exe = health._system({ pyenv, 'which', pyname }, { stderr = true }) + if python_exe == '' then + health.warn('pyenv could not find ' .. pyname .. '.') + end + end + + if python_exe == '' then + python_exe = vim.fn.exepath(pyname) + + if os.getenv('PATH') then + local path_sep = iswin and ';' or ':' + local paths = vim.split(os.getenv('PATH') or '', path_sep) + + for _, path in ipairs(paths) do + local path_bin = vim.fs.normalize(path .. '/' .. pyname) + if + path_bin ~= vim.fs.normalize(python_exe) + and vim.tbl_contains(python_multiple, path_bin) + and vim.fn.executable(path_bin) == 1 + then + python_multiple[#python_multiple + 1] = path_bin + end + end + + if vim.tbl_count(python_multiple) > 0 then + -- This is worth noting since the user may install something + -- that changes $PATH, like homebrew. + local message = string.format( + 'Multiple %s executables found. Set `g:%s` to avoid surprises.', + pyname, + host_prog_var + ) + health.info(message) + end + + if python_exe:find('shims') then + local message = string.format('`%s` appears to be a pyenv shim.', python_exe) + local advice = string.format( + '`pyenv` is not in $PATH, your pyenv installation is broken. Set `g:%s` to avoid surprises.', + host_prog_var + ) + health.warn(message, advice) + end + end + end + end + + if python_exe ~= '' and not vim.g[host_prog_var] then + if + venv == '' + and pyenv ~= '' + and pyenv_root ~= '' + and vim.startswith(vim.fn.resolve(python_exe), pyenv_root .. '/') + then + local advice = string.format( + 'Create a virtualenv specifically for Nvim using pyenv, and set `g:%s`. This will avoid the need to install the pynvim module in each version/virtualenv.', + host_prog_var + ) + 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 + + if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then + local advice = string.format( + 'Create a virtualenv specifically for Nvim and use `g:%s`. This will avoid the need to install the pynvim module in each virtualenv.', + host_prog_var + ) + health.warn('Your virtualenv is not set up optimally.', advice) + end + end + end + + if pyname and python_exe == '' and pyname ~= '' then + -- An error message should have already printed. + health.error('`' .. pyname .. '` was not found.') + elseif python_exe ~= '' and not check_bin(python_exe) then + python_exe = '' + end + + -- Diagnostic output + health.info('Executable: ' .. (python_exe == '' and 'Not found' or python_exe)) + if vim.tbl_count(python_multiple) > 0 then + for _, path_bin in ipairs(python_multiple) do + health.info('Other python executable: ' .. path_bin) + end + end + + if python_exe == '' then + -- No Python executable can import 'neovim'. Check if any Python executable + -- can import 'pynvim'. If so, that Python failed to import 'neovim' as + -- well, which is most probably due to a failed pip upgrade: + -- https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 + local pynvim_exe = vim.provider.python.detect_by_module('pynvim') + if pynvim_exe then + local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": ' + .. pynvim_exe + local advice = { + 'Use that Python version to reinstall "pynvim" and optionally "neovim".', + pynvim_exe .. ' -m pip uninstall pynvim neovim', + pynvim_exe .. ' -m pip install pynvim', + pynvim_exe .. ' -m pip install neovim # only if needed by third-party software', + } + health.error(message, advice) + end + else + local version_info_table = version_info(python_exe) + local pyversion = version_info_table[1] + local current = version_info_table[2] + local latest = version_info_table[3] + local status = version_info_table[4] + + if not vim.version.range('~3'):has(pyversion) then + health.warn('Unexpected Python version. This could lead to confusing error messages.') + end + + health.info('Python version: ' .. pyversion) + + if is_bad_response(status) then + health.info('pynvim version: ' .. current .. ' (' .. status .. ')') + else + health.info('pynvim version: ' .. current) + end + + if is_bad_response(current) then + health.error( + 'pynvim is not installed.\nError: ' .. current, + 'Run in shell: ' .. python_exe .. ' -m pip install pynvim' + ) + end + + if is_bad_response(latest) then + health.warn('Could not contact PyPI to get latest version.') + health.error('HTTP request failed: ' .. latest) + elseif is_bad_response(status) then + health.warn('Latest pynvim is NOT installed: ' .. latest) + elseif not is_bad_response(current) then + health.ok('Latest pynvim is installed.') + end + end + + health.start('Python virtualenv') + if not virtual_env then + health.ok('no $VIRTUAL_ENV') + return + end + local errors = {} + -- Keep hints as dict keys in order to discard duplicates. + local hints = {} + -- 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. + local bin_dir = iswin and 'Scripts' or 'bin' + local venv_bins = vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true) + venv_bins = vim.tbl_filter(function(v) + -- XXX: Remove irrelevant executables found in bin/. + return not v:match('python%-config') + end, venv_bins) + if vim.tbl_count(venv_bins) > 0 then + for _, venv_bin in pairs(venv_bins) do + venv_bin = vim.fs.normalize(venv_bin) + local py_bin_basename = vim.fs.basename(venv_bin) + local nvim_py_bin = python_exepath(vim.fn.exepath(py_bin_basename)) + local subshell_py_bin = python_exepath(py_bin_basename) + if venv_bin ~= nvim_py_bin then + errors[#errors + 1] = '$PATH yields this ' + .. py_bin_basename + .. ' executable: ' + .. nvim_py_bin + local hint = '$PATH ambiguities arise if the virtualenv is not ' + .. 'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, ' + .. 'check that invoking Python from the command line launches the correct one, ' + .. 'then relaunch Nvim.' + hints[hint] = true + end + if venv_bin ~= subshell_py_bin then + errors[#errors + 1] = '$PATH in subshells yields this ' + .. py_bin_basename + .. ' executable: ' + .. subshell_py_bin + local hint = '$PATH ambiguities in subshells typically are ' + .. 'caused by your shell config overriding the $PATH previously set by the ' + .. 'virtualenv. Either prevent them from doing so, or use this workaround: ' + .. 'https://vi.stackexchange.com/a/34996' + hints[hint] = true + end + end + else + errors[#errors + 1] = 'no Python executables found in the virtualenv ' + .. bin_dir + .. ' directory.' + end + + local msg = '$VIRTUAL_ENV is set to: ' .. virtual_env + if vim.tbl_count(errors) > 0 then + if vim.tbl_count(venv_bins) > 0 then + msg = string.format( + '%s\nAnd its %s directory contains: %s', + msg, + bin_dir, + table.concat( + vim.tbl_map(function(v) + return vim.fs.basename(v) + end, venv_bins), + ', ' + ) + ) + end + local conj = '\nBut ' + for _, err in ipairs(errors) do + msg = msg .. conj .. err + conj = '\nAnd ' + end + msg = msg .. '\nSo invoking Python may lead to unexpected results.' + health.warn(msg, vim.tbl_keys(hints)) + else + health.info(msg) + health.info( + 'Python version: ' + .. health._system( + 'python -c "import platform, sys; sys.stdout.write(platform.python_version())"' + ) + ) + health.ok('$VIRTUAL_ENV provides :!python.') + end +end + +local function ruby() + health.start('Ruby provider (optional)') + + if health._provider_disabled('ruby') then + return + end + + if vim.fn.executable('ruby') == 0 or vim.fn.executable('gem') == 0 then + health.warn( + '`ruby` and `gem` must be in $PATH.', + 'Install Ruby and verify that `ruby` and `gem` commands work.' + ) + return + end + health.info('Ruby: ' .. health._system({ 'ruby', '-v' })) + + local host, _ = vim.provider.ruby.detect() + if (not host) or host:find('^%s*$') then + health.warn('`neovim-ruby-host` not found.', { + 'Run `gem install neovim` to ensure the neovim RubyGem is installed.', + 'Run `gem environment` to ensure the gem bin directory is in $PATH.', + 'If you are using rvm/rbenv/chruby, try "rehashing".', + 'See :help g:ruby_host_prog for non-standard gem installations.', + 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', + }) + return + end + health.info('Host: ' .. host) + + local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$') + local ok, latest_gem = health._cmd_ok(vim.split(latest_gem_cmd, ' ')) + if not ok or latest_gem:find('^%s*$') then + health.error( + 'Failed to run: ' .. latest_gem_cmd, + { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } + ) + return + end + local gem_split = vim.split(latest_gem, [[neovim (\|, \|)$]]) + latest_gem = gem_split[1] or 'not found' + + local current_gem_cmd = { host, '--version' } + local current_gem + ok, current_gem = health._cmd_ok(current_gem_cmd) + if not ok then + health.error( + 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), + { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') } + ) + return + end + + if vim.version.lt(current_gem, latest_gem) then + local message = 'Gem "neovim" is out-of-date. Installed: ' + .. current_gem + .. ', latest: ' + .. latest_gem + health.warn(message, 'Run in shell: gem update neovim') + else + health.ok('Latest "neovim" gem is installed: ' .. current_gem) + end +end + +function M.check() + clipboard() + node() + perl() + python() + ruby() +end + +return M diff --git a/runtime/lua/vim/provider/node/health.lua b/runtime/lua/vim/provider/node/health.lua deleted file mode 100644 index b24a9e0061..0000000000 --- a/runtime/lua/vim/provider/node/health.lua +++ /dev/null @@ -1,109 +0,0 @@ -local health = vim.health -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' - -local M = {} - -function M.check() - health.start('Node.js provider (optional)') - - if health._provider_disabled('node') then - return - end - - if - vim.fn.executable('node') == 0 - or ( - vim.fn.executable('npm') == 0 - and vim.fn.executable('yarn') == 0 - and vim.fn.executable('pnpm') == 0 - ) - then - health.warn( - '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.', - 'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.' - ) - return - end - - -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' - local ok, node_v = health._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) - -- Skip further checks, they are nonsense if nodejs is too old. - return - end - if vim.fn['provider#node#can_inspect']() == 0 then - health.warn( - 'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.' - ) - end - - local node_detect_table = vim.fn['provider#node#Detect']() - local host = node_detect_table[1] - if host:find('^%s*$') then - health.warn('Missing "neovim" npm (or yarn, pnpm) package.', { - 'Run in shell: npm install -g neovim', - 'Run in shell (if you use yarn): yarn global add neovim', - 'Run in shell (if you use pnpm): pnpm install -g neovim', - 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim', - }) - return - end - health.info('Nvim node.js host: ' .. host) - - local manager = 'npm' - if vim.fn.executable('yarn') == 1 then - manager = 'yarn' - elseif vim.fn.executable('pnpm') == 1 then - manager = 'pnpm' - end - - local latest_npm_cmd = ( - 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, ' ')) - if not ok or latest_npm:find('^%s$') then - health.error( - 'Failed to run: ' .. latest_npm_cmd, - { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } - ) - return - end - - local pcall_ok, pkg_data = pcall(vim.json.decode, latest_npm) - if not pcall_ok then - return 'error: ' .. latest_npm - end - local latest_npm_subtable = pkg_data['dist-tags'] or {} - latest_npm = latest_npm_subtable['latest'] or 'unable to parse' - - local current_npm_cmd = { 'node', host, '--version' } - local current_npm - ok, current_npm = health._cmd_ok(current_npm_cmd) - if not ok then - health.error( - 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') } - ) - return - end - - if latest_npm ~= 'unable to parse' and vim.version.lt(current_npm, latest_npm) then - local message = 'Package "neovim" is out-of-date. Installed: ' - .. current_npm:gsub('%\n$', '') - .. ', latest: ' - .. latest_npm:gsub('%\n$', '') - - health.warn(message, { - 'Run in shell: npm install -g neovim', - 'Run in shell (if you use yarn): yarn global add neovim', - 'Run in shell (if you use pnpm): pnpm install -g neovim', - }) - else - health.ok('Latest "neovim" npm/yarn/pnpm package is installed: ' .. current_npm) - end -end - -return M diff --git a/runtime/lua/vim/provider/perl/health.lua b/runtime/lua/vim/provider/perl/health.lua deleted file mode 100644 index ffc0419b94..0000000000 --- a/runtime/lua/vim/provider/perl/health.lua +++ /dev/null @@ -1,90 +0,0 @@ -local health = vim.health - -local M = {} - -function M.check() - health.start('Perl provider (optional)') - - if health._provider_disabled('perl') then - return - end - - local perl_exec, perl_warnings = vim.provider.perl.detect() - - if not perl_exec then - health.warn(assert(perl_warnings), { - 'See :help provider-perl for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim', - }) - health.warn('No usable perl executable found') - return - end - - health.info('perl executable: ' .. perl_exec) - - -- 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', '' }) - if not ok then - return { perl_exec, '"App::cpanminus" module is not installed' } - end - - local latest_cpan_cmd = { - perl_exec, - '-MApp::cpanminus::fatscript', - '-e', - '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) - if not ok or latest_cpan:find('^%s*$') then - health.error( - 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), - { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } - ) - return - elseif latest_cpan[1] == '!' then - local cpanm_errs = vim.split(latest_cpan, '!') - if cpanm_errs[1]:find("Can't write to ") then - local advice = {} - for i = 2, #cpanm_errs do - advice[#advice + 1] = cpanm_errs[i] - end - - health.warn(cpanm_errs[1], advice) - -- Last line is the package info - latest_cpan = cpanm_errs[#cpanm_errs] - else - health.error('Unknown warning from command: ' .. latest_cpan_cmd, cpanm_errs) - return - end - end - latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]]) - if latest_cpan:find('^%s*$') then - health.error('Cannot parse version number from cpanm output: ' .. latest_cpan) - return - end - - 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) - if not ok then - health.error( - 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') } - ) - return - end - - if vim.version.lt(current_cpan, latest_cpan) then - local message = 'Module "Neovim::Ext" is out-of-date. Installed: ' - .. current_cpan - .. ', latest: ' - .. latest_cpan - health.warn(message, 'Run in shell: cpanm -n Neovim::Ext') - else - health.ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan) - end -end - -return M diff --git a/runtime/lua/vim/provider/python/health.lua b/runtime/lua/vim/provider/python/health.lua deleted file mode 100644 index 37ccb42da3..0000000000 --- a/runtime/lua/vim/provider/python/health.lua +++ /dev/null @@ -1,499 +0,0 @@ -local health = vim.health -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' - -local M = {} - -local function is(path, ty) - if not path then - return false - end - local stat = vim.loop.fs_stat(path) - if not stat then - return false - end - return stat.type == ty -end - --- Resolves Python executable path by invoking and checking `sys.executable`. -local function python_exepath(invocation) - local p = vim.system({ invocation, '-c', 'import sys; sys.stdout.write(sys.executable)' }):wait() - assert(p.code == 0, p.stderr) - 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. -local function check_for_pyenv() - local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) - - if pyenv_path == '' then - return { '', '' } - end - - health.info('pyenv: Path: ' .. pyenv_path) - - local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '') - - if pyenv_root == '' then - pyenv_root = vim.fn.system({ pyenv_path, 'root' }) - health.info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.') - end - - if not is(pyenv_root, 'directory') then - local message = string.format( - 'pyenv: Root does not exist: %s. Ignoring pyenv for all following checks.', - pyenv_root - ) - health.warn(message) - return { '', '' } - end - - health.info('pyenv: Root: ' .. pyenv_root) - - return { pyenv_path, pyenv_root } -end - --- Check the Python interpreter's usability. -local function check_bin(bin) - if not is(bin, 'file') and (not iswin or not is(bin .. '.exe', 'file')) then - health.error('"' .. bin .. '" was not found.') - return false - elseif vim.fn.executable(bin) == 0 then - health.error('"' .. bin .. '" is not executable.') - return false - end - return true -end - --- Fetch the contents of a URL. -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 }) - if rc ~= 0 then - return 'curl error with ' .. url .. ': ' .. rc - else - return out - end - elseif vim.fn.executable('python') == 1 then - local script = "try:\n\ - from urllib.request import urlopen\n\ - except ImportError:\n\ - from urllib2 import urlopen\n\ - response = urlopen('" .. url .. "')\n\ - print(response.read().decode('utf8'))\n" - local out, rc = health._system({ 'python', '-c', script }) - if out == '' and rc ~= 0 then - return 'python urllib.request error: ' .. rc - else - return out - end - end - - local message = 'missing `curl` ' - - if has_curl then - message = message .. '(with HTTPS support) ' - end - message = message .. 'and `python`, cannot make web request' - - return message -end - --- 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 - return 'error: ' .. pypi_response - end - - local pypi_element = pypi_data['info'] or {} - pypi_version = pypi_element['version'] or 'unable to parse' - end - return pypi_version -end - -local function is_bad_response(s) - local lower = s:lower() - return vim.startswith(lower, 'unable') - or vim.startswith(lower, 'error') - 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} --- } -local function version_info(python) - local pypi_version = latest_pypi_version() - - local python_version, rc = health._system({ - python, - '-c', - 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', - }) - - if rc ~= 0 or python_version == '' then - python_version = 'unable to parse ' .. python .. ' response' - end - - local nvim_path - nvim_path, rc = health._system({ - python, - '-c', - 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', - }) - if rc ~= 0 or nvim_path == '' then - return { python_version, 'unable to load neovim Python module', pypi_version, nvim_path } - end - - -- Assuming that multiple versions of a package are installed, sort them - -- numerically in descending order. - local function compare(metapath1, metapath2) - local a = vim.fn.matchstr(vim.fn.fnamemodify(metapath1, ':p:h:t'), [[[0-9.]\+]]) - local b = vim.fn.matchstr(vim.fn.fnamemodify(metapath2, ':p:h:t'), [[[0-9.]\+]]) - if a == b then - return 0 - elseif a > b then - return 1 - else - return -1 - end - end - - -- Try to get neovim.VERSION (added in 0.1.11dev). - local nvim_version - nvim_version, rc = health._system({ - python, - '-c', - 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', - }, { stderr = true, ignore_error = true }) - 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)) - metas = table.sort(metas, compare) - - if metas and next(metas) ~= nil then - for line in io.lines(metas[1]) do - local version = line:match('^Version: (%S+)') - if version then - nvim_version = version - break - end - end - end - end - - local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]]) - local version_status = 'unknown; ' .. nvim_path_base - if is_bad_response(nvim_version) and is_bad_response(pypi_version) then - if vim.version.lt(nvim_version, pypi_version) then - version_status = 'outdated; from ' .. nvim_path_base - else - version_status = 'up to date' - end - end - - return { python_version, nvim_version, pypi_version, version_status } -end - -function M.check() - 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 = {} - - if health._provider_disabled(pyname) then - return - end - - local pyenv_table = check_for_pyenv() - local pyenv = pyenv_table[1] - local pyenv_root = pyenv_table[2] - - if vim.g[host_prog_var] then - local message = string.format('Using: g:%s = "%s"', host_prog_var, vim.g[host_prog_var]) - health.info(message) - end - - local pythonx_warnings - pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') - - if not pyname then - health.warn( - 'No Python executable found that can `import neovim`. ' - .. 'Using the first available executable for diagnostics.' - ) - elseif vim.g[host_prog_var] then - python_exe = pyname - end - - -- No Python executable could `import neovim`, or host_prog_var was used. - if pythonx_warnings then - health.warn(pythonx_warnings, { - 'See :help provider-python for more information.', - 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim', - }) - elseif pyname and pyname ~= '' and python_exe == '' then - if not vim.g[host_prog_var] then - local message = string.format( - '`g:%s` is not set. Searching for %s in the environment.', - host_prog_var, - pyname - ) - health.info(message) - end - - if pyenv ~= '' then - python_exe = health._system({ pyenv, 'which', pyname }, { stderr = true }) - if python_exe == '' then - health.warn('pyenv could not find ' .. pyname .. '.') - end - end - - if python_exe == '' then - python_exe = vim.fn.exepath(pyname) - - if os.getenv('PATH') then - local path_sep = iswin and ';' or ':' - local paths = vim.split(os.getenv('PATH') or '', path_sep) - - for _, path in ipairs(paths) do - local path_bin = vim.fs.normalize(path .. '/' .. pyname) - if - path_bin ~= vim.fs.normalize(python_exe) - and vim.tbl_contains(python_multiple, path_bin) - and vim.fn.executable(path_bin) == 1 - then - python_multiple[#python_multiple + 1] = path_bin - end - end - - if vim.tbl_count(python_multiple) > 0 then - -- This is worth noting since the user may install something - -- that changes $PATH, like homebrew. - local message = string.format( - 'Multiple %s executables found. Set `g:%s` to avoid surprises.', - pyname, - host_prog_var - ) - health.info(message) - end - - if python_exe:find('shims') then - local message = string.format('`%s` appears to be a pyenv shim.', python_exe) - local advice = string.format( - '`pyenv` is not in $PATH, your pyenv installation is broken. Set `g:%s` to avoid surprises.', - host_prog_var - ) - health.warn(message, advice) - end - end - end - end - - if python_exe ~= '' and not vim.g[host_prog_var] then - if - venv == '' - and pyenv ~= '' - and pyenv_root ~= '' - and vim.startswith(vim.fn.resolve(python_exe), pyenv_root .. '/') - then - local advice = string.format( - 'Create a virtualenv specifically for Nvim using pyenv, and set `g:%s`. This will avoid the need to install the pynvim module in each version/virtualenv.', - host_prog_var - ) - 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 - - if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then - local advice = string.format( - 'Create a virtualenv specifically for Nvim and use `g:%s`. This will avoid the need to install the pynvim module in each virtualenv.', - host_prog_var - ) - health.warn('Your virtualenv is not set up optimally.', advice) - end - end - end - - if pyname and python_exe == '' and pyname ~= '' then - -- An error message should have already printed. - health.error('`' .. pyname .. '` was not found.') - elseif python_exe ~= '' and not check_bin(python_exe) then - python_exe = '' - end - - -- Diagnostic output - health.info('Executable: ' .. (python_exe == '' and 'Not found' or python_exe)) - if vim.tbl_count(python_multiple) > 0 then - for _, path_bin in ipairs(python_multiple) do - health.info('Other python executable: ' .. path_bin) - end - end - - if python_exe == '' then - -- No Python executable can import 'neovim'. Check if any Python executable - -- can import 'pynvim'. If so, that Python failed to import 'neovim' as - -- well, which is most probably due to a failed pip upgrade: - -- https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 - local pynvim_exe = vim.provider.python.detect_by_module('pynvim') - if pynvim_exe then - local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": ' - .. pynvim_exe - local advice = { - 'Use that Python version to reinstall "pynvim" and optionally "neovim".', - pynvim_exe .. ' -m pip uninstall pynvim neovim', - pynvim_exe .. ' -m pip install pynvim', - pynvim_exe .. ' -m pip install neovim # only if needed by third-party software', - } - health.error(message, advice) - end - else - local version_info_table = version_info(python_exe) - local pyversion = version_info_table[1] - local current = version_info_table[2] - local latest = version_info_table[3] - local status = version_info_table[4] - - if not vim.version.range('~3'):has(pyversion) then - health.warn('Unexpected Python version. This could lead to confusing error messages.') - end - - health.info('Python version: ' .. pyversion) - - if is_bad_response(status) then - health.info('pynvim version: ' .. current .. ' (' .. status .. ')') - else - health.info('pynvim version: ' .. current) - end - - if is_bad_response(current) then - health.error( - 'pynvim is not installed.\nError: ' .. current, - 'Run in shell: ' .. python_exe .. ' -m pip install pynvim' - ) - end - - if is_bad_response(latest) then - health.warn('Could not contact PyPI to get latest version.') - health.error('HTTP request failed: ' .. latest) - elseif is_bad_response(status) then - health.warn('Latest pynvim is NOT installed: ' .. latest) - elseif not is_bad_response(current) then - health.ok('Latest pynvim is installed.') - end - end - - health.start('Python virtualenv') - if not virtual_env then - health.ok('no $VIRTUAL_ENV') - return - end - local errors = {} - -- Keep hints as dict keys in order to discard duplicates. - local hints = {} - -- 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. - local bin_dir = iswin and 'Scripts' or 'bin' - local venv_bins = vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true) - venv_bins = vim.tbl_filter(function(v) - -- XXX: Remove irrelevant executables found in bin/. - return not v:match('python%-config') - end, venv_bins) - if vim.tbl_count(venv_bins) > 0 then - for _, venv_bin in pairs(venv_bins) do - venv_bin = vim.fs.normalize(venv_bin) - local py_bin_basename = vim.fs.basename(venv_bin) - local nvim_py_bin = python_exepath(vim.fn.exepath(py_bin_basename)) - local subshell_py_bin = python_exepath(py_bin_basename) - if venv_bin ~= nvim_py_bin then - errors[#errors + 1] = '$PATH yields this ' - .. py_bin_basename - .. ' executable: ' - .. nvim_py_bin - local hint = '$PATH ambiguities arise if the virtualenv is not ' - .. 'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, ' - .. 'check that invoking Python from the command line launches the correct one, ' - .. 'then relaunch Nvim.' - hints[hint] = true - end - if venv_bin ~= subshell_py_bin then - errors[#errors + 1] = '$PATH in subshells yields this ' - .. py_bin_basename - .. ' executable: ' - .. subshell_py_bin - local hint = '$PATH ambiguities in subshells typically are ' - .. 'caused by your shell config overriding the $PATH previously set by the ' - .. 'virtualenv. Either prevent them from doing so, or use this workaround: ' - .. 'https://vi.stackexchange.com/a/34996' - hints[hint] = true - end - end - else - errors[#errors + 1] = 'no Python executables found in the virtualenv ' - .. bin_dir - .. ' directory.' - end - - local msg = '$VIRTUAL_ENV is set to: ' .. virtual_env - if vim.tbl_count(errors) > 0 then - if vim.tbl_count(venv_bins) > 0 then - msg = string.format( - '%s\nAnd its %s directory contains: %s', - msg, - bin_dir, - table.concat( - vim.tbl_map(function(v) - return vim.fs.basename(v) - end, venv_bins), - ', ' - ) - ) - end - local conj = '\nBut ' - for _, err in ipairs(errors) do - msg = msg .. conj .. err - conj = '\nAnd ' - end - msg = msg .. '\nSo invoking Python may lead to unexpected results.' - health.warn(msg, vim.tbl_keys(hints)) - else - health.info(msg) - health.info( - 'Python version: ' - .. health._system( - 'python -c "import platform, sys; sys.stdout.write(platform.python_version())"' - ) - ) - health.ok('$VIRTUAL_ENV provides :!python.') - end -end - -return M diff --git a/runtime/lua/vim/provider/ruby/health.lua b/runtime/lua/vim/provider/ruby/health.lua deleted file mode 100644 index 80932e1bb0..0000000000 --- a/runtime/lua/vim/provider/ruby/health.lua +++ /dev/null @@ -1,69 +0,0 @@ -local health = vim.health -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' - -local M = {} - -function M.check() - health.start('Ruby provider (optional)') - - if health._provider_disabled('ruby') then - return - end - - if vim.fn.executable('ruby') == 0 or vim.fn.executable('gem') == 0 then - health.warn( - '`ruby` and `gem` must be in $PATH.', - 'Install Ruby and verify that `ruby` and `gem` commands work.' - ) - return - end - health.info('Ruby: ' .. health._system({ 'ruby', '-v' })) - - local host, _ = vim.provider.ruby.detect() - if (not host) or host:find('^%s*$') then - health.warn('`neovim-ruby-host` not found.', { - 'Run `gem install neovim` to ensure the neovim RubyGem is installed.', - 'Run `gem environment` to ensure the gem bin directory is in $PATH.', - 'If you are using rvm/rbenv/chruby, try "rehashing".', - 'See :help g:ruby_host_prog for non-standard gem installations.', - 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim', - }) - return - end - health.info('Host: ' .. host) - - local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$') - local ok, latest_gem = health._cmd_ok(vim.split(latest_gem_cmd, ' ')) - if not ok or latest_gem:find('^%s*$') then - health.error( - 'Failed to run: ' .. latest_gem_cmd, - { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' } - ) - return - end - local gem_split = vim.split(latest_gem, [[neovim (\|, \|)$]]) - latest_gem = gem_split[1] or 'not found' - - local current_gem_cmd = { host, '--version' } - local current_gem - ok, current_gem = health._cmd_ok(current_gem_cmd) - if not ok then - health.error( - 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), - { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') } - ) - return - end - - if vim.version.lt(current_gem, latest_gem) then - local message = 'Gem "neovim" is out-of-date. Installed: ' - .. current_gem - .. ', latest: ' - .. latest_gem - health.warn(message, 'Run in shell: gem update neovim') - else - health.ok('Latest "neovim" gem is installed: ' .. current_gem) - end -end - -return M -- cgit From 5cbd6d9b9f232a6ff22ae3a9af80075404226e4b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 23 May 2024 06:08:24 +0800 Subject: vim-patch:9.1.0430: getregionpos() doesn't handle one char selection (#28924) Problem: getregionpos() doesn't handle one char selection. Solution: Handle startspaces differently when is_oneChar is set. Also add a test for an exclusive charwise selection with multibyte chars (zeertzjq) closes: vim/vim#14825 https://github.com/vim/vim/commit/52a6f348874778cf315b47d9e8b5f818f4b97277 --- runtime/lua/vim/_meta/vimfn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index dee65a40c7..e00b2e6acd 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3596,8 +3596,8 @@ function vim.fn.getregion(pos1, pos2, opts) end --- the offset in screen columns from the start of the character. --- E.g., a position within a or after the last character. --- If the "off" number of an ending position is non-zero, it is ---- the character's number of cells included in the selection, ---- otherwise the whole character is included. +--- the offset of the character's first cell not included in the +--- selection, otherwise all its cells are included. --- --- @param pos1 table --- @param pos2 table -- cgit From 5ac8db10f0428c976dfc9a4935a74d0fe160995c Mon Sep 17 00:00:00 2001 From: Andre Toerien <49614525+AThePeanut4@users.noreply.github.com> Date: Thu, 23 May 2024 12:03:47 +0200 Subject: fix(lsp): trigger LspDetach on buffer delete (#28795) Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index ad1794e98d..700c10abc8 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -576,10 +576,19 @@ local function buf_attach(bufnr) on_detach = function() local params = { textDocument = { uri = uri } } for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client.id }, + }) + changetracking.reset_buf(client, bufnr) if client.supports_method(ms.textDocument_didClose) then client.notify(ms.textDocument_didClose, params) end + + local namespace = lsp.diagnostic.get_namespace(client.id) + vim.diagnostic.reset(namespace, bufnr) end for _, client in ipairs(all_clients) do client.attached_buffers[bufnr] = nil -- cgit From 2908f71dc9e9591f97e0f9d70dbc8d8b18f9e475 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 23 May 2024 15:17:03 +0200 Subject: refactor(lsp): reuse buf_detach_client logic in on_detach (#28939) --- runtime/lua/vim/lsp.lua | 68 +++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 39 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 700c10abc8..1592fd3151 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -501,6 +501,30 @@ local function text_document_did_save_handler(bufnr) end end +---@param bufnr integer resolved buffer +---@param client vim.lsp.Client +local function buf_detach_client(bufnr, client) + api.nvim_exec_autocmds('LspDetach', { + buffer = bufnr, + modeline = false, + data = { client_id = client.id }, + }) + + changetracking.reset_buf(client, bufnr) + + if client.supports_method(ms.textDocument_didClose) then + local uri = vim.uri_from_bufnr(bufnr) + local params = { textDocument = { uri = uri } } + client.notify(ms.textDocument_didClose, params) + end + + client.attached_buffers[bufnr] = nil + util.buf_versions[bufnr] = nil + + local namespace = lsp.diagnostic.get_namespace(client.id) + vim.diagnostic.reset(namespace, bufnr) +end + --- @type table local attached_buffers = {} @@ -574,26 +598,10 @@ local function buf_attach(bufnr) end, on_detach = function() - local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client.id }, - }) - - changetracking.reset_buf(client, bufnr) - if client.supports_method(ms.textDocument_didClose) then - client.notify(ms.textDocument_didClose, params) - end - - local namespace = lsp.diagnostic.get_namespace(client.id) - vim.diagnostic.reset(namespace, bufnr) - end - for _, client in ipairs(all_clients) do - client.attached_buffers[bufnr] = nil + local clients = lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) + for _, client in ipairs(clients) do + buf_detach_client(bufnr, client) end - util.buf_versions[bufnr] = nil attached_buffers[bufnr] = nil end, @@ -668,27 +676,9 @@ function lsp.buf_detach_client(bufnr, client_id) ) ) return + else + buf_detach_client(bufnr, client) end - - api.nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) - - changetracking.reset_buf(client, bufnr) - - if client.supports_method(ms.textDocument_didClose) then - local uri = vim.uri_from_bufnr(bufnr) - local params = { textDocument = { uri = uri } } - client.notify(ms.textDocument_didClose, params) - end - - client.attached_buffers[bufnr] = nil - util.buf_versions[bufnr] = nil - - local namespace = lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace, bufnr) end --- Checks if a buffer is attached for a particular client. -- cgit From af200c10cf9d117a14ebf9f2e9c666721a1090d6 Mon Sep 17 00:00:00 2001 From: Ilia Choly Date: Thu, 23 May 2024 09:17:53 -0400 Subject: fix(lsp): check if buffer was detached in on_init callback (#28914) Co-authored-by: Jongwook Choi --- runtime/lua/vim/lsp/client.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 8fb5879e9b..4beb7fefda 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -612,7 +612,10 @@ function Client:initialize() self:_run_callbacks(self._on_init_cbs, lsp.client_errors.ON_INIT_CALLBACK_ERROR, self, result) for buf in pairs(reattach_bufs) do - self:_on_attach(buf) + -- The buffer may have been detached in the on_init callback. + if self.attached_buffers[buf] then + self:_on_attach(buf) + end end log.info( -- cgit From 0a2218f965ac8cd7967d33b8c52e5b06fb284aac Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 23 May 2024 23:30:53 +0300 Subject: fix(comment): fall back to using trimmed comment markers (#28938) Problem: Currently comment detection, addition, and removal are done by matching 'commentstring' exactly. This has the downside when users want to add comment markers with space (like with `-- %s` commentstring) but also be able to uncomment lines that do not contain space (like `--aaa`). Solution: Use the following approach: - Line is commented if it matches 'commentstring' with trimmed parts. - Adding comment is 100% relying on 'commentstring' parts (as is now). - Removing comment is first trying exact 'commentstring' parts with fallback on trying its trimmed parts. --- runtime/lua/vim/_comment.lua | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index b6cb6c9884..044cd69716 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -77,14 +77,11 @@ local function make_comment_check(parts) local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) -- Commented line has the following structure: - -- - local nonblank_regex = '^%s-' .. l_esc .. '.*' .. r_esc .. '%s-$' - - -- Commented blank line can have any amount of whitespace around parts - local blank_regex = '^%s-' .. vim.trim(l_esc) .. '%s*' .. vim.trim(r_esc) .. '%s-$' + -- + local regex = '^%s-' .. vim.trim(l_esc) .. '.*' .. vim.trim(r_esc) .. '%s-$' return function(line) - return line:find(nonblank_regex) ~= nil or line:find(blank_regex) ~= nil + return line:find(regex) ~= nil end end @@ -153,14 +150,14 @@ end ---@return fun(line: string): string local function make_uncomment_function(parts) local l_esc, r_esc = vim.pesc(parts.left), vim.pesc(parts.right) - local nonblank_regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$' - local blank_regex = '^(%s*)' .. vim.trim(l_esc) .. '(%s*)' .. vim.trim(r_esc) .. '(%s-)$' + local regex = '^(%s*)' .. l_esc .. '(.*)' .. r_esc .. '(%s-)$' + local regex_trimmed = '^(%s*)' .. vim.trim(l_esc) .. '(.*)' .. vim.trim(r_esc) .. '(%s-)$' return function(line) - -- Try both non-blank and blank regexes - local indent, new_line, trail = line:match(nonblank_regex) + -- Try regex with exact comment parts first, fall back to trimmed parts + local indent, new_line, trail = line:match(regex) if new_line == nil then - indent, new_line, trail = line:match(blank_regex) + indent, new_line, trail = line:match(regex_trimmed) end -- Return original if line is not commented -- cgit From 0d3d198109ff971c1537dc5361437d0397dba7af Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 23 May 2024 22:51:57 +0200 Subject: vim-patch:9.1.0435: filetype: cygport files are not recognized Problem: filetype: cygport files are not recognized Solution: Recognize '*.cygport' files as sh filetype (Ken Takata) https://cygwin.github.io/cygport/cygport_in.html closes: vim/vim#14833 https://github.com/vim/vim/commit/cd79f8fbd34cdb918153d9fa3821eb4092b7b5fc Co-authored-by: K.Takata --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index b158cd83ba..d1fdd0aa16 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -948,6 +948,7 @@ local extension = { sexp = 'sexplib', bash = detect.bash, bats = detect.bash, + cygport = detect.bash, ebuild = detect.bash, eclass = detect.bash, env = detect.sh, -- cgit From cd05fbef170b29083973fd11170d25225feb8bed Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Fri, 24 May 2024 15:44:52 +0800 Subject: vim-patch:9.1.0441: getregionpos() can't properly indicate positions beyond eol (#28957) Problem: getregionpos() can't properly indicate positions beyond eol. Solution: Add an "eol" flag that enables handling positions beyond end of line like getpos() does (zeertzjq). Also fix the problem that a position still has the coladd beyond the end of the line when its column has been clamped. In the last test case with TABs at the end of the line the old behavior is obviously wrong. I decided to gate this behind a flag because returning positions that don't correspond to actual characters in the line may lead to mistakes for callers that want to calculate the length of the selected text, so the behavior is only enabled if the caller wants it. closes: vim/vim#14838 https://github.com/vim/vim/commit/2b09de910458247b70751928217422c38fd5abf8 --- runtime/lua/vim/_meta/vimfn.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index e00b2e6acd..f4daacfb7d 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -3599,6 +3599,19 @@ function vim.fn.getregion(pos1, pos2, opts) end --- the offset of the character's first cell not included in the --- selection, otherwise all its cells are included. --- +--- Apart from the options supported by |getregion()|, {opts} also +--- supports the following: +--- +--- eol If |TRUE|, indicate positions beyond +--- the end of a line with "col" values +--- one more than the length of the line. +--- If |FALSE|, positions are limited +--- within their lines, and if a line is +--- empty or the selection is entirely +--- beyond the end of a line, a "col" +--- value of 0 is used for both positions. +--- (default: |FALSE|) +--- --- @param pos1 table --- @param pos2 table --- @param opts? table -- cgit From 6dc62c2e2b413a9bbaf0746660c5638936303249 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Thu, 23 May 2024 11:40:51 +0200 Subject: docs: extract health to its own file --- runtime/lua/vim/health.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 263a6ec85f..2417709ea2 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -39,7 +39,7 @@ --- :checkhealth vim* --- < --- ---- Create a healthcheck *health-dev* +--- Create a healthcheck *health-dev* *vim.health* --- --- Healthchecks are functions that check the user environment, configuration, or --- any other prerequisites that a plugin cares about. Nvim ships with -- cgit From f864b68c5b0fe1482249167712cd16ff2b50ec45 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 3 May 2024 20:32:06 +0200 Subject: feat: allow gx to function for markdown links In other words, `gx` works regardless of where it was used in `[...](https://...)`. This only works on markdown buffers. Co-authored-by: ribru17 --- runtime/lua/vim/_defaults.lua | 15 +++++++++------ runtime/lua/vim/ui.lua | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 4684902410..ede634636e 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -107,22 +107,25 @@ do vim.inspect(cmd.cmd) ) end - - if err then - vim.notify(err, vim.log.levels.ERROR) - end + return err end local gx_desc = 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)' vim.keymap.set({ 'n' }, 'gx', function() - do_open(vim.fn.expand('')) + local err = do_open(require('vim.ui')._get_url()) + if err then + vim.notify(err, vim.log.levels.ERROR) + end end, { desc = gx_desc }) vim.keymap.set({ 'x' }, 'gx', function() local lines = vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() }) -- Trim whitespace on each line and concatenate. - do_open(table.concat(vim.iter(lines):map(vim.trim):totable())) + local err = do_open(table.concat(vim.iter(lines):map(vim.trim):totable())) + if err then + vim.notify(err, vim.log.levels.ERROR) + end end, { desc = gx_desc }) end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 3c947c51b0..99b9b78e2a 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -162,4 +162,24 @@ function M.open(path) return vim.system(cmd, { text = true, detach = true }), nil end +--- Gets the URL at cursor, if any. +function M._get_url() + if vim.bo.filetype == 'markdown' then + local range = vim.api.nvim_win_get_cursor(0) + vim.treesitter.get_parser():parse(range) + -- marking the node as `markdown_inline` is required. Setting it to `markdown` does not + -- work. + local current_node = vim.treesitter.get_node { lang = 'markdown_inline' } + while current_node do + local type = current_node:type() + if type == 'inline_link' or type == 'image' then + local child = assert(current_node:named_child(1)) + return vim.treesitter.get_node_text(child, 0) + end + current_node = current_node:parent() + end + end + return vim.fn.expand('') +end + return M -- cgit From d123202ae6ef3f046d5b6579c194dca82ddb8a8f Mon Sep 17 00:00:00 2001 From: dundargoc Date: Thu, 16 May 2024 18:33:09 +0200 Subject: fix: change deprecation presentation Deprecation with vim.deprecate is currently too noisy. Show the following warning instead: [function] is deprecated. Run ":checkhealth vim.deprecated" for more information. The important part is that the full message needs to be short enough to fit in one line in order to not trigger the "Press ENTER or type command to continue" prompt. The full information and stack trace for the deprecated functions will be shown in the new healthcheck `vim.deprecated`. --- runtime/lua/vim/_editor.lua | 76 +++++++++++++++++++++++++---------- runtime/lua/vim/deprecated/health.lua | 42 +++++++++++++++++++ runtime/lua/vim/diagnostic.lua | 4 +- 3 files changed, 98 insertions(+), 24 deletions(-) create mode 100644 runtime/lua/vim/deprecated/health.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index ad2b3f5dab..5e9be509c8 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1031,6 +1031,42 @@ function vim._cs_remote(rcid, server_addr, connect_error, args) } end +do + local function truncated_echo(msg) + -- Truncate message to avoid hit-enter-prompt + local max_width = vim.o.columns * math.max(vim.o.cmdheight - 1, 0) + vim.v.echospace + local msg_truncated = string.sub(msg, 1, max_width) + vim.api.nvim_echo({ { msg_truncated, 'WarningMsg' } }, true, {}) + end + + local notified = false + + function vim._truncated_echo_once(msg) + if not notified then + truncated_echo(msg) + notified = true + return true + end + return false + end +end + +--- This is basically the same as debug.traceback(), except the full paths are shown. +local function traceback() + local level = 4 + local backtrace = { 'stack traceback:' } + while true do + local info = debug.getinfo(level, 'Sl') + if not info then + break + end + local msg = (' %s:%s'):format(info.source:sub(2), info.currentline) + table.insert(backtrace, msg) + level = level + 1 + end + return table.concat(backtrace, '\n') +end + --- Shows a deprecation message to the user. --- ---@param name string Deprecated feature (function, API, etc.). @@ -1043,12 +1079,12 @@ end ---@return string|nil # Deprecated message, or nil if no message was shown. function vim.deprecate(name, alternative, version, plugin, backtrace) plugin = plugin or 'Nvim' - local will_be_removed = 'will be removed' - - -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md. - -- Example: if removal_version is 0.12 (soft-deprecated since 0.10-dev), show warnings starting at - -- 0.11, including 0.11-dev if plugin == 'Nvim' then + require('vim.deprecated.health').add(name, version, traceback(), alternative) + + -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md. + -- Example: if removal_version is 0.12 (soft-deprecated since 0.10-dev), show warnings starting at + -- 0.11, including 0.11-dev local major, minor = version:match('(%d+)%.(%d+)') major, minor = tonumber(major), tonumber(minor) @@ -1059,8 +1095,12 @@ function vim.deprecate(name, alternative, version, plugin, backtrace) return end - local removal_version = string.format('nvim-%d.%d', major, minor) - will_be_removed = vim.fn.has(removal_version) == 1 and 'was removed' or will_be_removed + local msg = ('%s is deprecated. Run ":checkhealth vim.deprecated" for more information'):format( + name + ) + + local displayed = vim._truncated_echo_once(msg) + return displayed and msg or nil else vim.validate { name = { name, 'string' }, @@ -1068,22 +1108,16 @@ function vim.deprecate(name, alternative, version, plugin, backtrace) version = { version, 'string', true }, plugin = { plugin, 'string', true }, } - end - local msg = ('%s is deprecated'):format(name) - msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.') - msg = ('%s%s\nFeature %s in %s %s'):format( - msg, - (plugin == 'Nvim' and ' :help deprecated' or ''), - will_be_removed, - plugin, - version - ) - local displayed = vim.notify_once(msg, vim.log.levels.WARN) - if displayed and backtrace ~= false then - vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN) + local msg = ('%s is deprecated'):format(name) + msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.') + msg = ('%s\nFeature will be removed in %s %s'):format(msg, plugin, version) + local displayed = vim.notify_once(msg, vim.log.levels.WARN) + if displayed and backtrace ~= false then + vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN) + end + return displayed and msg or nil end - return displayed and msg or nil end require('vim._options') diff --git a/runtime/lua/vim/deprecated/health.lua b/runtime/lua/vim/deprecated/health.lua new file mode 100644 index 0000000000..0f6b1f578c --- /dev/null +++ b/runtime/lua/vim/deprecated/health.lua @@ -0,0 +1,42 @@ +local M = {} +local health = vim.health + +local deprecated = {} + +function M.check() + if next(deprecated) == nil then + health.ok('No deprecated functions detected') + return + end + + for name, v in vim.spairs(deprecated) do + health.start('') + + local version, backtraces, alternative = v[1], v[2], v[3] + local major, minor = version:match('(%d+)%.(%d+)') + major, minor = tonumber(major), tonumber(minor) + local removal_version = string.format('nvim-%d.%d', major, minor) + local will_be_removed = vim.fn.has(removal_version) == 1 and 'was removed' or 'will be removed' + + local msg = ('%s is deprecated. Feature %s in Nvim %s'):format(name, will_be_removed, version) + local msg_alternative = alternative and ('use %s instead.'):format(alternative) + local advice = { msg_alternative } + table.insert(advice, backtraces) + advice = vim.iter(advice):flatten():totable() + health.warn(msg, advice) + end +end + +function M.add(name, version, backtrace, alternative) + if deprecated[name] == nil then + deprecated[name] = { version, { backtrace }, alternative } + return + end + + local it = vim.iter(deprecated[name][2]) + if it:find(backtrace) == nil then + table.insert(deprecated[name][2], backtrace) + end +end + +return M diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index b5146e5beb..348204abb7 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1279,9 +1279,7 @@ M.handlers.signs = { vim.deprecate( 'Defining diagnostic signs with :sign-define or sign_define()', 'vim.diagnostic.config()', - '0.12', - nil, - false -- suppress backtrace + '0.12' ) if not opts.signs.text then -- cgit From a616272f568a9492580abfd22ab460457ecdbfa3 Mon Sep 17 00:00:00 2001 From: Famiu Haque Date: Fri, 24 May 2024 15:57:46 +0600 Subject: feat(complete): specify reason for CompleteDone Problem: `CompleteDone` currently does not specify the reason for why completion was done, which is problematic for completion plugins as they cannot know whether the event was triggered due to the completion being canceled, accepted, or for some other reason. Solution: Add a `reason` key to `v:event`, which is set by `CompleteDone` to indicate why completion ended. --- runtime/lua/vim/_meta/vvars.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua index 1660d1dd6c..e00402ab3f 100644 --- a/runtime/lua/vim/_meta/vvars.lua +++ b/runtime/lua/vim/_meta/vvars.lua @@ -195,6 +195,7 @@ vim.v.errors = ... --- changed_window Is `v:true` if the event fired while --- changing window (or tab) on `DirChanged`. --- status Job status or exit code, -1 means "unknown". `TermClose` +--- reason Reason for completion being done. `CompleteDone` --- @type any vim.v.event = ... -- cgit From 1a2e6ebc59821fc10a02dae87e3524dbf32b7b33 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Fri, 24 May 2024 15:51:18 +0200 Subject: refactor: replace deprecated vim.loop with vim.uv --- runtime/lua/vim/health.lua | 4 ++-- runtime/lua/vim/provider/health.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 2417709ea2..f40f04a064 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -349,7 +349,7 @@ function M._system(cmd, args) if jobid < 1 then local message = - string.format('Command error (job=%d): %s (in %s)', jobid, shellify(cmd), vim.loop.cwd()) + string.format('Command error (job=%d): %s (in %s)', jobid, shellify(cmd), vim.uv.cwd()) error(message) return opts.output, 1 end @@ -368,7 +368,7 @@ function M._system(cmd, args) jobid, shell_error_code, shellify(cmd), - vim.loop.cwd() + vim.uv.cwd() ) if opts.output:find('%S') then emsg = string.format('%s\noutput: %s', emsg, opts.output) diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index edf813e96f..63e0da448a 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -1,5 +1,5 @@ local health = vim.health -local iswin = vim.loop.os_uname().sysname == 'Windows_NT' +local iswin = vim.uv.os_uname().sysname == 'Windows_NT' local M = {} @@ -229,7 +229,7 @@ local function is(path, ty) if not path then return false end - local stat = vim.loop.fs_stat(path) + local stat = vim.uv.fs_stat(path) if not stat then return false end -- cgit From e71713ba2b5cb4e3b725d162b2dd43e35975eead Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 24 May 2024 10:44:02 -0500 Subject: fix: show swapfile warning as a warning (#28971) The new default SwapExists autocommand displays warning text (W325) but does not use the WarningMsg highlight group as other warnings do. Use the WARN log level when displaying this warning. --- runtime/lua/vim/_defaults.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index ede634636e..7015d376ae 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -269,7 +269,10 @@ do return end vim.v.swapchoice = 'e' -- Choose "(E)dit". - vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid)) + vim.notify( + ('W325: Ignoring swapfile from Nvim process %d'):format(info.pid), + vim.log.levels.WARN + ) end, }) -- cgit From 206f8f24a2470f961cfe7e7c177443c0f199231c Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 24 May 2024 10:48:32 -0500 Subject: fix(fs): make vim.fs.root work for relative paths and unnamed buffers (#28964) If a buffer does not have a backing file then fall back to the current working directory. --- runtime/lua/vim/fs.lua | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index ce533ad0a9..b05220ee2c 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -328,8 +328,11 @@ function M.find(names, opts) return matches end ---- Find the first parent directory containing a specific "marker", relative to a buffer's ---- directory. +--- Find the first parent directory containing a specific "marker", relative to a file path or +--- buffer. +--- +--- If the buffer is unnamed (has no backing file) or has a non-empty 'buftype' then the search +--- begins from Nvim's |current-directory|. --- --- Example: --- @@ -346,13 +349,13 @@ end --- end) --- ``` --- ---- @param source integer|string Buffer number (0 for current buffer) or file path to begin the ---- search from. +--- @param source integer|string Buffer number (0 for current buffer) or file path (absolute or +--- relative to the |current-directory|) to begin the search from. --- @param marker (string|string[]|fun(name: string, path: string): boolean) A marker, or list --- of markers, to search for. If a function, the function is called for each --- evaluated item and should return true if {name} and {path} are a match. --- @return string? # Directory path containing one of the given markers, or nil if no directory was ---- found. +--- found. function M.root(source, marker) assert(source, 'missing required argument: source') assert(marker, 'missing required argument: marker') @@ -361,14 +364,18 @@ function M.root(source, marker) if type(source) == 'string' then path = source elseif type(source) == 'number' then - path = vim.api.nvim_buf_get_name(source) + if vim.bo[source].buftype ~= '' then + path = assert(vim.uv.cwd()) + else + path = vim.api.nvim_buf_get_name(source) + end else error('invalid type for argument "source": expected string or buffer number') end local paths = M.find(marker, { upward = true, - path = path, + path = vim.fn.fnamemodify(path, ':p:h'), }) if #paths == 0 then -- cgit From 2c6b6358722b2df9160c3739b0cea07e8779513f Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 24 May 2024 11:33:49 -0500 Subject: feat(defaults): add LSP default mappings (again) (#28650) --- runtime/lua/vim/_defaults.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 7015d376ae..5b964b84a0 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -149,6 +149,31 @@ do vim.keymap.set({ 'o' }, 'gc', textobject_rhs, { desc = 'Comment textobject' }) end + --- Default maps for LSP functions. + --- + --- These are mapped unconditionally to avoid different behavior depending on whether an LSP + --- client is attached. If no client is attached, or if a server does not support a capability, an + --- error message is displayed rather than exhibiting different behavior. + --- + --- See |grr|, |grn|, |gra|, |i_CTRL-S|. + do + vim.keymap.set('n', 'grn', function() + vim.lsp.buf.rename() + end, { desc = 'vim.lsp.buf.rename()' }) + + vim.keymap.set({ 'n', 'x' }, 'gra', function() + vim.lsp.buf.code_action() + end, { desc = 'vim.lsp.buf.code_action()' }) + + vim.keymap.set('n', 'grr', function() + vim.lsp.buf.references() + end, { desc = 'vim.lsp.buf.references()' }) + + vim.keymap.set('i', '', function() + vim.lsp.buf.signature_help() + end, { desc = 'vim.lsp.buf.signature_help()' }) + end + --- Map [d and ]d to move to the previous/next diagnostic. Map d to open a floating window --- for the diagnostic under the cursor. --- -- cgit