From c032e83b22994332dd8769ef34cb817906a63cac Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 26 Jan 2023 09:42:23 +0100 Subject: fix(treesitter): validate language name Problem: Some injections (like markdown) allow specifying arbitrary language names for code blocks, which may be lead to errors when looking for a corresponding parser in runtime path. Solution: Validate that the language name only contains alphanumeric characters and `_` (e.g., for `c_sharp`) and error otherwise. --- runtime/lua/vim/treesitter/language.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index c92d63b8c4..8634e53b7b 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -6,7 +6,7 @@ local M = {} --- --- Parsers are searched in the `parser` runtime directory, or the provided {path} --- ----@param lang string Language the parser should parse +---@param lang string Language the parser should parse (alphanumerical and `_` only) ---@param path (string|nil) Optional path the parser is located at ---@param silent (boolean|nil) Don't throw an error if language not found ---@param symbol_name (string|nil) Internal symbol name for the language to load @@ -16,13 +16,19 @@ function M.require_language(lang, path, silent, symbol_name) return true end if path == nil then - local fname = 'parser/' .. vim.fn.fnameescape(lang) .. '.*' + if not (lang and lang:match('[%w_]+') == lang) then + if silent then + return false + end + error("'" .. lang .. "' is not a valid language name") + end + + local fname = 'parser/' .. lang .. '.*' local paths = a.nvim_get_runtime_file(fname, false) if #paths == 0 then if silent then return false end - error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end path = paths[1] -- cgit From 8144deb0989ea5c61fe9a1a5802d230eba33dfdd Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 29 Jan 2023 10:39:20 +0100 Subject: vim-patch:9.0.1256: NetworkManager connection files are not recognized (#22038) Problem: NetworkManager connection files are not recognized. Solution: Add a pattern for NetworkManager connection files. (closes vim/vim#11893) https://github.com/vim/vim/commit/04e4f1d98556e67d7337224b67b71c828410ee0f Co-authored-by: ObserverOfTime --- 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 9293c828b8..0a26e363d5 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -226,6 +226,7 @@ local extension = { hook = function(path, bufnr) return M.getlines(bufnr, 1) == '[Trigger]' and 'conf' end, + nmconnection = 'confini', mklx = 'context', mkiv = 'context', mkii = 'context', -- cgit From c9ac4e487706658852f0e6c2e71cf669dafba90b Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Sun, 29 Jan 2023 14:19:07 -0500 Subject: vim-patch:9.0.1261: Elsa files are not recognized (#22047) Problem: Elsa files are not recognized. Solution: Add the name of Elsa files. (Amaan Qureshi) https://github.com/vim/vim/commit/2a99fe6c41efcd5d1eb47823e7e73cf391e230ba --- 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 0a26e363d5..1e7b85060e 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -332,6 +332,7 @@ local extension = { am = 'elf', exs = 'elixir', elm = 'elm', + lc = 'elsa', elv = 'elvish', ent = function(path, bufnr) return require('vim.filetype.detect').ent(bufnr) -- cgit From b649a96fc09fa7f18a988b0b0be5dadb54ad49de Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 31 Jan 2023 08:12:10 +0100 Subject: vim-patch:9.0.1263: KDL files are not recognized (#22058) Problem: KDL files are not recognized. Solution: Add a pattern for KDL files. (Amaan Qureshi, closes vim/vim#11898) https://github.com/vim/vim/commit/907349a74331fc1bc48cf43c1e7d54cb9e0e4fc9 Co-authored-by: Amaan Qureshi --- 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 1e7b85060e..b356f3d7aa 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -569,6 +569,7 @@ local extension = { libsonnet = 'jsonnet', jsp = 'jsp', jl = 'julia', + kdl = 'kdl', kv = 'kivy', kix = 'kix', kts = 'kotlin', -- cgit From d6d6ab3f8e77d54c8030c0c18f17d3c72ac4445c Mon Sep 17 00:00:00 2001 From: bfredl Date: Sat, 26 Feb 2022 15:19:10 +0100 Subject: feat(lua): low-level interpreter mode (nvim -ll) --- runtime/lua/vim/_init_packages.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 0c4ee8636d..e3a442af5e 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -42,8 +42,11 @@ function vim._load_package(name) return nil end --- Insert vim._load_package after the preloader at position 2 -table.insert(package.loaders, 2, vim._load_package) +-- TODO(bfredl): dedicated state for this? +if vim.api then + -- Insert vim._load_package after the preloader at position 2 + table.insert(package.loaders, 2, vim._load_package) +end -- builtin functions which always should be available require('vim.shared') @@ -78,6 +81,6 @@ function vim.empty_dict() end -- only on main thread: functions for interacting with editor state -if not vim.is_thread() then +if vim.api and not vim.is_thread() then require('vim._editor') end -- cgit From d63ad600e0571ccf07eed1e841e8519da7d4af9f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 1 Feb 2023 10:08:50 +0100 Subject: vim-patch:9.0.1268: .clangd and .stylelintrc files don't get a filetype (#22079) Problem: .clangd and .stylelintrc files don't get a filetype. Solution: Use yaml for .clangd and json for .stylelintrc files. (Mark Skelton, closes vim/vim#11916) https://github.com/vim/vim/commit/9c51798a1f3b79ace5ae0551a8bb122025ac94ed Co-authored-by: Mark Skelton --- 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 b356f3d7aa..d1a84fcecf 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1464,6 +1464,7 @@ local filename = { ['Pipfile.lock'] = 'json', ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', + ['.stylelintrc'] = 'json', ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', ['.hintrc'] = 'jsonc', @@ -1699,6 +1700,7 @@ local filename = { fglrxrc = 'xml', ['/etc/blkid.tab'] = 'xml', ['/etc/blkid.tab.old'] = 'xml', + ['.clangd'] = 'yaml', ['.clang-format'] = 'yaml', ['.clang-tidy'] = 'yaml', ['/etc/zprofile'] = 'zsh', -- cgit From c05b3c3bbdef4018ce70f0c822c4a55654a9c186 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 2 Feb 2023 17:26:22 +0100 Subject: vim-patch:9.0.1274: FIRRTL files are not recognized (#22102) Problem: FIRRTL files are not recognized. Solution: Add a pattern for FIRRTL files. (Amaan Qureshi, closes vim/vim#11931) https://github.com/vim/vim/commit/685bf83b73d0fe6fd36bb2949bebd6aae66a139e Co-authored-by: Amaan Qureshi --- 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 d1a84fcecf..8144731b09 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -385,6 +385,7 @@ local extension = { ['m4gl'] = 'fgl', ['4gl'] = 'fgl', ['4gh'] = 'fgl', + fir = 'firrtl', fish = 'fish', focexec = 'focexec', fex = 'focexec', -- cgit From 9a5678463c96baf3b39cb3083ddf0da87d39aa23 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 4 Feb 2023 14:58:38 +0000 Subject: fix(treesitter): fix most diagnostics --- runtime/lua/vim/treesitter.lua | 73 +++++++++------- runtime/lua/vim/treesitter/_meta.lua | 60 +++++++++++++ runtime/lua/vim/treesitter/highlighter.lua | 50 +++++++++-- runtime/lua/vim/treesitter/languagetree.lua | 128 +++++++++++++++++++--------- runtime/lua/vim/treesitter/playground.lua | 36 ++++---- runtime/lua/vim/treesitter/query.lua | 109 ++++++++++++++++------- 6 files changed, 331 insertions(+), 125 deletions(-) create mode 100644 runtime/lua/vim/treesitter/_meta.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 582922ecb6..96b1e24ba9 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -3,8 +3,11 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') +---@type table local parsers = setmetatable({}, { __mode = 'v' }) +---@class TreesitterModule +---@field highlighter TSHighlighter local M = vim.tbl_extend('error', query, language) M.language_version = vim._ts_get_language_version() @@ -12,6 +15,7 @@ M.minimum_language_version = vim._ts_get_minimum_language_version() setmetatable(M, { __index = function(t, k) + ---@diagnostic disable:no-unknown if k == 'highlighter' then t[k] = require('vim.treesitter.highlighter') return t[k] @@ -29,21 +33,23 @@ setmetatable(M, { --- --- It is not recommended to use this; use |get_parser()| instead. --- ----@param bufnr string Buffer the parser will be tied to (0 for current buffer) +---@param bufnr integer Buffer the parser will be tied to (0 for current buffer) ---@param lang string Language of the parser ---@param opts (table|nil) Options to pass to the created language tree --- ----@return LanguageTree |LanguageTree| object to use for parsing +---@return LanguageTree object to use for parsing function M._create_parser(bufnr, lang, opts) language.require_language(lang) if bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = vim.api.nvim_get_current_buf() end vim.fn.bufload(bufnr) local self = LanguageTree.new(bufnr, lang, opts) + ---@diagnostic disable:invisible + ---@private local function bytes_cb(_, ...) self:_on_bytes(...) @@ -58,12 +64,14 @@ function M._create_parser(bufnr, lang, opts) end ---@private - local function reload_cb(_, ...) - self:_on_reload(...) + local function reload_cb(_) + self:_on_reload() end + local source = self:source() --[[@as integer]] + a.nvim_buf_attach( - self:source(), + source, false, { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } ) @@ -77,11 +85,11 @@ end --- --- If needed, this will create the parser. --- ----@param bufnr (number|nil) Buffer the parser should be tied to (default: current buffer) +---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ---@param lang (string|nil) Filetype of this parser (default: buffer filetype) ---@param opts (table|nil) Options to pass to the created language tree --- ----@return LanguageTree |LanguageTree| object to use for parsing +---@return LanguageTree object to use for parsing function M.get_parser(bufnr, lang, opts) opts = opts or {} @@ -107,7 +115,7 @@ end ---@param lang string Language of this string ---@param opts (table|nil) Options to pass to the created language tree --- ----@return LanguageTree |LanguageTree| object to use for parsing +---@return LanguageTree object to use for parsing function M.get_string_parser(str, lang, opts) vim.validate({ str = { str, 'string' }, @@ -120,8 +128,8 @@ end --- Determines whether a node is the ancestor of another --- ----@param dest userdata Possible ancestor |tsnode| ----@param source userdata Possible descendant |tsnode| +---@param dest TSNode Possible ancestor +---@param source TSNode Possible descendant --- ---@return boolean True if {dest} is an ancestor of {source} function M.is_ancestor(dest, source) @@ -143,9 +151,12 @@ end --- Returns the node's range or an unpacked range table --- ----@param node_or_range (userdata|table) |tsnode| or table of positions +---@param node_or_range (TSNode|table) Node or table of positions --- ----@return table `{ start_row, start_col, end_row, end_col }` +---@return integer start_row +---@return integer start_col +---@return integer end_row +---@return integer end_col function M.get_node_range(node_or_range) if type(node_or_range) == 'table' then return unpack(node_or_range) @@ -156,9 +167,9 @@ end --- Determines whether (line, col) position is in node range --- ----@param node userdata |tsnode| defining the range ----@param line number Line (0-based) ----@param col number Column (0-based) +---@param node TSNode defining the range +---@param line integer Line (0-based) +---@param col integer Column (0-based) --- ---@return boolean True if the position is in node range function M.is_in_node_range(node, line, col) @@ -180,7 +191,7 @@ end --- Determines if a node contains a range --- ----@param node userdata |tsnode| +---@param node TSNode ---@param range table --- ---@return boolean True if the {node} contains the {range} @@ -197,9 +208,9 @@ end --- Each capture is represented by a table containing the capture name as a string as --- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined). --- ----@param bufnr number Buffer number (0 for current buffer) ----@param row number Position row ----@param col number Position column +---@param bufnr integer Buffer number (0 for current buffer) +---@param row integer Position row +---@param col integer Position column --- ---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` function M.get_captures_at_pos(bufnr, row, col) @@ -250,7 +261,7 @@ end --- Returns a list of highlight capture names under the cursor --- ----@param winnr (number|nil) Window handle or 0 for current window (default) +---@param winnr (integer|nil) Window handle or 0 for current window (default) --- ---@return string[] List of capture names function M.get_captures_at_cursor(winnr) @@ -271,14 +282,14 @@ end --- Returns the smallest named node at the given position --- ----@param bufnr number Buffer number (0 for current buffer) ----@param row number Position row ----@param col number Position column +---@param bufnr integer Buffer number (0 for current buffer) +---@param row integer Position row +---@param col integer Position column ---@param opts table Optional keyword arguments: --- - lang string|nil Parser language --- - ignore_injections boolean Ignore injected languages (default true) --- ----@return userdata|nil |tsnode| under the cursor +---@return TSNode|nil under the cursor function M.get_node_at_pos(bufnr, row, col, opts) if bufnr == 0 then bufnr = a.nvim_get_current_buf() @@ -295,7 +306,7 @@ end --- Returns the smallest named node under the cursor --- ----@param winnr (number|nil) Window handle or 0 for current window (default) +---@param winnr (integer|nil) Window handle or 0 for current window (default) --- ---@return string Name of node under the cursor function M.get_node_at_cursor(winnr) @@ -323,7 +334,7 @@ end --- }) --- --- ----@param bufnr (number|nil) Buffer to be highlighted (default: current buffer) +---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer) ---@param lang (string|nil) Language of the parser (default: buffer filetype) function M.start(bufnr, lang) bufnr = bufnr or a.nvim_get_current_buf() @@ -333,7 +344,7 @@ end --- Stops treesitter highlighting for a buffer --- ----@param bufnr (number|nil) Buffer to stop highlighting (default: current buffer) +---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer) function M.stop(bufnr) bufnr = bufnr or a.nvim_get_current_buf() @@ -351,13 +362,13 @@ end ---@param opts table|nil Optional options table with the following possible keys: --- - lang (string|nil): The language of the source buffer. If omitted, the --- filetype of the source buffer is used. ---- - bufnr (number|nil): Buffer to draw the tree into. If omitted, a new +--- - bufnr (integer|nil): Buffer to draw the tree into. If omitted, a new --- buffer is created. ---- - winid (number|nil): Window id to display the tree buffer in. If omitted, +--- - winid (integer|nil): Window id to display the tree buffer in. If omitted, --- a new window is created with {command}. --- - command (string|nil): Vimscript command to create the window. Default --- value is "topleft 60vnew". Only used when {winid} is nil. ---- - title (string|fun(bufnr:number):string|nil): Title of the window. If a +--- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a --- function, it accepts the buffer number of the source buffer as its only --- argument and should return a string. function M.show_tree(opts) diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua new file mode 100644 index 0000000000..87b4560798 --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -0,0 +1,60 @@ +---@meta + +---@class TSNode +---@field id fun(self: TSNode): integer +---@field range fun(self: TSNode): integer, integer, integer, integer +---@field start fun(self: TSNode): integer, integer, integer +---@field end_ fun(self: TSNode): integer, integer, integer +---@field type fun(self: TSNode): string +---@field symbol fun(self: TSNode): integer +---@field named fun(self: TSNode): boolean +---@field missing fun(self: TSNode): boolean +---@field child_count fun(self: TSNode): integer +---@field named_child_count fun(self: TSNode): integer +---@field child fun(self: TSNode, integer): TSNode +---@field name_child fun(self: TSNode, integer): TSNode +---@field descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode +---@field named_descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode +---@field parent fun(self: TSNode): TSNode +---@field next_sibling fun(self: TSNode): TSNode +---@field prev_sibling fun(self: TSNode): TSNode +---@field next_named_sibling fun(self: TSNode): TSNode +---@field prev_named_sibling fun(self: TSNode): TSNode +---@field named_children fun(self: TSNode): TSNode[] +---@field has_error fun(self: TSNode): boolean +---@field iter_children fun(self: TSNode): fun(): TSNode, string +local TSNode = {} + +---@param query userdata +---@param captures true +---@param start integer +---@param end_ integer +---@return fun(): integer, TSNode, any +function TSNode:_rawquery(query, captures, start, end_) end + +---@param query userdata +---@param captures false +---@param start integer +---@param end_ integer +---@return fun(): string, any +function TSNode:_rawquery(query, captures, start, end_) end + +---@class TSParser +---@field parse fun(self: TSParser, tree, source: integer|string): TSTree, integer[] +---@field included_ranges fun(self: TSParser): integer[] +---@field set_included_ranges fun(self: TSParser, ranges: integer[][]) + +---@class TSTree +---@field root fun(self: TSTree): TSNode +---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) +---@field copy fun(self: TSTree): TSTree + +---@return integer +vim._ts_get_language_version = function() end + +---@return integer +vim._ts_get_minimum_language_version = function() end + +---@param lang string +---@return TSParser +vim._create_ts_parser = function(lang) end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d77a0d0d03..8adaa4ef2f 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,13 +1,27 @@ local a = vim.api local query = require('vim.treesitter.query') --- support reload for quick experimentation +---@alias TSHlIter fun(): integer, TSNode, TSMetadata + +---@class TSHighlightState +---@field next_row integer +---@field iter TSHlIter|nil + ---@class TSHighlighter +---@field active table +---@field bufnr integer +---@field orig_spelloptions string +---@field _highlight_states table +---@field _queries table +---@field tree LanguageTree local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter TSHighlighter.active = TSHighlighter.active or {} +---@class TSHighlighterQuery +---@field _query Query|nil +---@field hl_cache table local TSHighlighterQuery = {} TSHighlighterQuery.__index = TSHighlighterQuery @@ -46,7 +60,7 @@ end --- Creates a new highlighter using @param tree --- ----@param tree LanguageTree |LanguageTree| parser object to use for highlighting +---@param tree LanguageTree parser object to use for highlighting ---@param opts (table|nil) Configuration of the highlighter: --- - queries table overwrite queries used by the highlighter ---@return TSHighlighter Created highlighter object @@ -57,9 +71,10 @@ function TSHighlighter.new(tree, opts) error('TSHighlighter can not be used with a string parser source.') end - opts = opts or {} + opts = opts or {} ---@type { queries: table } self.tree = tree tree:register_cbs({ + ---@diagnostic disable:invisible on_changedtree = function(...) self:on_changedtree(...) end, @@ -67,17 +82,20 @@ function TSHighlighter.new(tree, opts) self:on_bytes(...) end, on_detach = function(...) + ---@diagnostic disable-next-line:redundant-parameter self:on_detach(...) end, }) - self.bufnr = tree:source() + self.bufnr = tree:source() --[[@as integer]] self.edit_count = 0 self.redraw_count = 0 self.line_count = {} -- A map of highlight states. -- This state is kept during rendering across each line update. self._highlight_states = {} + + ---@type table self._queries = {} -- Queries for a specific language can be overridden by a custom @@ -128,6 +146,8 @@ function TSHighlighter:destroy() end ---@private +---@param tstree TSTree +---@return TSHighlightState function TSHighlighter:get_highlight_state(tstree) if not self._highlight_states[tstree] then self._highlight_states[tstree] = { @@ -145,6 +165,8 @@ function TSHighlighter:reset_highlight_state() end ---@private +---@param start_row integer +---@param new_end integer function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end @@ -155,6 +177,7 @@ function TSHighlighter:on_detach() end ---@private +---@param changes integer[][]? function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) @@ -165,7 +188,7 @@ end -- ---@private ---@param lang string Language used by the highlighter. ----@return Query +---@return TSHighlighterQuery function TSHighlighter:get_query(lang) if not self._queries[lang] then self._queries[lang] = TSHighlighterQuery.new(lang) @@ -175,7 +198,12 @@ function TSHighlighter:get_query(lang) end ---@private +---@param self TSHighlighter +---@param buf integer +---@param line integer +---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) + ---@diagnostic disable:invisible self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -213,7 +241,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) local hl = highlighter_query.hl_cache[capture] local capture_name = highlighter_query:query().captures[capture] - local spell = nil + local spell = nil ---@type boolean? if capture_name == 'spell' then spell = true elseif capture_name == 'nospell' then @@ -242,6 +270,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) end ---@private +---@param _win integer +---@param buf integer +---@param line integer function TSHighlighter._on_line(_, _win, buf, line, _) local self = TSHighlighter.active[buf] if not self then @@ -252,6 +283,9 @@ function TSHighlighter._on_line(_, _win, buf, line, _) end ---@private +---@param buf integer +---@param srow integer +---@param erow integer function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) local self = TSHighlighter.active[buf] if not self then @@ -266,6 +300,7 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) end ---@private +---@param buf integer function TSHighlighter._on_buf(_, buf) local self = TSHighlighter.active[buf] if self then @@ -274,6 +309,9 @@ function TSHighlighter._on_buf(_, buf) end ---@private +---@param _win integer +---@param buf integer +---@param _topline integer function TSHighlighter._on_win(_, _win, buf, _topline) local self = TSHighlighter.active[buf] if not self then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index a1e96f8ef2..89aac3ae26 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -2,20 +2,39 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') ----@class LanguageTree ----@field _callbacks function[] Callback handlers ----@field _children LanguageTree[] Injected languages ----@field _injection_query table Queries defining injected languages ----@field _opts table Options ----@field _parser userdata Parser for language ----@field _regions table List of regions this tree should manage and parse ----@field _lang string Language name ----@field _regions table ----@field _source (number|string) Buffer or string to parse ----@field _trees userdata[] Reference to parsed |tstree| (one for each language) ----@field _valid boolean If the parsed tree is valid +---@alias Range {[1]: integer, [2]: integer, [3]: integer, [4]: integer} +-- +---@alias TSCallbackName +---| 'changedtree' +---| 'bytes' +---| 'detach' +---| 'child_added' +---| 'child_removed' + +---@alias TSCallbackNameOn +---| 'on_changedtree' +---| 'on_bytes' +---| 'on_detach' +---| 'on_child_added' +---| 'on_child_removed' +---@class LanguageTree +---@field private _callbacks table Callback handlers +---@field private _children table Injected languages +---@field private _injection_query Query Queries defining injected languages +---@field private _opts table Options +---@field private _parser TSParser Parser for language +---@field private _regions Range[][] List of regions this tree should manage and parse +---@field private _lang string Language name +---@field private _source (integer|string) Buffer or string to parse +---@field private _trees TSTree[] Reference to parsed tree (one for each language) +---@field private _valid boolean If the parsed tree is valid local LanguageTree = {} + +---@class LanguageTreeOpts +---@field queries table -- Deprecated +---@field injections table + LanguageTree.__index = LanguageTree --- A |LanguageTree| holds the treesitter parser for a given language {lang} used @@ -23,16 +42,17 @@ LanguageTree.__index = LanguageTree --- needs to store parsers for these child languages as well (which in turn may contain --- child languages themselves, hence the name). --- ----@param source (number|string) Buffer or a string of text to parse +---@param source (integer|string) Buffer or a string of text to parse ---@param lang string Root language this tree represents ---@param opts (table|nil) Optional keyword arguments: --- - injections table Mapping language to injection query strings. --- This is useful for overriding the built-in --- runtime file searching for the injection language --- query per language. ----@return LanguageTree |LanguageTree| parser object +---@return LanguageTree parser object function LanguageTree.new(source, lang, opts) language.require_language(lang) + ---@type LanguageTreeOpts opts = opts or {} if opts.queries then @@ -65,6 +85,7 @@ function LanguageTree.new(source, lang, opts) end --- Invalidates this parser and all its children +---@param reload boolean|nil function LanguageTree:invalidate(reload) self._valid = false @@ -73,7 +94,7 @@ function LanguageTree:invalidate(reload) self._trees = {} end - for _, child in ipairs(self._children) do + for _, child in pairs(self._children) do child:invalidate(reload) end end @@ -111,8 +132,8 @@ end --- This will run the injection query for this language to --- determine if any child languages should be created. --- ----@return userdata[] Table of parsed |tstree| ----@return table Change list +---@return TSTree[] +---@return table|nil Change list function LanguageTree:parse() if self._valid then return self._trees @@ -146,7 +167,7 @@ function LanguageTree:parse() end local injections_by_lang = self:_get_injections() - local seen_langs = {} + local seen_langs = {} ---@type table for lang, injection_ranges in pairs(injections_by_lang) do local has_lang = language.require_language(lang, nil, true) @@ -188,8 +209,8 @@ end --- Invokes the callback for each |LanguageTree| and its children recursively --- ----@param fn function(tree: LanguageTree, lang: string) ----@param include_self boolean Whether to include the invoking tree in the results +---@param fn fun(tree: LanguageTree, lang: string) +---@param include_self boolean|nil Whether to include the invoking tree in the results function LanguageTree:for_each_child(fn, include_self) if include_self then fn(self, self._lang) @@ -204,7 +225,7 @@ end --- --- Note: This includes the invoking tree's child trees as well. --- ----@param fn function(tree: TSTree, languageTree: LanguageTree) +---@param fn fun(tree: TSTree, ltree: LanguageTree) function LanguageTree:for_each_tree(fn) for _, tree in ipairs(self._trees) do fn(tree, self) @@ -221,7 +242,7 @@ end --- ---@private ---@param lang string Language to add. ----@return LanguageTree Injected |LanguageTree| +---@return LanguageTree injected function LanguageTree:add_child(lang) if self._children[lang] then self:remove_child(lang) @@ -258,7 +279,7 @@ end --- `remove_child` must be called on the parent to remove it. function LanguageTree:destroy() -- Cleanup here - for _, child in ipairs(self._children) do + for _, child in pairs(self._children) do child:destroy() end end @@ -280,20 +301,22 @@ end --- Note: This call invalidates the tree and requires it to be parsed again. --- ---@private ----@param regions table List of regions this tree should manage and parse. +---@param regions integer[][][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(regions) -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(regions) do for i, range in ipairs(region) do if type(range) == 'table' and #range == 4 then + ---@diagnostic disable-next-line:no-unknown local start_row, start_col, end_row, end_col = unpack(range) local start_byte = 0 local end_byte = 0 + local source = self._source -- TODO(vigoux): proper byte computation here, and account for EOL ? - if type(self._source) == 'number' then + if type(source) == 'number' then -- Easy case, this is a buffer parser - start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col - end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col + start_byte = a.nvim_buf_get_offset(source, start_row) + start_col + end_byte = a.nvim_buf_get_offset(source, end_row) + end_col elseif type(self._source) == 'string' then -- string parser, single `\n` delimited string start_byte = vim.fn.byteidx(self._source, start_col) @@ -320,9 +343,13 @@ function LanguageTree:included_regions() end ---@private +---@param node TSNode +---@param id integer +---@param metadata TSMetadata +---@return Range local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then - return metadata[id].range + return metadata[id].range --[[@as Range]] end return { node:range() } end @@ -334,11 +361,13 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private +---@return table function LanguageTree:_get_injections() if not self._injection_query then return {} end + ---@type table>> local injections = {} for tree_index, tree in ipairs(self._trees) do @@ -348,14 +377,14 @@ function LanguageTree:_get_injections() for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do - local lang = nil - local ranges = {} - local combined = metadata.combined + local lang = nil ---@type string + local ranges = {} ---@type Range[] + local combined = metadata.combined ---@type boolean -- Directives can configure how injections are captured as well as actual node captures. -- This allows more advanced processing for determining ranges and language resolution. if metadata.content then - local content = metadata.content + local content = metadata.content ---@type any -- Allow for captured nodes to be used if type(content) == 'number' then @@ -368,7 +397,7 @@ function LanguageTree:_get_injections() end if metadata.language then - lang = metadata.language + lang = metadata.language ---@type string end -- You can specify the content and language together @@ -379,7 +408,7 @@ function LanguageTree:_get_injections() -- Lang should override any other language tag if name == 'language' and not lang then - lang = query.get_node_text(node, self._source) + lang = query.get_node_text(node, self._source) --[[@as string]] elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then @@ -417,6 +446,7 @@ function LanguageTree:_get_injections() end end + ---@type table local result = {} -- Generate a map by lang of node lists. @@ -429,11 +459,13 @@ function LanguageTree:_get_injections() for _, entry in pairs(patterns) do if entry.combined then + ---@diagnostic disable-next-line:no-unknown local regions = vim.tbl_map(function(e) return vim.tbl_flatten(e) end, entry.regions) table.insert(result[lang], regions) else + ---@diagnostic disable-next-line:no-unknown for _, ranges in ipairs(entry.regions) do table.insert(result[lang], ranges) end @@ -446,6 +478,7 @@ function LanguageTree:_get_injections() end ---@private +---@param cb_name TSCallbackName function LanguageTree:_do_callback(cb_name, ...) for _, cb in ipairs(self._callbacks[cb_name]) do cb(...) @@ -453,6 +486,17 @@ function LanguageTree:_do_callback(cb_name, ...) end ---@private +---@param bufnr integer +---@param changed_tick integer +---@param start_row integer +---@param start_col integer +---@param start_byte integer +---@param old_row integer +---@param old_col integer +---@param old_byte integer +---@param new_row integer +---@param new_col integer +---@param new_byte integer function LanguageTree:_on_bytes( bufnr, changed_tick, @@ -523,6 +567,7 @@ end --- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_removed` : emitted when a child is removed from the tree. function LanguageTree:register_cbs(cbs) + ---@cast cbs table if not cbs then return end @@ -549,6 +594,9 @@ function LanguageTree:register_cbs(cbs) end ---@private +---@param tree TSTree +---@param range Range +---@return boolean local function tree_contains(tree, range) local start_row, start_col, end_row, end_col = tree:root():range() local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) @@ -559,7 +607,7 @@ end --- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range table `{ start_line, start_col, end_line, end_col }` +---@param range Range `{ start_line, start_col, end_line, end_col }` ---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do @@ -573,10 +621,10 @@ end --- Gets the tree that contains {range}. --- ----@param range table `{ start_line, start_col, end_line, end_col }` +---@param range Range `{ start_line, start_col, end_line, end_col }` ---@param opts table|nil Optional keyword arguments: --- - ignore_injections boolean Ignore injected languages (default true) ----@return userdata|nil Contained |tstree| +---@return TSTree|nil function LanguageTree:tree_for_range(range, opts) opts = opts or {} local ignore = vim.F.if_nil(opts.ignore_injections, true) @@ -602,10 +650,10 @@ end --- Gets the smallest named node that contains {range}. --- ----@param range table `{ start_line, start_col, end_line, end_col }` +---@param range Range `{ start_line, start_col, end_line, end_col }` ---@param opts table|nil Optional keyword arguments: --- - ignore_injections boolean Ignore injected languages (default true) ----@return userdata|nil Found |tsnode| +---@return TSNode|nil Found node function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) if tree then @@ -615,7 +663,7 @@ end --- Gets the appropriate language that contains {range}. --- ----@param range table `{ start_line, start_col, end_line, end_col }` +---@param range Range `{ start_line, start_col, end_line, end_col }` ---@return LanguageTree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index bb073290c6..be7764e6f0 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -1,12 +1,13 @@ local api = vim.api -local M = {} - ----@class Playground +---@class TSPlayground ---@field ns number API namespace ---@field opts table Options table with the following keys: --- - anon (boolean): If true, display anonymous nodes --- - lang (boolean): If true, display the language alongside each node +---@field nodes Node[] +---@field named Node[] +local TSPlayground = {} --- ---@class Node ---@field id number Node id @@ -18,6 +19,7 @@ local M = {} ---@field end_lnum number Final line number of this node in the source buffer ---@field end_col number Final column number of this node in the source buffer ---@field lang string Source language of this node +---@field root TSNode --- Traverse all child nodes starting at {node}. --- @@ -31,10 +33,10 @@ local M = {} --- node of each of these trees is contained within a node in the primary tree. The {injections} --- table maps nodes in the primary tree to root nodes of injected trees. --- ----@param node userdata Starting node to begin traversal |tsnode| +---@param node TSNode Starting node to begin traversal |tsnode| ---@param depth number Current recursion depth ---@param lang string Language of the tree currently being traversed ----@param injections table Mapping of node ids to root nodes of injected language trees (see +---@param injections table Mapping of node ids to root nodes of injected language trees (see --- explanation above) ---@param tree Node[] Output table containing a list of tables each representing a node in the tree ---@private @@ -48,7 +50,7 @@ local function traverse(node, depth, lang, injections, tree) local type = child:type() local lnum, col, end_lnum, end_col = child:range() local named = child:named() - local text + local text ---@type string if named then if field then text = string.format('%s: (%s)', field, type) @@ -79,14 +81,14 @@ end --- Create a new Playground object. --- ----@param bufnr number Source buffer number +---@param bufnr integer Source buffer number ---@param lang string|nil Language of source buffer --- ----@return Playground|nil +---@return TSPlayground|nil ---@return string|nil Error message, if any --- ---@private -function M.new(self, bufnr, lang) +function TSPlayground:new(bufnr, lang) local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) if not ok then return nil, 'No parser available for the given buffer' @@ -96,7 +98,7 @@ function M.new(self, bufnr, lang) -- the primary tree that contains that root. Add a mapping from the node in the primary tree to -- the root in the child tree to the {injections} table. local root = parser:parse()[1]:root() - local injections = {} + local injections = {} ---@type table parser:for_each_child(function(child, lang_) child:for_each_tree(function(tree) local r = tree:root() @@ -112,7 +114,7 @@ function M.new(self, bufnr, lang) local nodes = traverse(root, 0, parser:lang(), injections, {}) - local named = {} + local named = {} ---@type Node[] for _, v in ipairs(nodes) do if v.named then named[#named + 1] = v @@ -138,9 +140,9 @@ end --- ---@param bufnr number Buffer number to write into. ---@private -function M.draw(self, bufnr) +function TSPlayground:draw(bufnr) vim.bo[bufnr].modifiable = true - local lines = {} + local lines = {} ---@type string[] for _, item in self:iter() do lines[#lines + 1] = table.concat({ string.rep(' ', item.depth), @@ -168,19 +170,19 @@ end ---@param i number Node number to get ---@return Node ---@private -function M.get(self, i) +function TSPlayground:get(i) local t = self.opts.anon and self.nodes or self.named return t[i] end --- Iterate over all of the nodes in this Playground object. --- ----@return function Iterator over all nodes in this Playground +---@return (fun(): integer, Node) Iterator over all nodes in this Playground ---@return table ---@return number ---@private -function M.iter(self) +function TSPlayground:iter() return ipairs(self.opts.anon and self.nodes or self.named) end -return M +return TSPlayground diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index dbf134573d..84ed2667b9 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,21 +1,25 @@ local a = vim.api local language = require('vim.treesitter.language') --- query: pattern matching on trees --- predicate matching is implemented in lua --- ---@class Query ---@field captures string[] List of captures used in query ----@field info table Contains used queries, predicates, directives +---@field info TSQueryInfo Contains used queries, predicates, directives ---@field query userdata Parsed query local Query = {} Query.__index = Query +---@class TSQueryInfo +---@field captures table +---@field patterns table + local M = {} ---@private +---@param files string[] +---@return string[] local function dedupe_files(files) local result = {} + ---@type table local seen = {} for _, path in ipairs(files) do @@ -65,10 +69,10 @@ function M.get_query_files(lang, query_name, is_included) return {} end - local base_query = nil + local base_query = nil ---@type string? local extensions = {} - local base_langs = {} + local base_langs = {} ---@type string[] -- Now get the base languages by looking at the first line of every file -- The syntax is the following : @@ -87,6 +91,7 @@ function M.get_query_files(lang, query_name, is_included) local extension = false for modeline in + ---@return string function() return file:read('*l') end @@ -97,6 +102,7 @@ function M.get_query_files(lang, query_name, is_included) local langlist = modeline:match(MODELINE_FORMAT) if langlist then + ---@diagnostic disable-next-line:param-type-mismatch for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') @@ -137,6 +143,8 @@ function M.get_query_files(lang, query_name, is_included) end ---@private +---@param filenames string[] +---@return string local function read_query_files(filenames) local contents = {} @@ -147,7 +155,8 @@ local function read_query_files(filenames) return table.concat(contents, '') end ---- The explicitly set queries from |vim.treesitter.query.set_query()| +-- The explicitly set queries from |vim.treesitter.query.set_query()| +---@type table> local explicit_queries = setmetatable({}, { __index = function(t, k) local lang_queries = {} @@ -174,7 +183,7 @@ end ---@param lang string Language to use for the query ---@param query_name string Name of the query (e.g. "highlights") --- ----@return Query Parsed query +---@return Query|nil Parsed query function M.get_query(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -188,6 +197,7 @@ function M.get_query(lang, query_name) end end +---@type {[string]: {[string]: Query}} local query_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) @@ -226,11 +236,11 @@ end --- Gets the text corresponding to a given node --- ----@param node userdata |tsnode| +---@param node TSNode ---@param source (number|string) Buffer or string from which the {node} is extracted ---@param opts (table|nil) Optional parameters. --- - concat: (boolean) Concatenate result in a string (default true) ----@return (string[]|string) +---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) @@ -239,12 +249,12 @@ function M.get_node_text(node, source, opts) local end_row, end_col, end_byte = node:end_() if type(source) == 'number' then - local lines local eof_row = a.nvim_buf_line_count(source) if start_row >= eof_row then return nil end + local lines ---@type string[] if end_col == 0 then lines = a.nvim_buf_get_lines(source, start_row, end_row, true) end_col = -1 @@ -267,8 +277,13 @@ function M.get_node_text(node, source, opts) end end +---@alias TSMatch table + +---@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean + -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) +---@type table local predicate_handlers = { ['eq?'] = function(match, _, source, predicate) local node = match[predicate[2]] @@ -277,13 +292,13 @@ local predicate_handlers = { end local node_text = M.get_node_text(node, source) - local str + local str ---@type string if type(predicate[3]) == 'string' then -- (#eq? @aa "foo") str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) + str = M.get_node_text(match[predicate[3]], source) --[[@as string]] end if node_text ~= str or str == nil then @@ -299,7 +314,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source), regex) + return string.find(M.get_node_text(node, source) --[[@as string]], regex) ~= nil end, ['match?'] = (function() @@ -321,10 +336,12 @@ local predicate_handlers = { }) return function(match, _, source, pred) + ---@cast match TSMatch local node = match[pred[2]] if not node then return true end + ---@diagnostic disable-next-line no-unknown local regex = compiled_vim_regexes[pred[3]] return regex:match_str(M.get_node_text(node, source)) end @@ -335,7 +352,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = M.get_node_text(node, source) --[[@as string]] for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -359,6 +376,7 @@ local predicate_handlers = { if not string_set then string_set = {} for i = 3, #predicate do + ---@diagnostic disable-next-line:no-unknown string_set[predicate[i]] = true end predicate['string_set'] = string_set @@ -371,21 +389,39 @@ local predicate_handlers = { -- As we provide lua-match? also expose vim-match? predicate_handlers['vim-match?'] = predicate_handlers['match?'] +---@class TSMetadata +---@field [integer] TSMetadata +---@field [string] integer|string +---@field range Range + +---@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) + +-- Predicate handler receive the following arguments +-- (match, pattern, bufnr, predicate) + -- Directives store metadata or perform side effects against a match. -- Directives should always end with a `!`. -- Directive handler receive the following arguments -- (match, pattern, bufnr, predicate, metadata) +---@type table local directive_handlers = { ['set!'] = function(_, _, _, pred, metadata) if #pred == 4 then -- (#set! @capture "key" "value") + ---@diagnostic disable-next-line:no-unknown local _, capture_id, key, value = unpack(pred) + ---@cast value integer|string + ---@cast capture_id integer + ---@cast key string if not metadata[capture_id] then metadata[capture_id] = {} end metadata[capture_id][key] = value else + ---@diagnostic disable-next-line:no-unknown local _, key, value = unpack(pred) + ---@cast value integer|string + ---@cast key string -- (#set! "key" "value") metadata[key] = value end @@ -393,9 +429,11 @@ local directive_handlers = { -- Shifts the range of a node. -- Example: (#offset! @_node 0 1 0 -1) ['offset!'] = function(match, _, _, pred, metadata) + ---@cast pred integer[] local capture_id = pred[2] local offset_node = match[capture_id] local range = { offset_node:range() } + ---@cast range integer[] bug in sumneko local start_row_offset = pred[3] or 0 local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 @@ -419,8 +457,9 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) --- - see |vim.treesitter.query.add_directive()| for argument meanings +---@param force boolean function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -437,12 +476,13 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) --- - match: see |treesitter-query| --- - node-level data are accessible via `match[capture_id]` --- - pattern: see |treesitter-query| --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` +---@param force boolean function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -474,6 +514,9 @@ local function is_directive(name) end ---@private +---@param match TSMatch +---@param pattern string +---@param source integer|string function Query:match_preds(match, pattern, source) local preds = self.info.patterns[pattern] @@ -482,8 +525,9 @@ function Query:match_preds(match, pattern, source) -- continue on the other case. This way unknown predicates will not be considered, -- which allows some testing and easier user extensibility (#12173). -- Also, tree-sitter strips the leading # from predicates for us. - local pred_name - local is_not + local pred_name ---@type string + + local is_not ---@type boolean -- Skip over directives... they will get processed after all the predicates. if not is_directive(pred[1]) then @@ -513,6 +557,8 @@ function Query:match_preds(match, pattern, source) end ---@private +---@param match TSMatch +---@param metadata TSMetadata function Query:apply_directives(match, pattern, source, metadata) local preds = self.info.patterns[pattern] @@ -534,6 +580,10 @@ end -- When the node's range is used, the stop is incremented by 1 -- to make the search inclusive. ---@private +---@param start integer +---@param stop integer +---@param node TSNode +---@return integer, integer local function value_or_node_range(start, stop, node) if start == nil and stop == nil then local node_start, _, node_stop, _ = node:range() @@ -565,14 +615,12 @@ end --- end --- --- ----@param node userdata |tsnode| under which the search will occur ----@param source (number|string) Source buffer or string to extract text from +---@param node TSNode under which the search will occur +---@param source (integer|string) Source buffer or string to extract text from ---@param start number Starting line for the search ---@param stop number Stopping line for the search (end-exclusive) --- ----@return number capture Matching capture id ----@return table capture_node Capture for {node} ----@return table metadata for the {capture} +---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -622,14 +670,12 @@ end --- end --- --- ----@param node userdata |tsnode| under which the search will occur ----@param source (number|string) Source buffer or string to search ----@param start number Starting line for the search ----@param stop number Stopping line for the search (end-exclusive) +---@param node TSNode under which the search will occur +---@param source (integer|string) Source buffer or string to search +---@param start integer Starting line for the search +---@param stop integer Stopping line for the search (end-exclusive) --- ----@return number pattern id ----@return table match ----@return table metadata +---@return (fun(): integer, table, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -638,6 +684,7 @@ function Query:iter_matches(node, source, start, stop) start, stop = value_or_node_range(start, stop, node) local raw_iter = node:_rawquery(self.query, false, start, stop) + ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() local metadata = {} -- cgit From 4b9bb3a1841489a18bbeb124fc11cbf359553ef5 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 4 Feb 2023 16:05:45 +0100 Subject: vim-patch:9.0.1278: go.work.sum files are not recognized (#22121) Problem: go.work.sum files are not recognized. Solution: Recognize go.work.sum files as the gosum filetype. (Amaan Qureshi, closes vim/vim#11940) https://github.com/vim/vim/commit/4ad8ae8465e30df38dba31910f130891b16d38a0 Co-authored-by: Amaan Qureshi --- 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 8144731b09..bc30b3bd11 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1433,6 +1433,7 @@ local filename = { gnashrc = 'gnash', ['.gnuplot'] = 'gnuplot', ['go.sum'] = 'gosum', + ['go.work.sum'] = 'gosum', ['go.work'] = 'gowork', ['.gprc'] = 'gp', ['/.gnupg/gpg.conf'] = 'gpg', -- cgit From bb8845340b1b9c2180fb19f049ff9deff5857d99 Mon Sep 17 00:00:00 2001 From: figsoda Date: Thu, 21 Jul 2022 12:08:37 +0100 Subject: feat(treesitter): allow capture text to be transformed Co-authored-by: Lewis Russell --- runtime/lua/vim/treesitter/languagetree.lua | 10 +++++++++- runtime/lua/vim/treesitter/query.lua | 15 +++++++++++++++ 2 files changed, 24 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 89aac3ae26..3e1bc5d1cb 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -354,6 +354,14 @@ local function get_range_from_metadata(node, id, metadata) return { node:range() } end +---@private +local function get_node_text(node, id, metadata, source) + if metadata[id] and metadata[id].text then + return metadata[id].text + end + return query.get_node_text(node, source) +end + --- Gets language injection points by language. --- --- This is where most of the injection processing occurs. @@ -408,7 +416,7 @@ function LanguageTree:_get_injections() -- Lang should override any other language tag if name == 'language' and not lang then - lang = query.get_node_text(node, self._source) --[[@as string]] + lang = get_node_text(node, id, metadata, self._source) --[[@as string]] elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 84ed2667b9..5ec8c67462 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -452,6 +452,21 @@ local directive_handlers = { metadata[capture_id].range = range end end, + + -- Transform the content of the node + -- Example: (#gsub! @_node ".*%.(.*)" "%1") + ['gsub!'] = function(match, _, bufnr, pred, metadata) + assert(#pred == 4) + + local id = pred[2] + local node = match[id] + local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' + + if not metadata[id] then + metadata[id] = {} + end + metadata[id].text = text:gsub(pred[3], pred[4]) + end, } --- Adds a new predicate to be used in queries -- cgit From e1d5ad1cb87d43c3d75619e239312d4ab2029b45 Mon Sep 17 00:00:00 2001 From: figsoda Date: Mon, 26 Dec 2022 16:10:59 -0500 Subject: feat(treesitter): add metadata option for get_node_text --- runtime/lua/vim/treesitter/languagetree.lua | 10 +---- runtime/lua/vim/treesitter/query.lua | 70 +++++++++++++++++------------ 2 files changed, 43 insertions(+), 37 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 3e1bc5d1cb..8255c6f4fe 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -354,14 +354,6 @@ local function get_range_from_metadata(node, id, metadata) return { node:range() } end ----@private -local function get_node_text(node, id, metadata, source) - if metadata[id] and metadata[id].text then - return metadata[id].text - end - return query.get_node_text(node, source) -end - --- Gets language injection points by language. --- --- This is where most of the injection processing occurs. @@ -416,7 +408,7 @@ function LanguageTree:_get_injections() -- Lang should override any other language tag if name == 'language' and not lang then - lang = get_node_text(node, id, metadata, self._source) --[[@as string]] + lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5ec8c67462..9136b596be 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -55,6 +55,38 @@ local function add_included_lang(base_langs, lang, ilang) return false end +---@private +---@param buf (number) +---@param range (table) +---@param concat (boolean) +---@returns (string[]|string|nil) +local function buf_range_get_text(buf, range, concat) + local lines + local start_row, start_col, end_row, end_col = unpack(range) + local eof_row = a.nvim_buf_line_count(buf) + if start_row >= eof_row then + return nil + end + + if end_col == 0 then + lines = a.nvim_buf_get_lines(buf, start_row, end_row, true) + end_col = -1 + else + lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true) + end + + if #lines > 0 then + if #lines == 1 then + lines[1] = string.sub(lines[1], start_col + 1, end_col) + else + lines[1] = string.sub(lines[1], start_col + 1) + lines[#lines] = string.sub(lines[#lines], 1, end_col) + end + end + + return concat and table.concat(lines, '\n') or lines +end + --- Gets the list of files used to make up a query --- ---@param lang string Language to get query for @@ -240,40 +272,22 @@ end ---@param source (number|string) Buffer or string from which the {node} is extracted ---@param opts (table|nil) Optional parameters. --- - concat: (boolean) Concatenate result in a string (default true) +--- - metadata (table) Metadata of a specific capture. This would be +--- set to `metadata[capture_id]` when using +--- |vim.treesitter.query.add_directive()|. ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) + local metadata = opts.metadata or {} - local start_row, start_col, start_byte = node:start() - local end_row, end_col, end_byte = node:end_() - - if type(source) == 'number' then - local eof_row = a.nvim_buf_line_count(source) - if start_row >= eof_row then - return nil - end - - local lines ---@type string[] - if end_col == 0 then - lines = a.nvim_buf_get_lines(source, start_row, end_row, true) - end_col = -1 - else - lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true) - end - - if #lines > 0 then - if #lines == 1 then - lines[1] = string.sub(lines[1], start_col + 1, end_col) - else - lines[1] = string.sub(lines[1], start_col + 1) - lines[#lines] = string.sub(lines[#lines], 1, end_col) - end - end - - return concat and table.concat(lines, '\n') or lines + if metadata.text then + return metadata.text + elseif type(source) == 'number' then + return metadata.range and buf_range_get_text(source, metadata.range, concat) + or buf_range_get_text(source, { node:range() }, concat) elseif type(source) == 'string' then - return source:sub(start_byte + 1, end_byte) + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) end end -- cgit From 4c66f5ff97a52fbc933fdbe1907c4b960d5a7403 Mon Sep 17 00:00:00 2001 From: figsoda Date: Mon, 26 Dec 2022 16:11:45 -0500 Subject: feat(treesitter): respect metadata[id].range for offset! --- runtime/lua/vim/treesitter/query.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 9136b596be..a0522d7cda 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -445,9 +445,11 @@ local directive_handlers = { ['offset!'] = function(match, _, _, pred, metadata) ---@cast pred integer[] local capture_id = pred[2] - local offset_node = match[capture_id] - local range = { offset_node:range() } - ---@cast range integer[] bug in sumneko + if not metadata[capture_id] then + metadata[capture_id] = {} + end + + local range = metadata[capture_id].range or { match[capture_id]:range() } local start_row_offset = pred[3] or 0 local start_col_offset = pred[4] or 0 local end_row_offset = pred[5] or 0 @@ -460,9 +462,6 @@ local directive_handlers = { -- If this produces an invalid range, we just skip it. if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then - if not metadata[capture_id] then - metadata[capture_id] = {} - end metadata[capture_id].range = range end end, -- cgit From 23e34fe534d201a1323ab040cb2201d21fe865cc Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 5 Feb 2023 17:59:18 +0100 Subject: vim-patch:9.0.1281: Cadence files are not recognized (#22130) Problem: Cadence files are not recognized. Solution: Recognize Cadence files. (Janez Podhostnik, closes vim/vim#11951) https://github.com/vim/vim/commit/cb626a4692df7154be02b47d6089ec679e95cb44 Co-authored-by: Janez Podhostnik --- 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 bc30b3bd11..a18abf09ac 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -191,6 +191,7 @@ local extension = { qc = 'c', cabal = 'cabal', capnp = 'capnp', + cdc = 'cdc', cdl = 'cdl', toc = 'cdrtoc', cfc = 'cf', -- cgit From 1675f0e270579036c454c38f28e650b60a3fba1a Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Mon, 6 Feb 2023 04:01:17 -0500 Subject: vim-patch:9.0.1282: Ron files are not recognized (#22132) Problem: Ron files are not recognized. Solution: Recognize Ron files. (Amaan Qureshi, closes vim/vim#11948) https://github.com/vim/vim/commit/c8ef30bc2eaec956549510cd4b2efc96b7aee563 --- 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 a18abf09ac..58728a8872 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -871,6 +871,7 @@ local extension = { Snw = 'rnoweb', robot = 'robot', resource = 'robot', + ron = 'ron', rsc = 'routeros', x = 'rpcgen', rpl = 'rpl', -- cgit From 8fbe75b3dda7ab3b9e6df0d5406539bde4c80887 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 7 Feb 2023 10:29:37 +0100 Subject: vim-patch:9.0.1288: FunC files are not recognized (#22153) Problem: FunC files are not recognized. Solution: Recognize FunC files. (Amaan Qureshi, closes vim/vim#11949) https://github.com/vim/vim/commit/91deac45392fe93094b9c31403b1ae771dc71938 Co-authored-by: Amaan Qureshi --- 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 58728a8872..cdc6e4bb7c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -423,6 +423,7 @@ local extension = { fsh = 'fsh', fsi = 'fsharp', fsx = 'fsharp', + fc = 'func', fusion = 'fusion', gdb = 'gdb', gdmo = 'gdmo', -- cgit From 1ca4a8b1ddf5256ad53cf486d0b2125434168270 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 8 Feb 2023 09:47:55 +0100 Subject: vim-patch:9.0.1291: Move language files are not recognized (#22162) Problem: Move language files are not recognized. Solution: Recognize Move language files. (Amaan Qureshi, closes vim/vim#11947) https://github.com/vim/vim/commit/6642982beaf4f1f5164f0315a1b3e3c275156089 Co-authored-by: Amaan Qureshi --- 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 cdc6e4bb7c..1f605b72f3 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -692,6 +692,7 @@ local extension = { isc = 'monk', moo = 'moo', moon = 'moonscript', + move = 'move', mp = 'mp', mpiv = function(path, bufnr) return 'mp', function(b) -- cgit From f5bad01869df449cb1e4ae7f264bcd5c8150f606 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Feb 2023 15:20:47 +0000 Subject: feat(treesitter): playground improvements - Render node ranges as virtual text - Set filettype=query. The virtual text is to avoid parsing errors. - Make sure highlights text is always in view. --- runtime/lua/vim/treesitter.lua | 13 ++++++++--- runtime/lua/vim/treesitter/playground.lua | 36 +++++++++++++++++++------------ 2 files changed, 32 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 96b1e24ba9..a15d3ec23c 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -411,6 +411,7 @@ function M.show_tree(opts) vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' + vim.bo[b].filetype = 'query' local title = opts.title if not title then @@ -425,9 +426,6 @@ function M.show_tree(opts) pg:draw(b) - vim.fn.matchadd('Comment', '\\[[0-9:-]\\+\\]') - vim.fn.matchadd('String', '".*"') - a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) a.nvim_buf_set_keymap(b, 'n', '', '', { desc = 'Jump to the node under the cursor in the source buffer', @@ -467,6 +465,15 @@ function M.show_tree(opts) end_col = math.max(0, pos.end_col), hl_group = 'Visual', }) + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if pos.lnum < topline and pos.end_lnum < topline then + a.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) + elseif pos.lnum > botline and pos.end_lnum > botline then + a.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + end end, }) diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index be7764e6f0..001bc2d5bf 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -136,6 +136,8 @@ function TSPlayground:new(bufnr, lang) return t end +local decor_ns = api.nvim_create_namespace('ts.playground') + --- Write the contents of this Playground into {bufnr}. --- ---@param bufnr number Buffer number to write into. @@ -144,22 +146,28 @@ function TSPlayground:draw(bufnr) vim.bo[bufnr].modifiable = true local lines = {} ---@type string[] for _, item in self:iter() do - lines[#lines + 1] = table.concat({ - string.rep(' ', item.depth), - item.text, - item.lnum == item.end_lnum - and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) - or string.format( - ' [%d:%d-%d:%d]', - item.lnum + 1, - item.col + 1, - item.end_lnum + 1, - item.end_col - ), - self.opts.lang and string.format(' %s', item.lang) or '', - }) + lines[#lines + 1] = string.rep(' ', item.depth) .. item.text end api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) + + for i, item in self:iter() do + local range_str + if item.lnum == item.end_lnum then + range_str = string.format('[%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) + else + range_str = + string.format('[%d:%d-%d:%d]', item.lnum + 1, item.col + 1, item.end_lnum + 1, item.end_col) + end + + local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + + api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, 0, { + virt_text = { { range_str, 'Comment' }, { lang_str, 'Title' } }, + }) + end + vim.bo[bufnr].modifiable = false end -- cgit From 8a985d12dd6b4a5a4ba825939f36b7b1a324d849 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Feb 2023 16:08:22 +0000 Subject: fix(treesitter): don't trample parsers when filetype!=lang This allows vim.treesitter.show_tree() to work on buffers where the filetype does not match the parser language name e.g, bash/sh. --- runtime/lua/vim/treesitter.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index a15d3ec23c..4127198576 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -96,11 +96,13 @@ function M.get_parser(bufnr, lang, opts) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end - if lang == nil then - lang = a.nvim_buf_get_option(bufnr, 'filetype') - end - if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then + if parsers[bufnr] == nil then + lang = lang or a.nvim_buf_get_option(bufnr, 'filetype') + parsers[bufnr] = M._create_parser(bufnr, lang, opts) + elseif lang and parsers[bufnr]:lang() ~= lang then + -- Only try to create a new parser if lang is provided + -- and it doesn't match the stored parser parsers[bufnr] = M._create_parser(bufnr, lang, opts) end -- cgit From c5b34fa55483d84d1de32937ffff0b7cf1aeba78 Mon Sep 17 00:00:00 2001 From: glacambre Date: Sat, 11 Feb 2023 09:45:11 +0100 Subject: refactor: move init_default_autocmds to lua The original motivation for this change came from developping https://github.com/neovim/neovim/pull/22159, which will require adding more autocommand creation to Neovim's startup sequence. This change requires lightly editing a test that expected no autocommand to have been created from lua. --- runtime/lua/vim/_editor.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index da8764fbd4..3f27e61810 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -894,6 +894,26 @@ function vim._init_default_mappings() ]]) end +function vim._init_default_autocmds() + local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim_terminal', {}) + vim.api.nvim_create_autocmd({ 'bufreadcmd' }, { + pattern = 'term://*', + group = nvim_terminal_augroup, + nested = true, + command = "if !exists('b:term_title')|call termopen(matchstr(expand(\"\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'cwd': expand(get(matchlist(expand(\"\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})", + }) + vim.api.nvim_create_autocmd({ 'cmdwinenter' }, { + pattern = '[:>]', + group = vim.api.nvim_create_augroup('nvim_cmdwin', {}), + command = 'syntax sync minlines=1 maxlines=1', + }) +end + +function vim._init_defaults() + vim._init_default_mappings() + vim._init_default_autocmds() +end + require('vim._meta') return vim -- cgit From 9668c166e88cd71e517cacfb8d266b75047604f7 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Sat, 11 Feb 2023 16:08:33 +0100 Subject: fix(filetype): make vim.filetype.match() work with contents only (#22181) Co-authored-by: Gregory Anders --- runtime/lua/vim/filetype.lua | 95 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 47 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 1f605b72f3..ec568b5b82 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2449,6 +2449,7 @@ local function match_pattern(name, path, tail, pat) return false end end + -- If the pattern contains a / match against the full path, otherwise just the tail local fullpat = '^' .. pat .. '$' local matches @@ -2526,64 +2527,64 @@ function M.match(args) name = api.nvim_buf_get_name(bufnr) end - if name then - name = normalize_path(name) - end - local ft, on_detect - -- First check for the simple case where the full path exists as a key - local path = vim.fn.fnamemodify(name, ':p') - ft, on_detect = dispatch(filename[path], path, bufnr) - if ft then - return ft, on_detect - end + if name then + name = normalize_path(name) - -- Next check against just the file name - local tail = vim.fn.fnamemodify(name, ':t') - ft, on_detect = dispatch(filename[tail], path, bufnr) - if ft then - return ft, on_detect - end + -- First check for the simple case where the full path exists as a key + local path = vim.fn.fnamemodify(name, ':p') + ft, on_detect = dispatch(filename[path], path, bufnr) + if ft then + return ft, on_detect + end - -- Next, check the file path against available patterns with non-negative priority - local j = 1 - for i, v in ipairs(pattern_sorted) do - local k = next(v) - local opts = v[k][2] - if opts.priority < 0 then - j = i - break + -- Next check against just the file name + local tail = vim.fn.fnamemodify(name, ':t') + ft, on_detect = dispatch(filename[tail], path, bufnr) + if ft then + return ft, on_detect end - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect + -- Next, check the file path against available patterns with non-negative priority + local j = 1 + for i, v in ipairs(pattern_sorted) do + local k = next(v) + local opts = v[k][2] + if opts.priority < 0 then + j = i + break + end + + local filetype = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + ft, on_detect = dispatch(filetype, path, bufnr, matches) + if ft then + return ft, on_detect + end end end - end - -- Next, check file extension - local ext = vim.fn.fnamemodify(name, ':e') - ft, on_detect = dispatch(extension[ext], path, bufnr) - if ft then - return ft, on_detect - end + -- Next, check file extension + local ext = vim.fn.fnamemodify(name, ':e') + ft, on_detect = dispatch(extension[ext], path, bufnr) + if ft then + return ft, on_detect + end - -- Next, check patterns with negative priority - for i = j, #pattern_sorted do - local v = pattern_sorted[i] - local k = next(v) + -- Next, check patterns with negative priority + for i = j, #pattern_sorted do + local v = pattern_sorted[i] + local k = next(v) - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect + local filetype = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + ft, on_detect = dispatch(filetype, path, bufnr, matches) + if ft then + return ft, on_detect + end end end end -- cgit From b518aceaa8f738e581e58aacae93514699b5ff8e Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Tue, 14 Feb 2023 00:04:16 +0100 Subject: feat(filetype): fall back to file extension when matching from hashbang (#22140) If nothing matched in match_from_hashbang, also check the file extension table. For a hashbang like '#!/bin/env foo', this will set the filetype to 'fooscript' assuming the filetype for the 'foo' extension is 'fooscript' in the extension table. --- runtime/lua/vim/filetype.lua | 4 +++- runtime/lua/vim/filetype/detect.lua | 11 ++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index ec568b5b82..ca91f3f402 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2604,7 +2604,9 @@ function M.match(args) -- If the function tries to use the filename that is nil then it will fail, -- but this enables checks which do not need a filename to still work. local ok - ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name) + ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name, function(ext) + return dispatch(extension[ext], name, bufnr) + end) if ok and ft then return ft end diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index edffdde9c7..b3d9fedeae 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1420,7 +1420,7 @@ local patterns_hashbang = { ---@private -- File starts with "#!". -local function match_from_hashbang(contents, path) +local function match_from_hashbang(contents, path, dispatch_extension) local first_line = contents[1] -- Check for a line like "#!/usr/bin/env {options} bash". Turn it into -- "#!/usr/bin/bash" to make matching easier. @@ -1473,6 +1473,11 @@ local function match_from_hashbang(contents, path) return ft end end + + -- If nothing matched, check the extension table. For a hashbang like + -- '#!/bin/env foo', this will set the filetype to 'fooscript' assuming + -- the filetype for the 'foo' extension is 'fooscript' in the extension table. + return dispatch_extension(name) end local patterns_text = { @@ -1652,10 +1657,10 @@ local function match_from_text(contents, path) return cvs_diff(path, contents) end -M.match_contents = function(contents, path) +function M.match_contents(contents, path, dispatch_extension) local first_line = contents[1] if first_line:find('^#!') then - return match_from_hashbang(contents, path) + return match_from_hashbang(contents, path, dispatch_extension) else return match_from_text(contents, path) end -- cgit From a289e82142fdc5ff657dd30198546eeb1e115fe9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 15 Feb 2023 12:26:07 +0000 Subject: fix(treesitter): make params optional --- runtime/lua/vim/treesitter/query.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index a0522d7cda..008e5a54d7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -487,7 +487,7 @@ local directive_handlers = { ---@param name string Name of the predicate, without leading # ---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param force boolean +---@param force boolean|nil function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -510,7 +510,7 @@ end --- - pattern: see |treesitter-query| --- - predicate: list of strings containing the full directive being called, e.g. --- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` ----@param force boolean +---@param force boolean|nil function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) -- cgit From f43fa301c1a2817239e046a242902af65b7cac71 Mon Sep 17 00:00:00 2001 From: Eduard Baturin Date: Sat, 18 Feb 2023 09:43:59 +0300 Subject: fix(lsp): check if the buffer is a directory before w! it (#22289) --- runtime/lua/vim/lsp/util.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 38051e6410..4beb4fc367 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -759,9 +759,11 @@ function M.rename(old_fname, new_fname, opts) vim.fn.bufload(oldbuf) -- The there may be pending changes in the buffer - api.nvim_buf_call(oldbuf, function() - vim.cmd('w!') - end) + if vim.fn.isdirectory(old_fname) == 0 then + api.nvim_buf_call(oldbuf, function() + vim.cmd('w!') + end) + end local ok, err = os.rename(old_fname, new_fname) assert(ok, err) -- cgit From 2f6413797499a1bdac28c20a491e8ea3be33bda3 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 19 Feb 2023 01:27:32 +0100 Subject: vim-patch:9.0.1319: PRQL files are not recognized (#22319) Problem: PRQL files are not recognized. Solution: Add a filetype pattern for PRQL files. (Matthias Queitsch, closes vim/vim#12018) https://github.com/vim/vim/commit/9de960ace0f017fcfeaf64a2f6492f0f88b11fdb Co-authored-by: Matthias Queitsch --- 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 ca91f3f402..4ee7d46b20 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -810,6 +810,7 @@ local extension = { pdb = 'prolog', pml = 'promela', proto = 'proto', + prql = 'prql', ['psd1'] = 'ps1', ['psm1'] = 'ps1', ['ps1'] = 'ps1', -- cgit From bdf6d8733e2a954031de86e9a2d09c3b03f6641d Mon Sep 17 00:00:00 2001 From: Jason Hansen Date: Mon, 20 Feb 2023 23:24:47 -0700 Subject: fix(lsp): wrong format of bufnr and client order in error message (#22336) --- runtime/lua/vim/lsp.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c5392ac154..0a620e1367 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1736,8 +1736,8 @@ function lsp.buf_detach_client(bufnr, client_id) vim.notify( string.format( 'Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', - client_id, - bufnr + bufnr, + client_id ) ) return -- cgit From 8714a4009c0f0be0bb27a6b3eb486eeb3d9f3049 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 21 Feb 2023 17:09:18 +0000 Subject: feat(treesitter): add filetype -> lang API Problem: vim.treesitter does not know how to map a specific filetype to a parser. This creates problems since in a few places (including in vim.treesitter itself), the filetype is incorrectly used in place of lang. Solution: Add an API to enable this: - Add vim.treesitter.language.add() as a replacement for vim.treesitter.language.require_language(). - Optional arguments are now passed via an opts table. - Also takes a filetype (or list of filetypes) so we can keep track of what filetypes are associated with which langs. - Deprecated vim.treesitter.language.require_language(). - Add vim.treesitter.language.get_lang() which returns the associated lang for a given filetype. - Add vim.treesitter.language.register() to associate filetypes to a lang without loading the parser. --- runtime/lua/vim/treesitter.lua | 24 +++++---- runtime/lua/vim/treesitter/health.lua | 2 +- runtime/lua/vim/treesitter/language.lua | 84 ++++++++++++++++++++++++++--- runtime/lua/vim/treesitter/languagetree.lua | 4 +- runtime/lua/vim/treesitter/query.lua | 2 +- 5 files changed, 94 insertions(+), 22 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4127198576..c524b20bc2 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -29,6 +29,8 @@ setmetatable(M, { end, }) +---@diagnostic disable:invisible + --- Creates a new parser --- --- It is not recommended to use this; use |get_parser()| instead. @@ -39,16 +41,15 @@ setmetatable(M, { --- ---@return LanguageTree object to use for parsing function M._create_parser(bufnr, lang, opts) - language.require_language(lang) if bufnr == 0 then bufnr = vim.api.nvim_get_current_buf() end vim.fn.bufload(bufnr) - local self = LanguageTree.new(bufnr, lang, opts) + language.add(lang, { filetype = vim.bo[bufnr].filetype }) - ---@diagnostic disable:invisible + local self = LanguageTree.new(bufnr, lang, opts) ---@private local function bytes_cb(_, ...) @@ -96,13 +97,16 @@ function M.get_parser(bufnr, lang, opts) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end + if lang == nil then + local ft = vim.bo[bufnr].filetype + lang = language.get_lang(ft) or ft + -- TODO(lewis6991): we should error here and not default to ft + -- if not lang then + -- error(string.format('filetype %s of buffer %d is not associated with any lang', ft, bufnr)) + -- end + end - if parsers[bufnr] == nil then - lang = lang or a.nvim_buf_get_option(bufnr, 'filetype') - parsers[bufnr] = M._create_parser(bufnr, lang, opts) - elseif lang and parsers[bufnr]:lang() ~= lang then - -- Only try to create a new parser if lang is provided - -- and it doesn't match the stored parser + if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then parsers[bufnr] = M._create_parser(bufnr, lang, opts) end @@ -123,7 +127,7 @@ function M.get_string_parser(str, lang, opts) str = { str, 'string' }, lang = { lang, 'string' }, }) - language.require_language(lang) + language.add(lang) return LanguageTree.new(str, lang, opts) end diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index c0a1eca0ce..1abcdd0b31 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -17,7 +17,7 @@ function M.check() for _, parser in pairs(parsers) do local parsername = vim.fn.fnamemodify(parser, ':t:r') - local is_loadable, ret = pcall(ts.language.require_language, parsername) + local is_loadable, ret = pcall(ts.language.add, parsername) if not is_loadable or not ret then health.report_error( diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 8634e53b7b..8637d7d544 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -2,19 +2,66 @@ local a = vim.api local M = {} +---@type table +local ft_to_lang = {} + +---@param filetype string +---@return string|nil +function M.get_lang(filetype) + return ft_to_lang[filetype] +end + +---@deprecated +function M.require_language(lang, path, silent, symbol_name) + return M.add(lang, { + silent = silent, + path = path, + symbol_name = symbol_name, + }) +end + +---@class treesitter.RequireLangOpts +---@field path? string +---@field silent? boolean +---@field filetype? string|string[] +---@field symbol_name? string + --- Asserts that a parser for the language {lang} is installed. --- --- Parsers are searched in the `parser` runtime directory, or the provided {path} --- ---@param lang string Language the parser should parse (alphanumerical and `_` only) ----@param path (string|nil) Optional path the parser is located at ----@param silent (boolean|nil) Don't throw an error if language not found ----@param symbol_name (string|nil) Internal symbol name for the language to load +---@param opts (table|nil) Options: +--- - filetype (string|string[]) Filetype(s) that lang can be parsed with. +--- Note this is not strictly the same as lang since a single lang can +--- parse multiple filetypes. +--- Defaults to lang. +--- - path (string|nil) Optional path the parser is located at +--- - symbol_name (string|nil) Internal symbol name for the language to load +--- - silent (boolean|nil) Don't throw an error if language not found ---@return boolean If the specified language is installed -function M.require_language(lang, path, silent, symbol_name) +function M.add(lang, opts) + ---@cast opts treesitter.RequireLangOpts + opts = opts or {} + local path = opts.path + local silent = opts.silent + local filetype = opts.filetype or lang + local symbol_name = opts.symbol_name + + vim.validate({ + lang = { lang, 'string' }, + path = { path, 'string', true }, + silent = { silent, 'boolean', true }, + symbol_name = { symbol_name, 'string', true }, + filetype = { filetype, { 'string', 'table' }, true }, + }) + + M.register(lang, filetype or lang) + if vim._ts_has_language(lang) then return true end + if path == nil then if not (lang and lang:match('[%w_]+') == lang) then if silent then @@ -35,9 +82,9 @@ function M.require_language(lang, path, silent, symbol_name) end if silent then - return pcall(function() - vim._ts_add_language(path, lang, symbol_name) - end) + if not pcall(vim._ts_add_language, path, lang, symbol_name) then + return false + end else vim._ts_add_language(path, lang, symbol_name) end @@ -45,6 +92,27 @@ function M.require_language(lang, path, silent, symbol_name) return true end +--- Register a lang to be used for a filetype (or list of filetypes). +---@param lang string Language to register +---@param filetype string|string[] Filetype(s) to associate with lang +function M.register(lang, filetype) + vim.validate({ + lang = { lang, 'string' }, + filetype = { filetype, { 'string', 'table' } }, + }) + + local filetypes ---@type string[] + if type(filetype) == 'string' then + filetypes = { filetype } + else + filetypes = filetype + end + + for _, f in ipairs(filetypes) do + ft_to_lang[f] = lang + end +end + --- Inspects the provided language. --- --- Inspecting provides some useful information on the language like node names, ... @@ -52,7 +120,7 @@ end ---@param lang string Language ---@return table function M.inspect_language(lang) - M.require_language(lang) + M.add(lang) return vim._ts_inspect_language(lang) end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 8255c6f4fe..81ad83db2c 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -51,7 +51,7 @@ LanguageTree.__index = LanguageTree --- query per language. ---@return LanguageTree parser object function LanguageTree.new(source, lang, opts) - language.require_language(lang) + language.add(lang) ---@type LanguageTreeOpts opts = opts or {} @@ -170,7 +170,7 @@ function LanguageTree:parse() local seen_langs = {} ---@type table for lang, injection_ranges in pairs(injections_by_lang) do - local has_lang = language.require_language(lang, nil, true) + local has_lang = language.add(lang, { silent = true }) -- Child language trees should just be ignored if not found, since -- they can depend on the text of a node. Intermediate strings diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 008e5a54d7..83910316a6 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -252,7 +252,7 @@ end) --- ---@return Query Parsed query function M.parse_query(lang, query) - language.require_language(lang) + language.add(lang) local cached = query_cache[lang][query] if cached then return cached -- cgit From d1b34b74580fc09d86144c35e24a1a8a0d531b36 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 22 Feb 2023 10:58:15 +0100 Subject: vim-patch:9.0.1337: yuck files are not recognized (#22358) Problem: Yuck files are not recognized. Solution: Add a filetype pattern for yuck files. (Amaan Qureshi, closes vim/vim#12033) https://github.com/vim/vim/commit/cfce5cf542db20c7beba5b4211c0ae3305a64a43 Co-authored-by: Amaan Qureshi --- 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 4ee7d46b20..0dd2dcfcbe 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1131,6 +1131,7 @@ local extension = { yml = 'yaml', yaml = 'yaml', yang = 'yang', + yuck = 'yuck', ['z8a'] = 'z8a', zig = 'zig', zir = 'zir', -- cgit From 675826da63e879efa97c0998fea192ed5c79d56e Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 22 Feb 2023 08:01:08 -0700 Subject: refactor(treesitter): Add vim.treesitter.get_node() (#22360) This function replaces both vim.treesitter.get_node_at_pos() and vim.treesitter.get_node_at_cursor(). These two functions are similar enough that they don't need separate interfaces. Even worse, get_node_at_pos() returns a TSNode while get_node_at_cursor() returns a string, so the two functions behave slightly differently. vim.treesitter.get_node() combines these two into a more streamlined interface. With no arguments, it returns the node under the cursor in the current buffer. Optionally, it can accept a buffer number or a position to get the node at a given position in a given buffer. --- runtime/lua/vim/treesitter.lua | 57 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index c524b20bc2..c1e5325519 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -286,6 +286,50 @@ function M.get_captures_at_cursor(winnr) return captures end +--- Returns the smallest named node at the given position +--- +---@param opts table|nil Optional keyword arguments: +--- - bufnr integer|nil Buffer number (nil or 0 for current buffer) +--- - pos table|nil 0-indexed (row, col) tuple. Defaults to cursor position in the +--- current window. Required if {bufnr} is not the current buffer +--- - ignore_injections boolean Ignore injected languages (default true) +--- +---@return TSNode | nil Node at the given position +function M.get_node(opts) + opts = opts or {} + + local bufnr = opts.bufnr + + if not bufnr or bufnr == 0 then + bufnr = a.nvim_get_current_buf() + end + + local row, col + if opts.pos then + assert(#opts.pos == 2, 'Position must be a (row, col) tuple') + row, col = opts.pos[1], opts.pos[2] + else + assert( + bufnr == a.nvim_get_current_buf(), + 'Position must be explicitly provided when not using the current buffer' + ) + local pos = a.nvim_win_get_cursor(0) + -- Subtract one to account for 1-based row indexing in nvim_win_get_cursor + row, col = pos[1] - 1, pos[2] + end + + assert(row >= 0 and col >= 0, 'Invalid position: row and col must be non-negative') + + local ts_range = { row, col, row, col } + + local root_lang_tree = M.get_parser(bufnr) + if not root_lang_tree then + return + end + + return root_lang_tree:named_node_for_range(ts_range, opts) +end + --- Returns the smallest named node at the given position --- ---@param bufnr integer Buffer number (0 for current buffer) @@ -296,12 +340,16 @@ end --- - ignore_injections boolean Ignore injected languages (default true) --- ---@return TSNode|nil under the cursor +---@deprecated function M.get_node_at_pos(bufnr, row, col, opts) + vim.deprecate('vim.treesitter.get_node_at_pos()', 'vim.treesitter.get_node()', '0.10') if bufnr == 0 then bufnr = a.nvim_get_current_buf() end local ts_range = { row, col, row, col } + opts = opts or {} + local root_lang_tree = M.get_parser(bufnr, opts.lang) if not root_lang_tree then return @@ -315,12 +363,13 @@ end ---@param winnr (integer|nil) Window handle or 0 for current window (default) --- ---@return string Name of node under the cursor +---@deprecated function M.get_node_at_cursor(winnr) + vim.deprecate('vim.treesitter.get_node_at_cursor()', 'vim.treesitter.get_node():type()', '0.10') winnr = winnr or 0 local bufnr = a.nvim_win_get_buf(winnr) - local cursor = a.nvim_win_get_cursor(winnr) - return M.get_node_at_pos(bufnr, cursor[1] - 1, cursor[2], { ignore_injections = false }):type() + return M.get_node({ bufnr = bufnr, ignore_injections = false }):type() end --- Starts treesitter highlighting for a buffer @@ -493,8 +542,8 @@ function M.show_tree(opts) a.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - local cursor = a.nvim_win_get_cursor(win) - local cursor_node = M.get_node_at_pos(buf, cursor[1] - 1, cursor[2], { + local cursor_node = M.get_node({ + bufnr = buf, lang = opts.lang, ignore_injections = false, }) -- cgit From 6752f1005d26c93a033d856a60b7b296f3e51634 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 14 Dec 2022 19:58:18 +0100 Subject: docs: naming conventions, guidelines close #21063 --- runtime/lua/vim/keymap.lua | 50 ++++++++++++++++------------------------------ runtime/lua/vim/ui.lua | 7 ++++--- 2 files changed, 21 insertions(+), 36 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index ef1c66ea20..911b584c14 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -1,51 +1,35 @@ local keymap = {} ---- Add a new |mapping|. +--- Adds a new |mapping|. --- Examples: ---
lua
----   -- Can add mapping to Lua functions
+---   -- Map to a Lua function:
 ---   vim.keymap.set('n', 'lhs', function() print("real lua function") end)
----
----   -- Can use it to map multiple modes
+---   -- Map to multiple modes:
 ---   vim.keymap.set({'n', 'v'}, 'lr', vim.lsp.buf.references, { buffer=true })
----
----   -- Can add mapping for specific buffer
+---   -- Buffer-local mapping:
 ---   vim.keymap.set('n', 'w', "w", { silent = true, buffer = 5 })
----
----   -- Expr mappings
+---   -- Expr mapping:
 ---   vim.keymap.set('i', '', function()
 ---     return vim.fn.pumvisible() == 1 and "" or ""
 ---   end, { expr = true })
----   --  mappings
+---   --  mapping:
 ---   vim.keymap.set('n', '[%%', '(MatchitNormalMultiBackward)')
 --- 
--- ---- Note that in a mapping like: ----
lua
----    vim.keymap.set('n', 'asdf', require('jkl').my_fun)
---- 
---- ---- the ``require('jkl')`` gets evaluated during this call in order to access the function. ---- If you want to avoid this cost at startup you can wrap it in a function, for example: ----
lua
----    vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
---- 
---- ----@param mode string|table Same mode short names as |nvim_set_keymap()|. +---@param mode string|table 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 also be a Lua function. --- ----@param opts table|nil A table of |:map-arguments|. ---- + Accepts options accepted by the {opts} parameter in |nvim_set_keymap()|, ---- with the following notable differences: ---- - replace_keycodes: Defaults to `true` if "expr" is `true`. ---- - noremap: Always overridden with the inverse of "remap" (see below). ---- + In addition to those options, the table accepts the following keys: ---- - buffer: (number or boolean) Add a mapping to the given buffer. ---- When `0` or `true`, use the current buffer. ---- - remap: (boolean) Make the mapping recursive. ---- This is the inverse of the "noremap" option from |nvim_set_keymap()|. +---@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" number|boolean Creates buffer-local mapping, `0` or `true` +--- for current buffer. +--- - remap: (boolean) Make the mapping recursive. Inverses "noremap". --- Defaults to `false`. ---@see |nvim_set_keymap()| function keymap.set(mode, lhs, rhs, opts) diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 8f5be15221..aaee175f3a 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -1,6 +1,7 @@ local M = {} ---- Prompts the user to pick a single item from a collection of entries +--- Prompts the user to pick from a list of items, allowing arbitrary (potentially asynchronous) +--- work until `on_choice`. --- ---@param items table Arbitrary items ---@param opts table Additional options @@ -35,7 +36,6 @@ local M = {} --- end --- end) --- - function M.select(items, opts, on_choice) vim.validate({ items = { items, 'table', false }, @@ -55,7 +55,8 @@ function M.select(items, opts, on_choice) end end ---- Prompts the user for input +--- Prompts the user for input, allowing arbitrary (potentially asynchronous) work until +--- `on_confirm`. --- ---@param opts table Additional options. See |input()| --- - prompt (string|nil) -- cgit From 05de0f4fea237132ff3a9a5a35e9c711c4ece579 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 21 Feb 2023 17:03:04 +0100 Subject: docs(treesitter): fix parse errors --- runtime/lua/vim/treesitter.lua | 4 ++-- runtime/lua/vim/treesitter/languagetree.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index c1e5325519..c4df5b2d24 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -157,7 +157,7 @@ end --- Returns the node's range or an unpacked range table --- ----@param node_or_range (TSNode|table) Node or table of positions +---@param node_or_range (TSNode | table) Node or table of positions --- ---@return integer start_row ---@return integer start_col @@ -339,7 +339,7 @@ end --- - lang string|nil Parser language --- - ignore_injections boolean Ignore injected languages (default true) --- ----@return TSNode|nil under the cursor +---@return TSNode | nil Node at the given position ---@deprecated function M.get_node_at_pos(bufnr, row, col, opts) vim.deprecate('vim.treesitter.get_node_at_pos()', 'vim.treesitter.get_node()', '0.10') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 81ad83db2c..c9fd4bb2ea 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -653,7 +653,7 @@ end ---@param range Range `{ 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 +---@return TSNode | nil Found node function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) if tree then -- cgit From 6dfbeb0d990d24657754463c6ab155c19e7f5f56 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 21 Feb 2023 17:39:29 +0100 Subject: docs: fix more treesitter parsing errors --- runtime/lua/vim/treesitter.lua | 2 +- runtime/lua/vim/treesitter/query.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index c4df5b2d24..44922bbc4d 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -218,7 +218,7 @@ end ---@param row integer Position row ---@param col integer Position column --- ----@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` +---@return table[] List of captures `{ capture = "name", metadata = { ... } }` function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then bufnr = a.nvim_get_current_buf() diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 83910316a6..58a29f2fe0 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -639,7 +639,7 @@ end --- -- typically useful info about the node: --- local type = node:type() -- type of the captured node --- local row1, col1, row2, col2 = node:range() -- range of the capture ---- ... use the info here ... +--- -- ... use the info here ... --- end --- --- @@ -693,7 +693,7 @@ end --- --- local node_data = metadata[id] -- Node level metadata --- ---- ... use the info here ... +--- -- ... use the info here ... --- end --- end --- -- cgit From f140175564001cc1ad84128a0147a5d2f7798a63 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 23 Feb 2023 13:35:46 +0100 Subject: refactor(lsp): remove workaround for missing bit module (#22373) --- runtime/lua/vim/lsp/semantic_tokens.lua | 15 ++++----------- 1 file changed, 4 insertions(+), 11 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 b1bc48dac6..00b4757ea9 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -1,6 +1,7 @@ local api = vim.api local handlers = require('vim.lsp.handlers') local util = require('vim.lsp.util') +local bit = require('bit') --- @class STTokenRange --- @field line number line number 0-based @@ -58,18 +59,10 @@ local function modifiers_from_number(x, modifiers_table) local modifiers = {} local idx = 1 while x > 0 do - if _G.bit then - if _G.bit.band(x, 1) == 1 then - modifiers[#modifiers + 1] = modifiers_table[idx] - end - x = _G.bit.rshift(x, 1) - else - --TODO(jdrouhard): remove this branch once `bit` module is available for non-LuaJIT (#21222) - if x % 2 == 1 then - modifiers[#modifiers + 1] = modifiers_table[idx] - end - x = math.floor(x / 2) + if bit.band(x, 1) == 1 then + modifiers[#modifiers + 1] = modifiers_table[idx] end + x = bit.rshift(x, 1) idx = idx + 1 end -- cgit From 75e53341f37eeeda7d9be7f934249f7e5e4397e9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Feb 2023 15:19:52 +0000 Subject: perf(treesitter): smarter languagetree invalidation Problem: Treesitter injections are slow because all injected trees are invalidated on every change. Solution: Implement smarter invalidation to avoid reparsing injected regions. - In on_bytes, try and update self._regions as best we can. This PR just offsets any regions after the change. - Add valid flags for each region in self._regions. - Call on_bytes recursively for all children. - We still need to run the query every time for the top level tree. I don't know how to avoid this. However, if the new injection ranges don't change, then we re-use the old trees and avoid reparsing children. This should result in roughly a 2-3x reduction in tree parsing when the comment injections are enabled. --- runtime/lua/vim/treesitter/_range.lua | 126 ++++++++++++++ runtime/lua/vim/treesitter/languagetree.lua | 249 ++++++++++++++++++---------- runtime/lua/vim/treesitter/query.lua | 2 +- 3 files changed, 290 insertions(+), 87 deletions(-) create mode 100644 runtime/lua/vim/treesitter/_range.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua new file mode 100644 index 0000000000..b87542c20f --- /dev/null +++ b/runtime/lua/vim/treesitter/_range.lua @@ -0,0 +1,126 @@ +local api = vim.api + +local M = {} + +---@alias Range4 {[1]: integer, [2]: integer, [3]: integer, [4]: integer} +---@alias Range6 {[1]: integer, [2]: integer, [3]: integer, [4]: integer, [5]: integer, [6]: integer} + +---@private +---@param a_row integer +---@param a_col integer +---@param b_row integer +---@param b_col integer +---@return integer +--- 1: a > b +--- 0: a == b +--- -1: a < b +local function cmp_pos(a_row, a_col, b_row, b_col) + if a_row == b_row then + if a_col > b_col then + return 1 + elseif a_col < b_col then + return -1 + else + return 0 + end + elseif a_row > b_row then + return 1 + end + + return -1 +end + +M.cmp_pos = { + lt = function(...) + return cmp_pos(...) == -1 + end, + le = function(...) + return cmp_pos(...) ~= 1 + end, + gt = function(...) + return cmp_pos(...) == 1 + end, + ge = function(...) + return cmp_pos(...) ~= -1 + end, + eq = function(...) + return cmp_pos(...) == 0 + end, + ne = function(...) + return cmp_pos(...) ~= 0 + end, +} + +setmetatable(M.cmp_pos, { __call = cmp_pos }) + +---@private +---@param r1 Range4|Range6 +---@param r2 Range4|Range6 +---@return boolean +function M.intercepts(r1, r2) + local off_1 = #r1 == 6 and 1 or 0 + local off_2 = #r1 == 6 and 1 or 0 + + local srow_1, scol_1, erow_1, ecol_1 = r1[1], r2[2], r1[3 + off_1], r1[4 + off_1] + local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + + -- r1 is above r2 + if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then + return false + end + + -- r1 is below r2 + if M.cmp_pos.ge(srow_1, scol_1, erow_2, ecol_2) then + return false + end + + return true +end + +---@private +---@param r1 Range4|Range6 +---@param r2 Range4|Range6 +---@return boolean whether r1 contains r2 +function M.contains(r1, r2) + local off_1 = #r1 == 6 and 1 or 0 + local off_2 = #r1 == 6 and 1 or 0 + + local srow_1, scol_1, erow_1, ecol_1 = r1[1], r2[2], r1[3 + off_1], r1[4 + off_1] + local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + + -- start doesn't fit + if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then + return false + end + + -- end doesn't fit + if M.cmp_pos.lt(erow_1, ecol_1, erow_2, ecol_2) then + return false + end + + return true +end + +---@private +---@param source integer|string +---@param range Range4 +---@return Range6 +function M.add_bytes(source, range) + local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] + local start_byte = 0 + local end_byte = 0 + -- TODO(vigoux): proper byte computation here, and account for EOL ? + if type(source) == 'number' then + -- Easy case, this is a buffer parser + start_byte = api.nvim_buf_get_offset(source, start_row) + start_col + end_byte = api.nvim_buf_get_offset(source, end_row) + end_col + elseif type(source) == 'string' then + -- string parser, single `\n` delimited string + start_byte = vim.fn.byteidx(source, start_col) + end_byte = vim.fn.byteidx(source, end_col) + end + + return { start_row, start_col, start_byte, end_row, end_col, end_byte } +end + +return M diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index c9fd4bb2ea..2d4e2e595b 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -1,9 +1,8 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') +local Range = require('vim.treesitter._range') ----@alias Range {[1]: integer, [2]: integer, [3]: integer, [4]: integer} --- ---@alias TSCallbackName ---| 'changedtree' ---| 'bytes' @@ -24,11 +23,13 @@ local language = require('vim.treesitter.language') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language ----@field private _regions Range[][] List of regions this tree should manage and parse +---@field private _regions Range6[][] List of regions this tree should manage and parse ---@field private _lang string Language name ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ----@field private _valid boolean If the parsed tree is valid +---@field private _valid boolean|table If the parsed tree is valid +--- TODO(lewis6991): combine _regions, _valid and _trees +---@field private _is_child boolean local LanguageTree = {} ---@class LanguageTreeOpts @@ -114,6 +115,9 @@ end --- If the tree is invalid, call `parse()`. --- This will return the updated tree. function LanguageTree:is_valid() + if type(self._valid) == 'table' then + return #self._valid == #self._regions + end return self._valid end @@ -127,6 +131,16 @@ function LanguageTree:source() return self._source end +---@private +---This is only exposed so it can be wrapped for profiling +---@param old_tree TSTree +---@return TSTree, integer[] +function LanguageTree:_parse_tree(old_tree) + local tree, tree_changes = self._parser:parse(old_tree, self._source) + self:_do_callback('changedtree', tree_changes, tree) + return tree, tree_changes +end + --- Parses all defined regions using a treesitter parser --- for the language this tree represents. --- This will run the injection query for this language to @@ -135,35 +149,27 @@ end ---@return TSTree[] ---@return table|nil Change list function LanguageTree:parse() - if self._valid then + if self:is_valid() then return self._trees end - local parser = self._parser local changes = {} - local old_trees = self._trees - self._trees = {} - -- If there are no ranges, set to an empty list -- so the included ranges in the parser are cleared. - if self._regions and #self._regions > 0 then + if #self._regions > 0 then for i, ranges in ipairs(self._regions) do - local old_tree = old_trees[i] - parser:set_included_ranges(ranges) - - local tree, tree_changes = parser:parse(old_tree, self._source) - self:_do_callback('changedtree', tree_changes, tree) - - table.insert(self._trees, tree) - vim.list_extend(changes, tree_changes) + if not self._valid or not self._valid[i] then + self._parser:set_included_ranges(ranges) + local tree, tree_changes = self:_parse_tree(self._trees[i]) + self._trees[i] = tree + vim.list_extend(changes, tree_changes) + end end else - local tree, tree_changes = parser:parse(old_trees[1], self._source) - self:_do_callback('changedtree', tree_changes, tree) - - table.insert(self._trees, tree) - vim.list_extend(changes, tree_changes) + local tree, tree_changes = self:_parse_tree(self._trees[1]) + self._trees = { tree } + changes = tree_changes end local injections_by_lang = self:_get_injections() @@ -249,6 +255,7 @@ function LanguageTree:add_child(lang) end self._children[lang] = LanguageTree.new(self._source, lang, self._opts) + self._children[lang]._is_child = true self:invalidate() self:_do_callback('child_added', self._children[lang]) @@ -298,43 +305,35 @@ end --- This allows for embedded languages to be parsed together across different --- nodes, which is useful for templating languages like ERB and EJS. --- ---- Note: This call invalidates the tree and requires it to be parsed again. ---- ---@private ----@param regions integer[][][] List of regions this tree should manage and parse. +---@param regions Range4[][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(regions) -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(regions) do for i, range in ipairs(region) do if type(range) == 'table' and #range == 4 then - ---@diagnostic disable-next-line:no-unknown - local start_row, start_col, end_row, end_col = unpack(range) - local start_byte = 0 - local end_byte = 0 - local source = self._source - -- TODO(vigoux): proper byte computation here, and account for EOL ? - if type(source) == 'number' then - -- Easy case, this is a buffer parser - start_byte = a.nvim_buf_get_offset(source, start_row) + start_col - end_byte = a.nvim_buf_get_offset(source, end_row) + end_col - elseif type(self._source) == 'string' then - -- string parser, single `\n` delimited string - start_byte = vim.fn.byteidx(self._source, start_col) - end_byte = vim.fn.byteidx(self._source, end_col) - end + region[i] = Range.add_bytes(self._source, range) + end + end + end - region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte } + if #self._regions ~= #regions then + self._trees = {} + self:invalidate() + elseif self._valid ~= false then + if self._valid == true then + self._valid = {} + end + for i = 1, #regions do + self._valid[i] = true + if not vim.deep_equal(self._regions[i], regions[i]) then + self._valid[i] = nil + self._trees[i] = nil end end end self._regions = regions - -- Trees are no longer valid now that we have changed regions. - -- TODO(vigoux,steelsojka): Look into doing this smarter so we can use some of the - -- old trees for incremental parsing. Currently, this only - -- affects injected languages. - self._trees = {} - self:invalidate() end --- Gets the set of included regions @@ -346,10 +345,10 @@ end ---@param node TSNode ---@param id integer ---@param metadata TSMetadata ----@return Range +---@return Range4 local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then - return metadata[id].range --[[@as Range]] + return metadata[id].range --[[@as Range4]] end return { node:range() } end @@ -378,7 +377,7 @@ function LanguageTree:_get_injections() self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do local lang = nil ---@type string - local ranges = {} ---@type Range[] + local ranges = {} ---@type Range4[] local combined = metadata.combined ---@type boolean -- Directives can configure how injections are captured as well as actual node captures. @@ -408,6 +407,7 @@ function LanguageTree:_get_injections() -- Lang should override any other language tag if name == 'language' and not lang then + ---@diagnostic disable-next-line lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'combined' then combined = true @@ -426,6 +426,8 @@ function LanguageTree:_get_injections() end end + assert(type(lang) == 'string') + -- Each tree index should be isolated from the other nodes. if not injections[tree_index] then injections[tree_index] = {} @@ -446,7 +448,7 @@ function LanguageTree:_get_injections() end end - ---@type table + ---@type table local result = {} -- Generate a map by lang of node lists. @@ -485,6 +487,45 @@ function LanguageTree:_do_callback(cb_name, ...) end end +---@private +---@param regions Range6[][] +---@param old_range Range6 +---@param new_range Range6 +---@return table region indices to invalidate +local function update_regions(regions, old_range, new_range) + ---@type table + local valid = {} + + for i, ranges in ipairs(regions or {}) do + valid[i] = true + for j, r in ipairs(ranges) do + if Range.intercepts(r, old_range) then + valid[i] = nil + break + end + + -- Range after change. Adjust + if Range.cmp_pos.gt(r[1], r[2], old_range[4], old_range[5]) then + local byte_offset = new_range[6] - old_range[6] + local row_offset = new_range[4] - old_range[4] + + -- Update the range to avoid invalidation in set_included_regions() + -- which will compare the regions against the parsed injection regions + ranges[j] = { + r[1] + row_offset, + r[2], + r[3] + byte_offset, + r[4] + row_offset, + r[5], + r[6] + byte_offset, + } + end + end + end + + return valid +end + ---@private ---@param bufnr integer ---@param changed_tick integer @@ -510,14 +551,53 @@ function LanguageTree:_on_bytes( new_col, new_byte ) - self:invalidate() - local old_end_col = old_col + ((old_row == 0) and start_col or 0) local new_end_col = new_col + ((new_row == 0) and start_col or 0) - -- Edit all trees recursively, together BEFORE emitting a bytes callback. - -- In most cases this callback should only be called from the root tree. - self:for_each_tree(function(tree) + local old_range = { + start_row, + start_col, + start_byte, + start_row + old_row, + old_end_col, + start_byte + old_byte, + } + + local new_range = { + start_row, + start_col, + start_byte, + start_row + new_row, + new_end_col, + start_byte + new_byte, + } + + local valid_regions = update_regions(self._regions, old_range, new_range) + + if #self._regions == 0 or #valid_regions == 0 then + self._valid = false + else + self._valid = valid_regions + end + + for _, child in pairs(self._children) do + child:_on_bytes( + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) + end + + -- Edit trees together BEFORE emitting a bytes callback. + for _, tree in ipairs(self._trees) do tree:edit( start_byte, start_byte + old_byte, @@ -529,22 +609,24 @@ function LanguageTree:_on_bytes( start_row + new_row, new_end_col ) - end) + end - self:_do_callback( - 'bytes', - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) + if not self._is_child then + self:_do_callback( + 'bytes', + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) + end end ---@private @@ -595,19 +677,15 @@ end ---@private ---@param tree TSTree ----@param range Range +---@param range Range4 ---@return boolean local function tree_contains(tree, range) - local start_row, start_col, end_row, end_col = tree:root():range() - local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) - local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) - - return start_fits and end_fits + return Range.contains({ tree:root():range() }, range) end --- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range Range `{ start_line, start_col, end_line, end_col }` +---@param range Range4 `{ start_line, start_col, end_line, end_col }` ---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do @@ -621,7 +699,7 @@ end --- Gets the tree that contains {range}. --- ----@param range Range `{ start_line, start_col, end_line, end_col }` +---@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 @@ -631,10 +709,9 @@ function LanguageTree:tree_for_range(range, opts) if not ignore then for _, child in pairs(self._children) do - for _, tree in pairs(child:trees()) do - if tree_contains(tree, range) then - return tree - end + local tree = child:tree_for_range(range, opts) + if tree then + return tree end end end @@ -650,7 +727,7 @@ end --- Gets the smallest named node that contains {range}. --- ----@param range Range `{ start_line, start_col, end_line, end_col }` +---@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 @@ -663,7 +740,7 @@ end --- Gets the appropriate language that contains {range}. --- ----@param range Range `{ start_line, start_col, end_line, end_col }` +---@param range Range4 `{ start_line, start_col, end_line, end_col }` ---@return LanguageTree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 58a29f2fe0..13d98a0625 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -406,7 +406,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata ---@field [integer] TSMetadata ---@field [string] integer|string ----@field range Range +---@field range Range4 ---@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) -- cgit From 8c339aa04b2a1ca99a297b2eada8ebc6218f5f1c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 23 Feb 2023 17:52:33 +0100 Subject: vim-patch:9.0.1346: Starlark files are not recognized (#22380) Problem: Starlark files are not recognized. Solution: Add patterns for Starlark files. (Amaan Qureshi, closes vim/vim#12049) https://github.com/vim/vim/commit/ca06b30073de22dc120b532e90fbee2a10ef9772 Co-authored-by: Amaan Qureshi --- 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 0dd2dcfcbe..ae3ad6f628 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -995,6 +995,9 @@ local extension = { ssa = 'ssa', ass = 'ssa', st = 'st', + ipd = 'starlark', + star = 'starlark', + starlark = 'starlark', imata = 'stata', ['do'] = 'stata', mata = 'stata', -- cgit From 1df3f5ec6aca24cbe7b78ead5c37ad06a65c84e8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Feb 2023 17:05:20 +0000 Subject: feat(treesitter): upstream foldexpr from nvim-treesitter --- runtime/lua/vim/treesitter.lua | 20 ++++ runtime/lua/vim/treesitter/_fold.lua | 173 +++++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 runtime/lua/vim/treesitter/_fold.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 44922bbc4d..fead7b7b1b 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -115,6 +115,16 @@ function M.get_parser(bufnr, lang, opts) return parsers[bufnr] end +---@private +---@param bufnr (integer|nil) Buffer number +---@return boolean +function M._has_parser(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = a.nvim_get_current_buf() + end + return parsers[bufnr] ~= nil +end + --- Returns a string parser --- ---@param str string Text to parse @@ -612,4 +622,14 @@ function M.show_tree(opts) }) end +--- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': +---
lua
+--- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
+--- 
+---@param lnum integer|nil Line number to calculate fold level for +---@return string +function M.foldexpr(lnum) + return require('vim.treesitter._fold').foldexpr(lnum) +end + return M diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua new file mode 100644 index 0000000000..a66cc6d543 --- /dev/null +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -0,0 +1,173 @@ +local api = vim.api + +local M = {} + +--- Memoizes a function based on the buffer tick of the provided bufnr. +--- The cache entry is cleared when the buffer is detached to avoid memory leaks. +---@generic F: function +---@param fn F fn to memoize, taking the bufnr as first argument +---@return F +local function memoize_by_changedtick(fn) + ---@type table + local cache = {} + + ---@param bufnr integer + return function(bufnr, ...) + local tick = api.nvim_buf_get_changedtick(bufnr) + + if cache[bufnr] then + if cache[bufnr].last_tick == tick then + return cache[bufnr].result + end + else + local function detach_handler() + cache[bufnr] = nil + end + + -- Clean up logic only! + api.nvim_buf_attach(bufnr, false, { + on_detach = detach_handler, + on_reload = detach_handler, + }) + end + + cache[bufnr] = { + result = fn(bufnr, ...), + last_tick = tick, + } + + return cache[bufnr].result + end +end + +---@param bufnr integer +---@param capture string +---@param query_name string +---@param callback fun(id: integer, node:TSNode, metadata: TSMetadata) +local function iter_matches_with_capture(bufnr, capture, query_name, callback) + local parser = vim.treesitter.get_parser(bufnr) + + if not parser then + return + end + + parser:for_each_tree(function(tree, lang_tree) + local lang = lang_tree:lang() + local query = vim.treesitter.query.get_query(lang, query_name) + if query then + local root = tree:root() + local start, _, stop = root:range() + for _, match, metadata in query:iter_matches(root, bufnr, start, stop) do + for id, node in pairs(match) do + if query.captures[id] == capture then + callback(id, node, metadata) + end + end + end + end + end) +end + +---@private +--- TODO(lewis6991): copied from languagetree.lua. Consolidate +---@param node TSNode +---@param id integer +---@param metadata TSMetadata +---@return Range +local function get_range_from_metadata(node, id, metadata) + if metadata[id] and metadata[id].range then + return metadata[id].range --[[@as Range]] + end + return { node:range() } +end + +-- This is cached on buf tick to avoid computing that multiple times +-- Especially not for every line in the file when `zx` is hit +---@param bufnr integer +---@return table +local folds_levels = memoize_by_changedtick(function(bufnr) + local max_fold_level = vim.wo.foldnestmax + local function trim_level(level) + if level > max_fold_level then + return max_fold_level + end + return level + end + + -- start..stop is an inclusive range + local start_counts = {} ---@type table + local stop_counts = {} ---@type table + + local prev_start = -1 + local prev_stop = -1 + + local min_fold_lines = vim.wo.foldminlines + + iter_matches_with_capture(bufnr, 'fold', 'folds', function(id, node, metadata) + local range = get_range_from_metadata(node, id, metadata) + local start, stop, stop_col = range[1], range[3], range[4] + + 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 > min_fold_lines and not (start == prev_start and stop == prev_stop) then + start_counts[start] = (start_counts[start] or 0) + 1 + stop_counts[stop] = (stop_counts[stop] or 0) + 1 + prev_start = start + prev_stop = stop + end + end) + + ---@type table + local levels = {} + local current_level = 0 + + -- We now have the list of fold opening and closing, fill the gaps and mark where fold start + for lnum = 0, api.nvim_buf_line_count(bufnr) do + local last_trimmed_level = trim_level(current_level) + current_level = current_level + (start_counts[lnum] or 0) + local trimmed_level = trim_level(current_level) + current_level = current_level - (stop_counts[lnum] or 0) + + -- Determine if it's the start/end of a fold + -- NB: vim's fold-expr interface does not have a mechanism to indicate that + -- two (or more) folds start at this line, so it cannot distinguish between + -- ( \n ( \n )) \n (( \n ) \n ) + -- versus + -- ( \n ( \n ) \n ( \n ) \n ) + -- If it did have such a mechanism, (trimmed_level - last_trimmed_level) + -- would be the correct number of starts to pass on. + local prefix = '' + if trimmed_level - last_trimmed_level > 0 then + prefix = '>' + end + + levels[lnum + 1] = prefix .. tostring(trimmed_level) + end + + return levels +end) + +---@param lnum integer|nil +---@return string +function M.foldexpr(lnum) + lnum = lnum or vim.v.lnum + local bufnr = api.nvim_get_current_buf() + + ---@diagnostic disable-next-line:invisible + if not vim.treesitter._has_parser(bufnr) or not lnum then + return '0' + end + + local levels = folds_levels(bufnr) or {} + + return levels[lnum] or '0' +end + +return M -- cgit From 3f35ebb14dd8b1ceeef45c4f42949f14e3b54b88 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Feb 2023 18:09:44 +0000 Subject: fix(treesitter): fixup language invalidation (#22381) --- runtime/lua/vim/treesitter/languagetree.lua | 38 +++++++++++++++++++---------- 1 file changed, 25 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 2d4e2e595b..122496cff3 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -27,7 +27,7 @@ local Range = require('vim.treesitter._range') ---@field private _lang string Language name ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ----@field private _valid boolean|table If the parsed tree is valid +---@field private _valid boolean|table If the parsed tree is valid --- TODO(lewis6991): combine _regions, _valid and _trees ---@field private _is_child boolean local LanguageTree = {} @@ -115,10 +115,18 @@ end --- If the tree is invalid, call `parse()`. --- This will return the updated tree. function LanguageTree:is_valid() - if type(self._valid) == 'table' then - return #self._valid == #self._regions + local valid = self._valid + + if type(valid) == 'table' then + for _, v in ipairs(valid) do + if not v then + return false + end + end + return true end - return self._valid + + return valid end --- Returns a map of language to child tree. @@ -323,11 +331,17 @@ function LanguageTree:set_included_regions(regions) elseif self._valid ~= false then if self._valid == true then self._valid = {} + for i = 1, #regions do + self._valid[i] = true + end end + for i = 1, #regions do - self._valid[i] = true if not vim.deep_equal(self._regions[i], regions[i]) then - self._valid[i] = nil + self._valid[i] = false + end + + if not self._valid[i] then self._trees[i] = nil end end @@ -491,16 +505,16 @@ end ---@param regions Range6[][] ---@param old_range Range6 ---@param new_range Range6 ----@return table region indices to invalidate +---@return table region indices to invalidate local function update_regions(regions, old_range, new_range) - ---@type table + ---@type table local valid = {} for i, ranges in ipairs(regions or {}) do valid[i] = true for j, r in ipairs(ranges) do if Range.intercepts(r, old_range) then - valid[i] = nil + valid[i] = false break end @@ -572,12 +586,10 @@ function LanguageTree:_on_bytes( start_byte + new_byte, } - local valid_regions = update_regions(self._regions, old_range, new_range) - - if #self._regions == 0 or #valid_regions == 0 then + if #self._regions == 0 then self._valid = false else - self._valid = valid_regions + self._valid = update_regions(self._regions, old_range, new_range) end for _, child in pairs(self._children) do -- cgit From 5e1308b7caa89b458cc97d2db956379aa54ed68a Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Thu, 23 Feb 2023 16:12:01 -0500 Subject: vim-patch:9.0.1348: Un-grammar files are not recognized (#22383) Problem: Un-grammar files are not recognized. Solution: Add patterns for Un-grammar files. (Amaan Qureshi, closes vim/vim#12034) https://github.com/vim/vim/commit/44e08c1cf83f5a50f8b21613551304a6651c1161 --- 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 ae3ad6f628..3a68b61914 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1056,6 +1056,7 @@ local extension = { uc = 'uc', uit = 'uil', uil = 'uil', + ungram = 'ungrammar', sba = 'vb', vb = 'vb', dsm = 'vb', -- cgit From c57af5d41cd039194dbd9c6fb5b68b377d2a5b59 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Feb 2023 09:50:59 +0000 Subject: feat(treesitter)!: remove silent option from language.add() Simply use `pcall` if you want to silence an error. --- runtime/lua/vim/treesitter/language.lua | 32 ++++++++++------------------- runtime/lua/vim/treesitter/languagetree.lua | 2 +- 2 files changed, 12 insertions(+), 22 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 8637d7d544..0796383bf5 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -13,11 +13,19 @@ end ---@deprecated function M.require_language(lang, path, silent, symbol_name) - return M.add(lang, { + local opts = { silent = silent, path = path, symbol_name = symbol_name, - }) + } + + if silent then + local installed = pcall(M.add, lang, opts) + return installed + end + + M.add(lang, opts) + return true end ---@class treesitter.RequireLangOpts @@ -38,20 +46,16 @@ end --- Defaults to lang. --- - path (string|nil) Optional path the parser is located at --- - symbol_name (string|nil) Internal symbol name for the language to load ---- - silent (boolean|nil) Don't throw an error if language not found ----@return boolean If the specified language is installed function M.add(lang, opts) ---@cast opts treesitter.RequireLangOpts opts = opts or {} local path = opts.path - local silent = opts.silent local filetype = opts.filetype or lang local symbol_name = opts.symbol_name vim.validate({ lang = { lang, 'string' }, path = { path, 'string', true }, - silent = { silent, 'boolean', true }, symbol_name = { symbol_name, 'string', true }, filetype = { filetype, { 'string', 'table' }, true }, }) @@ -64,32 +68,18 @@ function M.add(lang, opts) if path == nil then if not (lang and lang:match('[%w_]+') == lang) then - if silent then - return false - end error("'" .. lang .. "' is not a valid language name") end local fname = 'parser/' .. lang .. '.*' local paths = a.nvim_get_runtime_file(fname, false) if #paths == 0 then - if silent then - return false - end error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end path = paths[1] end - if silent then - if not pcall(vim._ts_add_language, path, lang, symbol_name) then - return false - end - else - vim._ts_add_language(path, lang, symbol_name) - end - - return true + vim._ts_add_language(path, lang, symbol_name) end --- Register a lang to be used for a filetype (or list of filetypes). diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 122496cff3..7ec7bbfa12 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -184,7 +184,7 @@ function LanguageTree:parse() local seen_langs = {} ---@type table for lang, injection_ranges in pairs(injections_by_lang) do - local has_lang = language.add(lang, { silent = true }) + local has_lang = pcall(language.add, lang) -- Child language trees should just be ignored if not found, since -- they can depend on the text of a node. Intermediate strings -- cgit From 1803dadb209b9193ad8673b243091a4602b3a855 Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 24 Feb 2023 19:55:50 +0800 Subject: refactor(lsp): remove deprecated code (#22389) --- runtime/lua/vim/lsp.lua | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 0a620e1367..206c7c9b7e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1294,26 +1294,6 @@ function lsp.start_client(config) client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities) - - -- Deprecation wrapper: this will be removed in 0.8 - local mt = {} - mt.__index = function(table, key) - if key == 'resolved_capabilities' then - vim.notify_once( - '[LSP] Accessing client.resolved_capabilities is deprecated, ' - .. 'update your plugins or configuration to access client.server_capabilities instead.' - .. 'The new key/value pairs in server_capabilities directly match those ' - .. 'defined in the language server protocol', - vim.log.levels.WARN - ) - rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities)) - return rawget(table, key) - else - return rawget(table, key) - end - end - setmetatable(client, mt) - client.supports_method = function(method) local required_capability = lsp._request_name_to_capability[method] -- if we don't know about the method, assume that the client supports it. -- cgit From 4297127f14fa1b9062db7de0d63981887ebed063 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 24 Feb 2023 15:34:52 +0100 Subject: vim-patch:9.0.1350: CPON files are not recognized (#22392) Problem: CPON files are not recognized. Solution: Add patterns for CPON files. (Amaan Qureshi, closes vim/vim#12053) https://github.com/vim/vim/commit/c2254764bcada43eea894eb5852a26d5ac5ca8b0 Co-authored-by: Amaan Qureshi --- 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 3a68b61914..5475f09444 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -242,6 +242,7 @@ local extension = { csh = function(path, bufnr) return require('vim.filetype.detect').csh(path, bufnr) end, + cpon = 'cpon', moc = 'cpp', hh = 'cpp', tlh = 'cpp', -- cgit From 15cce77b383d0b0bfdaa1415cdde005cc43267d0 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 24 Feb 2023 17:50:37 +0100 Subject: vim-patch:9.0.1351: Dhall files are not recognized (#22393) Problem: Dhall files are not recognized. Solution: Add patterns for Dhall files. (Amaan Qureshi, closes vim/vim#12052) https://github.com/vim/vim/commit/def5521752abefe12db8cc3111a3b205ad1ac929 Co-authored-by: Amaan Qureshi --- 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 5475f09444..a4e078fd04 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -296,6 +296,7 @@ local extension = { desc = 'desc', directory = 'desktop', desktop = 'desktop', + dhall = 'dhall', diff = 'diff', rej = 'diff', Dockerfile = 'dockerfile', -- cgit From 5732aa706c639b3d775573d91d1139f24624629c Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sat, 25 Feb 2023 03:07:18 -0600 Subject: feat(lsp): implement workspace/didChangeWatchedFiles (#21293) --- runtime/lua/vim/_editor.lua | 1 + runtime/lua/vim/_watch.lua | 174 +++++++++++++++++++++++ runtime/lua/vim/lsp/_watchfiles.lua | 274 ++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/handlers.lua | 38 +++-- runtime/lua/vim/lsp/protocol.lua | 9 ++ 5 files changed, 487 insertions(+), 9 deletions(-) create mode 100644 runtime/lua/vim/_watch.lua create mode 100644 runtime/lua/vim/lsp/_watchfiles.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 3f27e61810..92444ff550 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -37,6 +37,7 @@ for k, v in pairs({ health = true, fs = true, secure = true, + _watch = true, }) do vim._submodules[k] = v end diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua new file mode 100644 index 0000000000..dba1522ec8 --- /dev/null +++ b/runtime/lua/vim/_watch.lua @@ -0,0 +1,174 @@ +local M = {} + +--- Enumeration describing the types of events watchers will emit. +M.FileChangeType = vim.tbl_add_reverse_lookup({ + Created = 1, + Changed = 2, + Deleted = 3, +}) + +---@private +--- Joins filepath elements by static '/' separator +--- +---@param ... (string) The path elements. +local function filepath_join(...) + return table.concat({ ... }, '/') +end + +---@private +--- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle +--- +---@param handle (uv_fs_event_t|uv_fs_poll_t) The handle to stop +local function stop(handle) + local _, stop_err = handle:stop() + assert(not stop_err, stop_err) + local is_closing, close_err = handle:is_closing() + assert(not close_err, close_err) + if not is_closing then + handle:close() + end +end + +--- Initializes and starts a |uv_fs_event_t| +--- +---@param path (string) The path to watch +---@param opts (table|nil) Additional options +--- - uvflags (table|nil) +--- Same flags as accepted by |uv.fs_event_start()| +---@param callback (function) The function called when new events +---@returns (function) A function to stop the watch +function M.watch(path, opts, callback) + vim.validate({ + path = { path, 'string', false }, + opts = { opts, 'table', true }, + callback = { callback, 'function', false }, + }) + + path = vim.fs.normalize(path) + local uvflags = opts and opts.uvflags or {} + local handle, new_err = vim.loop.new_fs_event() + assert(not new_err, new_err) + local _, start_err = handle:start(path, uvflags, function(err, filename, events) + assert(not err, err) + local fullpath = path + if filename then + filename = filename:gsub('\\', '/') + fullpath = filepath_join(fullpath, filename) + end + local change_type = events.change and M.FileChangeType.Changed or 0 + if events.rename then + local _, staterr, staterrname = vim.loop.fs_stat(fullpath) + if staterrname == 'ENOENT' then + change_type = M.FileChangeType.Deleted + else + assert(not staterr, staterr) + change_type = M.FileChangeType.Created + end + end + callback(fullpath, change_type) + end) + assert(not start_err, start_err) + return function() + stop(handle) + end +end + +local default_poll_interval_ms = 2000 + +---@private +--- Implementation for poll, hiding internally-used parameters. +--- +---@param watches (table|nil) A tree structure to maintain state for recursive watches. +--- - handle (uv_fs_poll_t) +--- The libuv handle +--- - cancel (function) +--- A function that cancels the handle and all children's handles +--- - is_dir (boolean) +--- Indicates whether the path is a directory (and the poll should +--- be invoked recursively) +--- - children (table|nil) +--- A mapping of directory entry name to its recursive watches +local function poll_internal(path, opts, callback, watches) + path = vim.fs.normalize(path) + local interval = opts and opts.interval or default_poll_interval_ms + watches = watches or { + is_dir = true, + } + + if not watches.handle then + local poll, new_err = vim.loop.new_fs_poll() + assert(not new_err, new_err) + watches.handle = poll + local _, start_err = poll:start( + path, + interval, + vim.schedule_wrap(function(err) + if err == 'ENOENT' then + return + end + assert(not err, err) + poll_internal(path, opts, callback, watches) + callback(path, M.FileChangeType.Changed) + end) + ) + assert(not start_err, start_err) + callback(path, M.FileChangeType.Created) + end + + watches.cancel = function() + if watches.children then + for _, w in pairs(watches.children) do + w.cancel() + end + end + stop(watches.handle) + end + + if watches.is_dir then + watches.children = watches.children or {} + local exists = {} + for name, ftype in vim.fs.dir(path) do + exists[name] = true + if not watches.children[name] then + watches.children[name] = { + is_dir = ftype == 'directory', + } + poll_internal(filepath_join(path, name), opts, callback, watches.children[name]) + end + end + + local newchildren = {} + for name, watch in pairs(watches.children) do + if exists[name] then + newchildren[name] = watch + else + watch.cancel() + watches.children[name] = nil + callback(path .. '/' .. name, M.FileChangeType.Deleted) + end + end + watches.children = newchildren + end + + return watches.cancel +end + +--- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the +--- directory at path. +--- +---@param path (string) The path to watch. Must refer to a directory. +---@param opts (table|nil) Additional options +--- - interval (number|nil) +--- Polling interval in ms as passed to |uv.fs_poll_start()|. Defaults to 2000. +---@param callback (function) The function called when new events +---@returns (function) A function to stop the watch. +function M.poll(path, opts, callback) + vim.validate({ + path = { path, 'string', false }, + opts = { opts, 'table', true }, + callback = { callback, 'function', false }, + }) + return poll_internal(path, opts, callback, nil) +end + +return M diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua new file mode 100644 index 0000000000..b9268b963c --- /dev/null +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -0,0 +1,274 @@ +local bit = require('bit') +local watch = require('vim._watch') +local protocol = require('vim.lsp.protocol') + +local M = {} + +---@private +---Parses the raw pattern into a number of Lua-native patterns. +--- +---@param pattern string The raw glob pattern +---@return table A list of Lua patterns. A match with any of them matches the input glob pattern. +local function parse(pattern) + local patterns = { '' } + + local path_sep = '[/\\]' + local non_path_sep = '[^/\\]' + + local function append(chunks) + local new_patterns = {} + for _, p in ipairs(patterns) do + for _, chunk in ipairs(chunks) do + table.insert(new_patterns, p .. chunk) + end + end + patterns = new_patterns + end + + local function split(s, sep) + local segments = {} + local segment = '' + local in_braces = false + local in_brackets = false + for i = 1, #s do + local c = string.sub(s, i, i) + if c == sep and not in_braces and not in_brackets then + table.insert(segments, segment) + segment = '' + else + if c == '{' then + in_braces = true + elseif c == '}' then + in_braces = false + elseif c == '[' then + in_brackets = true + elseif c == ']' then + in_brackets = false + end + segment = segment .. c + end + end + if segment ~= '' then + table.insert(segments, segment) + end + return segments + end + + local function escape(c) + if + c == '?' + or c == '.' + or c == '(' + or c == ')' + or c == '%' + or c == '[' + or c == ']' + or c == '*' + or c == '+' + or c == '-' + then + return '%' .. c + end + return c + end + + local segments = split(pattern, '/') + for i, segment in ipairs(segments) do + local last_seg = i == #segments + if segment == '**' then + local chunks = { + path_sep .. '-', + '.-' .. path_sep, + } + if last_seg then + chunks = { '.-' } + end + append(chunks) + else + local in_braces = false + local brace_val = '' + local in_brackets = false + local bracket_val = '' + for j = 1, #segment do + local char = string.sub(segment, j, j) + if char ~= '}' and in_braces then + brace_val = brace_val .. char + else + if in_brackets and (char ~= ']' or bracket_val == '') then + local res + if char == '-' then + res = char + elseif bracket_val == '' and char == '!' then + res = '^' + elseif char == '/' then + res = '' + else + res = escape(char) + end + bracket_val = bracket_val .. res + else + if char == '{' then + in_braces = true + elseif char == '[' then + in_brackets = true + elseif char == '}' then + local choices = split(brace_val, ',') + local parsed_choices = {} + for _, choice in ipairs(choices) do + table.insert(parsed_choices, parse(choice)) + end + append(vim.tbl_flatten(parsed_choices)) + in_braces = false + brace_val = '' + elseif char == ']' then + append({ '[' .. bracket_val .. ']' }) + in_brackets = false + bracket_val = '' + elseif char == '?' then + append({ non_path_sep }) + elseif char == '*' then + append({ non_path_sep .. '-' }) + else + append({ escape(char) }) + end + end + end + end + + if not last_seg and (segments[i + 1] ~= '**' or i + 1 < #segments) then + append({ path_sep }) + end + end + end + + return patterns +end + +---@private +--- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern +--- Modeled after VSCode's implementation: https://github.com/microsoft/vscode/blob/0319eed971719ad48e9093daba9d65a5013ec5ab/src/vs/base/common/glob.ts#L509 +--- +---@param pattern string|table The glob pattern (raw or parsed) to match. +---@param s string The string to match against pattern. +---@return boolean Whether or not pattern matches s. +function M._match(pattern, s) + if type(pattern) == 'string' then + pattern = parse(pattern) + end + -- Since Lua's built-in string pattern matching does not have an alternate + -- operator like '|', `parse` will construct one pattern for each possible + -- alternative. Any pattern that matches thus matches the glob. + for _, p in ipairs(pattern) do + if s:match('^' .. p .. '$') then + return true + end + end + return false +end + +M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll + +---@type table> client id -> registration id -> cancel function +local cancels = vim.defaulttable() + +local queue_timeout_ms = 100 +---@type table client id -> libuv timer which will send queued changes at its timeout +local queue_timers = {} +---@type table client id -> set of queued changes to send in a single LSP notification +local change_queues = {} +---@type table> client id -> URI -> last type of change processed +--- Used to prune consecutive events of the same type for the same file +local change_cache = vim.defaulttable() + +local to_lsp_change_type = { + [watch.FileChangeType.Created] = protocol.FileChangeType.Created, + [watch.FileChangeType.Changed] = protocol.FileChangeType.Changed, + [watch.FileChangeType.Deleted] = protocol.FileChangeType.Deleted, +} + +--- Registers the workspace/didChangeWatchedFiles capability dynamically. +--- +---@param reg table LSP Registration object. +---@param ctx table Context from the |lsp-handler|. +function M.register(reg, ctx) + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) + for _, w in ipairs(reg.registerOptions.watchers) do + local glob_patterns = {} + if type(w.globPattern) == 'string' then + for _, folder in ipairs(client.workspace_folders) do + table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern }) + end + else + table.insert(glob_patterns, w.globPattern) + end + for _, glob_pattern in ipairs(glob_patterns) do + local pattern = parse(glob_pattern.pattern) + local base_dir = nil + if type(glob_pattern.baseUri) == 'string' then + base_dir = glob_pattern.baseUri + elseif type(glob_pattern.baseUri) == 'table' then + base_dir = glob_pattern.baseUri.uri + end + assert(base_dir, "couldn't identify root of watch") + base_dir = vim.uri_to_fname(base_dir) + local kind = w.kind + or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + + table.insert( + cancels[client_id][reg.id], + M._watchfunc(base_dir, { uvflags = { recursive = true } }, function(fullpath, change_type) + change_type = to_lsp_change_type[change_type] + -- e.g. match kind with Delete bit (0b0100) to Delete change_type (3) + local kind_mask = bit.lshift(1, change_type - 1) + local change_type_match = bit.band(kind, kind_mask) == kind_mask + if not M._match(pattern, fullpath) or not change_type_match then + return + end + + local change = { + uri = vim.uri_from_fname(fullpath), + type = change_type, + } + + local last_type = change_cache[client_id][change.uri] + if last_type ~= change.type then + change_queues[client_id] = change_queues[client_id] or {} + table.insert(change_queues[client_id], change) + change_cache[client_id][change.uri] = change.type + end + + if not queue_timers[client_id] then + queue_timers[client_id] = vim.defer_fn(function() + client.notify('workspace/didChangeWatchedFiles', { + changes = change_queues[client_id], + }) + queue_timers[client_id] = nil + change_queues[client_id] = nil + change_cache[client_id] = nil + end, queue_timeout_ms) + end + end) + ) + end + end +end + +--- Unregisters the workspace/didChangeWatchedFiles capability dynamically. +--- +---@param unreg table LSP Unregistration object. +---@param ctx table Context from the |lsp-handler|. +function M.unregister(unreg, ctx) + local client_id = ctx.client_id + local client_cancels = cancels[client_id] + local reg_cancels = client_cancels[unreg.id] + while #reg_cancels > 0 do + table.remove(reg_cancels)() + end + client_cancels[unreg.id] = nil + if not next(cancels[client_id]) then + cancels[client_id] = nil + end +end + +return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 5096100a60..ee5b63d260 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -117,15 +117,35 @@ M['window/showMessageRequest'] = function(_, result) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability -M['client/registerCapability'] = function(_, _, ctx) - local client_id = ctx.client_id - local warning_tpl = 'The language server %s triggers a registerCapability ' - .. 'handler despite dynamicRegistration set to false. ' - .. 'Report upstream, this warning is harmless' - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format('id=%d', client_id) - local warning = string.format(warning_tpl, client_name) - log.warn(warning) +M['client/registerCapability'] = function(_, result, ctx) + local log_unsupported = false + for _, reg in ipairs(result.registrations) do + if reg.method == 'workspace/didChangeWatchedFiles' then + require('vim.lsp._watchfiles').register(reg, ctx) + else + log_unsupported = true + end + end + if log_unsupported then + local client_id = ctx.client_id + local warning_tpl = 'The language server %s triggers a registerCapability ' + .. 'handler despite dynamicRegistration set to false. ' + .. 'Report upstream, this warning is harmless' + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format('id=%d', client_id) + local warning = string.format(warning_tpl, client_name) + log.warn(warning) + end + return vim.NIL +end + +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability +M['client/unregisterCapability'] = function(_, result, ctx) + for _, unreg in ipairs(result.unregisterations) do + if unreg.method == 'workspace/didChangeWatchedFiles' then + require('vim.lsp._watchfiles').unregister(unreg, ctx) + end + end return vim.NIL end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 12345b6c8c..df1ab26667 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -28,6 +28,10 @@ end ---@class lsp.MessageActionItem ---@field title string +---@class lsp.FileEvent +---@field uri string +---@field type lsp.FileChangeType + local constants = { DiagnosticSeverity = { -- Reports an error. @@ -60,6 +64,7 @@ local constants = { }, -- The file event type. + ---@enum lsp.FileChangeType FileChangeType = { -- The file got created. Created = 1, @@ -841,6 +846,10 @@ function protocol.make_client_capabilities() semanticTokens = { refreshSupport = true, }, + didChangeWatchedFiles = { + dynamicRegistration = true, + relativePatternSupport = true, + }, }, experimental = nil, window = { -- cgit From f0f27e9aef7c237dd55fbb5c2cd47c2f42d01742 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Sat, 25 Feb 2023 11:17:28 +0100 Subject: Revert "feat(lsp): implement workspace/didChangeWatchedFiles (#21293)" This reverts commit 5732aa706c639b3d775573d91d1139f24624629c. Causes editor to freeze in projects with many watcher registrations --- runtime/lua/vim/_editor.lua | 1 - runtime/lua/vim/_watch.lua | 174 ----------------------- runtime/lua/vim/lsp/_watchfiles.lua | 274 ------------------------------------ runtime/lua/vim/lsp/handlers.lua | 38 ++--- runtime/lua/vim/lsp/protocol.lua | 9 -- 5 files changed, 9 insertions(+), 487 deletions(-) delete mode 100644 runtime/lua/vim/_watch.lua delete mode 100644 runtime/lua/vim/lsp/_watchfiles.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 92444ff550..3f27e61810 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -37,7 +37,6 @@ for k, v in pairs({ health = true, fs = true, secure = true, - _watch = true, }) do vim._submodules[k] = v end diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua deleted file mode 100644 index dba1522ec8..0000000000 --- a/runtime/lua/vim/_watch.lua +++ /dev/null @@ -1,174 +0,0 @@ -local M = {} - ---- Enumeration describing the types of events watchers will emit. -M.FileChangeType = vim.tbl_add_reverse_lookup({ - Created = 1, - Changed = 2, - Deleted = 3, -}) - ----@private ---- Joins filepath elements by static '/' separator ---- ----@param ... (string) The path elements. -local function filepath_join(...) - return table.concat({ ... }, '/') -end - ----@private ---- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle ---- ----@param handle (uv_fs_event_t|uv_fs_poll_t) The handle to stop -local function stop(handle) - local _, stop_err = handle:stop() - assert(not stop_err, stop_err) - local is_closing, close_err = handle:is_closing() - assert(not close_err, close_err) - if not is_closing then - handle:close() - end -end - ---- Initializes and starts a |uv_fs_event_t| ---- ----@param path (string) The path to watch ----@param opts (table|nil) Additional options ---- - uvflags (table|nil) ---- Same flags as accepted by |uv.fs_event_start()| ----@param callback (function) The function called when new events ----@returns (function) A function to stop the watch -function M.watch(path, opts, callback) - vim.validate({ - path = { path, 'string', false }, - opts = { opts, 'table', true }, - callback = { callback, 'function', false }, - }) - - path = vim.fs.normalize(path) - local uvflags = opts and opts.uvflags or {} - local handle, new_err = vim.loop.new_fs_event() - assert(not new_err, new_err) - local _, start_err = handle:start(path, uvflags, function(err, filename, events) - assert(not err, err) - local fullpath = path - if filename then - filename = filename:gsub('\\', '/') - fullpath = filepath_join(fullpath, filename) - end - local change_type = events.change and M.FileChangeType.Changed or 0 - if events.rename then - local _, staterr, staterrname = vim.loop.fs_stat(fullpath) - if staterrname == 'ENOENT' then - change_type = M.FileChangeType.Deleted - else - assert(not staterr, staterr) - change_type = M.FileChangeType.Created - end - end - callback(fullpath, change_type) - end) - assert(not start_err, start_err) - return function() - stop(handle) - end -end - -local default_poll_interval_ms = 2000 - ----@private ---- Implementation for poll, hiding internally-used parameters. ---- ----@param watches (table|nil) A tree structure to maintain state for recursive watches. ---- - handle (uv_fs_poll_t) ---- The libuv handle ---- - cancel (function) ---- A function that cancels the handle and all children's handles ---- - is_dir (boolean) ---- Indicates whether the path is a directory (and the poll should ---- be invoked recursively) ---- - children (table|nil) ---- A mapping of directory entry name to its recursive watches -local function poll_internal(path, opts, callback, watches) - path = vim.fs.normalize(path) - local interval = opts and opts.interval or default_poll_interval_ms - watches = watches or { - is_dir = true, - } - - if not watches.handle then - local poll, new_err = vim.loop.new_fs_poll() - assert(not new_err, new_err) - watches.handle = poll - local _, start_err = poll:start( - path, - interval, - vim.schedule_wrap(function(err) - if err == 'ENOENT' then - return - end - assert(not err, err) - poll_internal(path, opts, callback, watches) - callback(path, M.FileChangeType.Changed) - end) - ) - assert(not start_err, start_err) - callback(path, M.FileChangeType.Created) - end - - watches.cancel = function() - if watches.children then - for _, w in pairs(watches.children) do - w.cancel() - end - end - stop(watches.handle) - end - - if watches.is_dir then - watches.children = watches.children or {} - local exists = {} - for name, ftype in vim.fs.dir(path) do - exists[name] = true - if not watches.children[name] then - watches.children[name] = { - is_dir = ftype == 'directory', - } - poll_internal(filepath_join(path, name), opts, callback, watches.children[name]) - end - end - - local newchildren = {} - for name, watch in pairs(watches.children) do - if exists[name] then - newchildren[name] = watch - else - watch.cancel() - watches.children[name] = nil - callback(path .. '/' .. name, M.FileChangeType.Deleted) - end - end - watches.children = newchildren - end - - return watches.cancel -end - ---- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the ---- directory at path. ---- ----@param path (string) The path to watch. Must refer to a directory. ----@param opts (table|nil) Additional options ---- - interval (number|nil) ---- Polling interval in ms as passed to |uv.fs_poll_start()|. Defaults to 2000. ----@param callback (function) The function called when new events ----@returns (function) A function to stop the watch. -function M.poll(path, opts, callback) - vim.validate({ - path = { path, 'string', false }, - opts = { opts, 'table', true }, - callback = { callback, 'function', false }, - }) - return poll_internal(path, opts, callback, nil) -end - -return M diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua deleted file mode 100644 index b9268b963c..0000000000 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ /dev/null @@ -1,274 +0,0 @@ -local bit = require('bit') -local watch = require('vim._watch') -local protocol = require('vim.lsp.protocol') - -local M = {} - ----@private ----Parses the raw pattern into a number of Lua-native patterns. ---- ----@param pattern string The raw glob pattern ----@return table A list of Lua patterns. A match with any of them matches the input glob pattern. -local function parse(pattern) - local patterns = { '' } - - local path_sep = '[/\\]' - local non_path_sep = '[^/\\]' - - local function append(chunks) - local new_patterns = {} - for _, p in ipairs(patterns) do - for _, chunk in ipairs(chunks) do - table.insert(new_patterns, p .. chunk) - end - end - patterns = new_patterns - end - - local function split(s, sep) - local segments = {} - local segment = '' - local in_braces = false - local in_brackets = false - for i = 1, #s do - local c = string.sub(s, i, i) - if c == sep and not in_braces and not in_brackets then - table.insert(segments, segment) - segment = '' - else - if c == '{' then - in_braces = true - elseif c == '}' then - in_braces = false - elseif c == '[' then - in_brackets = true - elseif c == ']' then - in_brackets = false - end - segment = segment .. c - end - end - if segment ~= '' then - table.insert(segments, segment) - end - return segments - end - - local function escape(c) - if - c == '?' - or c == '.' - or c == '(' - or c == ')' - or c == '%' - or c == '[' - or c == ']' - or c == '*' - or c == '+' - or c == '-' - then - return '%' .. c - end - return c - end - - local segments = split(pattern, '/') - for i, segment in ipairs(segments) do - local last_seg = i == #segments - if segment == '**' then - local chunks = { - path_sep .. '-', - '.-' .. path_sep, - } - if last_seg then - chunks = { '.-' } - end - append(chunks) - else - local in_braces = false - local brace_val = '' - local in_brackets = false - local bracket_val = '' - for j = 1, #segment do - local char = string.sub(segment, j, j) - if char ~= '}' and in_braces then - brace_val = brace_val .. char - else - if in_brackets and (char ~= ']' or bracket_val == '') then - local res - if char == '-' then - res = char - elseif bracket_val == '' and char == '!' then - res = '^' - elseif char == '/' then - res = '' - else - res = escape(char) - end - bracket_val = bracket_val .. res - else - if char == '{' then - in_braces = true - elseif char == '[' then - in_brackets = true - elseif char == '}' then - local choices = split(brace_val, ',') - local parsed_choices = {} - for _, choice in ipairs(choices) do - table.insert(parsed_choices, parse(choice)) - end - append(vim.tbl_flatten(parsed_choices)) - in_braces = false - brace_val = '' - elseif char == ']' then - append({ '[' .. bracket_val .. ']' }) - in_brackets = false - bracket_val = '' - elseif char == '?' then - append({ non_path_sep }) - elseif char == '*' then - append({ non_path_sep .. '-' }) - else - append({ escape(char) }) - end - end - end - end - - if not last_seg and (segments[i + 1] ~= '**' or i + 1 < #segments) then - append({ path_sep }) - end - end - end - - return patterns -end - ----@private ---- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern ---- Modeled after VSCode's implementation: https://github.com/microsoft/vscode/blob/0319eed971719ad48e9093daba9d65a5013ec5ab/src/vs/base/common/glob.ts#L509 ---- ----@param pattern string|table The glob pattern (raw or parsed) to match. ----@param s string The string to match against pattern. ----@return boolean Whether or not pattern matches s. -function M._match(pattern, s) - if type(pattern) == 'string' then - pattern = parse(pattern) - end - -- Since Lua's built-in string pattern matching does not have an alternate - -- operator like '|', `parse` will construct one pattern for each possible - -- alternative. Any pattern that matches thus matches the glob. - for _, p in ipairs(pattern) do - if s:match('^' .. p .. '$') then - return true - end - end - return false -end - -M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll - ----@type table> client id -> registration id -> cancel function -local cancels = vim.defaulttable() - -local queue_timeout_ms = 100 ----@type table client id -> libuv timer which will send queued changes at its timeout -local queue_timers = {} ----@type table client id -> set of queued changes to send in a single LSP notification -local change_queues = {} ----@type table> client id -> URI -> last type of change processed ---- Used to prune consecutive events of the same type for the same file -local change_cache = vim.defaulttable() - -local to_lsp_change_type = { - [watch.FileChangeType.Created] = protocol.FileChangeType.Created, - [watch.FileChangeType.Changed] = protocol.FileChangeType.Changed, - [watch.FileChangeType.Deleted] = protocol.FileChangeType.Deleted, -} - ---- Registers the workspace/didChangeWatchedFiles capability dynamically. ---- ----@param reg table LSP Registration object. ----@param ctx table Context from the |lsp-handler|. -function M.register(reg, ctx) - local client_id = ctx.client_id - local client = vim.lsp.get_client_by_id(client_id) - for _, w in ipairs(reg.registerOptions.watchers) do - local glob_patterns = {} - if type(w.globPattern) == 'string' then - for _, folder in ipairs(client.workspace_folders) do - table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern }) - end - else - table.insert(glob_patterns, w.globPattern) - end - for _, glob_pattern in ipairs(glob_patterns) do - local pattern = parse(glob_pattern.pattern) - local base_dir = nil - if type(glob_pattern.baseUri) == 'string' then - base_dir = glob_pattern.baseUri - elseif type(glob_pattern.baseUri) == 'table' then - base_dir = glob_pattern.baseUri.uri - end - assert(base_dir, "couldn't identify root of watch") - base_dir = vim.uri_to_fname(base_dir) - local kind = w.kind - or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete - - table.insert( - cancels[client_id][reg.id], - M._watchfunc(base_dir, { uvflags = { recursive = true } }, function(fullpath, change_type) - change_type = to_lsp_change_type[change_type] - -- e.g. match kind with Delete bit (0b0100) to Delete change_type (3) - local kind_mask = bit.lshift(1, change_type - 1) - local change_type_match = bit.band(kind, kind_mask) == kind_mask - if not M._match(pattern, fullpath) or not change_type_match then - return - end - - local change = { - uri = vim.uri_from_fname(fullpath), - type = change_type, - } - - local last_type = change_cache[client_id][change.uri] - if last_type ~= change.type then - change_queues[client_id] = change_queues[client_id] or {} - table.insert(change_queues[client_id], change) - change_cache[client_id][change.uri] = change.type - end - - if not queue_timers[client_id] then - queue_timers[client_id] = vim.defer_fn(function() - client.notify('workspace/didChangeWatchedFiles', { - changes = change_queues[client_id], - }) - queue_timers[client_id] = nil - change_queues[client_id] = nil - change_cache[client_id] = nil - end, queue_timeout_ms) - end - end) - ) - end - end -end - ---- Unregisters the workspace/didChangeWatchedFiles capability dynamically. ---- ----@param unreg table LSP Unregistration object. ----@param ctx table Context from the |lsp-handler|. -function M.unregister(unreg, ctx) - local client_id = ctx.client_id - local client_cancels = cancels[client_id] - local reg_cancels = client_cancels[unreg.id] - while #reg_cancels > 0 do - table.remove(reg_cancels)() - end - client_cancels[unreg.id] = nil - if not next(cancels[client_id]) then - cancels[client_id] = nil - end -end - -return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index ee5b63d260..5096100a60 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -117,35 +117,15 @@ M['window/showMessageRequest'] = function(_, result) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability -M['client/registerCapability'] = function(_, result, ctx) - local log_unsupported = false - for _, reg in ipairs(result.registrations) do - if reg.method == 'workspace/didChangeWatchedFiles' then - require('vim.lsp._watchfiles').register(reg, ctx) - else - log_unsupported = true - end - end - if log_unsupported then - local client_id = ctx.client_id - local warning_tpl = 'The language server %s triggers a registerCapability ' - .. 'handler despite dynamicRegistration set to false. ' - .. 'Report upstream, this warning is harmless' - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format('id=%d', client_id) - local warning = string.format(warning_tpl, client_name) - log.warn(warning) - end - return vim.NIL -end - ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability -M['client/unregisterCapability'] = function(_, result, ctx) - for _, unreg in ipairs(result.unregisterations) do - if unreg.method == 'workspace/didChangeWatchedFiles' then - require('vim.lsp._watchfiles').unregister(unreg, ctx) - end - end +M['client/registerCapability'] = function(_, _, ctx) + local client_id = ctx.client_id + local warning_tpl = 'The language server %s triggers a registerCapability ' + .. 'handler despite dynamicRegistration set to false. ' + .. 'Report upstream, this warning is harmless' + local client = vim.lsp.get_client_by_id(client_id) + local client_name = client and client.name or string.format('id=%d', client_id) + local warning = string.format(warning_tpl, client_name) + log.warn(warning) return vim.NIL end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index df1ab26667..12345b6c8c 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -28,10 +28,6 @@ end ---@class lsp.MessageActionItem ---@field title string ----@class lsp.FileEvent ----@field uri string ----@field type lsp.FileChangeType - local constants = { DiagnosticSeverity = { -- Reports an error. @@ -64,7 +60,6 @@ local constants = { }, -- The file event type. - ---@enum lsp.FileChangeType FileChangeType = { -- The file got created. Created = 1, @@ -846,10 +841,6 @@ function protocol.make_client_capabilities() semanticTokens = { refreshSupport = true, }, - didChangeWatchedFiles = { - dynamicRegistration = true, - relativePatternSupport = true, - }, }, experimental = nil, window = { -- cgit From c1514d7e6762ed62dee027ecc29bafd4aae2206e Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 25 Feb 2023 18:47:05 +0100 Subject: fix(lsp): fix some type annotations (#22397) --- runtime/lua/vim/lsp.lua | 97 +++++++++++++++++++--------------------- runtime/lua/vim/lsp/protocol.lua | 9 +--- runtime/lua/vim/lsp/rpc.lua | 8 ++-- runtime/lua/vim/lsp/types.lua | 20 +++++++++ 4 files changed, 72 insertions(+), 62 deletions(-) create mode 100644 runtime/lua/vim/lsp/types.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 206c7c9b7e..d215b4c47e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -67,7 +67,7 @@ lsp._request_name_to_capability = { ---@private --- Concatenates and writes a list of strings to the Vim error buffer. --- ----@param {...} table[] List to write to the buffer +---@param ... string List to write to the buffer local function err_message(...) nvim_err_writeln(table.concat(vim.tbl_flatten({ ... }))) nvim_command('redraw') @@ -76,9 +76,8 @@ end ---@private --- Returns the buffer number for the given {bufnr}. --- ----@param bufnr (number|nil) Buffer number to resolve. Defaults to the current ----buffer if not given. ----@returns bufnr (number) Number of requested buffer +---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer +---@return integer bufnr local function resolve_bufnr(bufnr) validate({ bufnr = { bufnr, 'n', true } }) if bufnr == nil or bufnr == 0 then @@ -104,7 +103,7 @@ end --- Checks whether a given path is a directory. --- ---@param filename (string) path to check ----@returns true if {filename} exists and is a directory, false otherwise +---@return boolean # true if {filename} exists and is a directory, false otherwise local function is_dir(filename) validate({ filename = { filename, 's' } }) local stat = uv.fs_stat(filename) @@ -133,7 +132,7 @@ local format_line_ending = { ---@private ---@param bufnr (number) ----@returns (string) +---@return string local function buf_get_line_ending(bufnr) return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n' end @@ -142,7 +141,7 @@ local client_index = 0 ---@private --- Returns a new, unused client id. --- ----@returns (number) client id +---@return integer client_id local function next_client_id() client_index = client_index + 1 return client_index @@ -197,7 +196,7 @@ lsp.client_errors = tbl_extend( --- Normalizes {encoding} to valid LSP encoding names. --- ---@param encoding (string) Encoding to normalize ----@returns (string) normalized encoding name +---@return string # normalized encoding name local function validate_encoding(encoding) validate({ encoding = { encoding, 's' }, @@ -215,9 +214,8 @@ end --- Parses a command invocation into the command itself and its args. If there --- are no arguments, an empty table is returned as the second argument. --- ----@param input (List) ----@returns (string) the command ----@returns (list of strings) its arguments +---@param input string[] +---@return string command, string[] args #the command and arguments function lsp._cmd_parts(input) validate({ cmd = { @@ -244,9 +242,9 @@ end ---@private --- Augments a validator function with support for optional (nil) values. --- ----@param fn (fun(v)) The original validator function; should return a +---@param fn (fun(v): boolean) The original validator function; should return a ---bool. ----@returns (fun(v)) The augmented function. Also returns true if {v} is +---@return fun(v): boolean # The augmented function. Also returns true if {v} is ---`nil`. local function optional_validator(fn) return function(v) @@ -258,10 +256,8 @@ end --- Validates a client configuration as given to |vim.lsp.start_client()|. --- ---@param config (table) ----@returns (table) "Cleaned" config, containing only the command, its +---@return table config Cleaned config, containing the command, its ---arguments, and a valid encoding. ---- ----@see |vim.lsp.start_client()| local function validate_client_config(config) validate({ config = { config, 't' }, @@ -314,7 +310,7 @@ end --- Returns full text of buffer {bufnr} as a string. --- ---@param bufnr (number) Buffer handle, or 0 for current. ----@returns Buffer text as string. +---@return string # Buffer text as string. local function buf_get_full_text(bufnr) local line_ending = buf_get_line_ending(bufnr) local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending) @@ -331,7 +327,7 @@ end --- even if it has side effects. --- ---@param fn (function) Function to run ----@returns (function) Memoized function +---@return function fn Memoized function local function once(fn) local value local ran = false @@ -373,7 +369,7 @@ do --- @field lines string[] snapshot of buffer lines from last didChange --- @field lines_tmp string[] --- @field pending_changes table[] List of debounced changes in incremental sync mode - --- @field timer nil|userdata uv_timer + --- @field timer nil|uv.uv_timer_t uv_timer --- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification --- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet --- @field refs number how many clients are using this group @@ -648,7 +644,7 @@ do if debounce == 0 then send_changes(bufnr, group.sync_kind, state, buf_state) else - local timer = uv.new_timer() + local timer = assert(uv.new_timer(), 'Must be able to create timer') buf_state.timer = timer timer:start( debounce, @@ -1006,7 +1002,7 @@ end --- server will base its workspaceFolders, rootUri, and rootPath --- on initialization. --- ----@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be +---@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. function lsp.start_client(config) @@ -1037,7 +1033,7 @@ function lsp.start_client(config) --- Returns the default handler if the user hasn't set a custom one. --- ---@param method (string) LSP method name - ---@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers| + ---@return function|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers| local function resolve_handler(method) return handlers[method] or default_handlers[method] end @@ -1343,11 +1339,11 @@ function lsp.start_client(config) --- This is a thin wrapper around {client.rpc.request} with some additional --- checks for capabilities and handler availability. --- - ---@param method (string) LSP method name. - ---@param params (table) LSP request params. - ---@param handler (function|nil) Response |lsp-handler| for this method. - ---@param bufnr (number) Buffer handle (0 for current). - ---@returns ({status}, [request_id]): {status} is a bool indicating + ---@param method string LSP method name. + ---@param params table 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 ---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 @@ -1395,10 +1391,11 @@ function lsp.start_client(config) ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for --- a result. Defaults to 1000 ---@param bufnr (number) Buffer handle (0 for current). - ---@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|. - ---On timeout, cancel or error, returns `(nil, err)` where `err` is a - ---string describing the failure reason. If the request was unsuccessful - ---returns `nil`. + ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where + --- `err` and `result` come from the |lsp-handler|. + --- On timeout, cancel or error, returns `(nil, err)` where `err` is a + --- string describing the failure reason. If the request was unsuccessful + --- returns `nil`. ---@see |vim.lsp.buf_request_sync()| function client.request_sync(method, params, timeout_ms, bufnr) local request_result = nil @@ -1429,7 +1426,7 @@ function lsp.start_client(config) --- ---@param method string LSP method name. ---@param params table|nil LSP request params. - ---@returns {status} (bool) true if the notification was successful. + ---@return boolean status true if the notification was successful. ---If it is false, then it will always be false ---(the client has shutdown). function client.notify(method, params) @@ -1443,7 +1440,7 @@ function lsp.start_client(config) --- Cancels a request with a given request id. --- ---@param id (number) id of request to cancel - ---@returns true if any client returns true; false otherwise + ---@return boolean status true if notification was successful. false otherwise ---@see |vim.lsp.client.notify()| function client.cancel_request(id) validate({ id = { id, 'n' } }) @@ -1489,7 +1486,7 @@ function lsp.start_client(config) ---@private --- Checks whether a client is stopped. --- - ---@returns (bool) true if client is stopped or in the process of being + ---@return boolean # true if client is stopped or in the process of being ---stopped; false otherwise function client.is_stopped() return rpc.is_closing() @@ -1497,7 +1494,7 @@ function lsp.start_client(config) ---@private --- Runs the on_attach function from the client's config if it was defined. - ---@param bufnr (number) Buffer number + ---@param bufnr integer Buffer number function client._on_attach(bufnr) text_document_did_open_handler(bufnr, client) @@ -1771,8 +1768,8 @@ end --- Returns list of buffers attached to client_id. --- ----@param client_id number client id ----@returns list of buffer ids +---@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) return client and vim.tbl_keys(client.attached_buffers) or {} @@ -1906,7 +1903,7 @@ api.nvim_create_autocmd('VimLeavePre', { ---@param handler function|nil See |lsp-handler| --- If nil, follows resolution strategy defined in |lsp-handler-configuration| --- ----@returns 2-tuple: +---@return table, fun() 2-tuple: --- - Map of client-id:request-id pairs for all successful requests. --- - Function which can be used to cancel all the requests. You could instead --- iterate all clients and call their `cancel_request()` methods. @@ -1964,10 +1961,10 @@ end ---@param method (string) LSP method name ---@param params (table|nil) Parameters to send to the server ---@param callback (function) The callback to call when all requests are finished. --- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. --- A map of client_id:request_result will be provided to the callback --- ----@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`. +--- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. +--- A map of client_id:request_result will be provided to the callback +--- +---@return fun() cancel A function that will cancel all requests function lsp.buf_request_all(bufnr, method, params, callback) local request_results = {} local result_count = 0 @@ -2008,9 +2005,9 @@ end ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for a --- result. Defaults to 1000 --- ----@returns Map of client_id:request_result. On timeout, cancel or error, ---- returns `(nil, err)` where `err` is a string describing the failure ---- reason. +---@return table|nil result, string|nil err Map of client_id:request_result. +--- On timeout, cancel or error, returns `(nil, err)` where `err` is a string describing +--- the failure reason. function lsp.buf_request_sync(bufnr, method, params, timeout_ms) local request_results @@ -2035,7 +2032,7 @@ end ---@param method (string) Name of the request method ---@param params (any) Arguments to send to the server --- ----@returns true if any client returns true; false otherwise +---@return boolean success true if any client returns true; false otherwise function lsp.buf_notify(bufnr, method, params) validate({ bufnr = { bufnr, 'n', true }, @@ -2172,7 +2169,7 @@ function lsp.formatexpr(opts) for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do if client.supports_method('textDocument/rangeFormatting') then local params = util.make_formatting_params() - local end_line = vim.fn.getline(end_lnum) + local end_line = vim.fn.getline(end_lnum) --[[@as string]] local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding) params.range = { start = { @@ -2207,7 +2204,7 @@ end ---@param pattern string Pattern used to find a workspace symbol ---@param flags string See |tag-function| --- ----@returns A list of matching tags +---@return table[] tags A list of matching tags function lsp.tagfunc(...) return require('vim.lsp.tagfunc')(...) end @@ -2215,7 +2212,7 @@ end ---Checks whether a client is stopped. --- ---@param client_id (number) ----@returns true if client is stopped, false otherwise. +---@return boolean stopped true if client is stopped, false otherwise. function lsp.client_is_stopped(client_id) return active_clients[client_id] == nil end @@ -2262,7 +2259,7 @@ function lsp.set_log_level(level) end --- Gets the path of the logfile used by the LSP client. ----@returns (String) Path to logfile. +---@return string path to log file function lsp.get_log_path() return log.get_filename() end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 12345b6c8c..41dfc9e00e 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -20,14 +20,6 @@ function transform_schema_to_table() end --]=] ----@class lsp.ShowMessageRequestParams ----@field type lsp.MessageType ----@field message string ----@field actions nil|lsp.MessageActionItem[] - ----@class lsp.MessageActionItem ----@field title string - local constants = { DiagnosticSeverity = { -- Reports an error. @@ -60,6 +52,7 @@ local constants = { }, -- The file event type. + ---@enum lsp.FileChangeType FileChangeType = { -- The file got created. Created = 1, diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index f1492601ff..aa833deb99 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -319,9 +319,9 @@ end --- ---@param method (string) The invoked LSP method ---@param params (table|nil) Parameters for the invoked LSP method ----@param callback (function) Callback to invoke +---@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending ----@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not +---@return boolean success, integer|nil request_id true, request_id if request could be sent, `false` if not function Client:request(method, params, callback, notify_reply_callback) validate({ callback = { callback, 'f' }, @@ -538,9 +538,9 @@ local function public_client(client) --- ---@param method (string) The invoked LSP method ---@param params (table|nil) Parameters for the invoked LSP method - ---@param callback (function) Callback to invoke + ---@param callback fun(err: lsp.ResponseError | nil, result: any) Callback to invoke ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending - ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not + ---@return boolean success, integer|nil request_id true, message_id if request could be sent, `false` if not function result.request(method, params, callback, notify_reply_callback) return client:request(method, params, callback, notify_reply_callback) end diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua new file mode 100644 index 0000000000..1aea6841ee --- /dev/null +++ b/runtime/lua/vim/lsp/types.lua @@ -0,0 +1,20 @@ +---@meta + +---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: table, config: table|nil) + +---@class lsp.ResponseError +---@field code integer +---@field message string +---@field data string|number|boolean|table[]|table|nil + +---@class lsp.ShowMessageRequestParams +---@field type lsp.MessageType +---@field message string +---@field actions nil|lsp.MessageActionItem[] + +---@class lsp.MessageActionItem +---@field title string + +---@class lsp.FileEvent +---@field uri string +---@field type lsp.FileChangeType -- cgit From 774e59f3f9bf50c8350857c6722bb58df2dd940a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Feb 2023 16:53:33 +0000 Subject: feat(treesitter): expand the API --- runtime/lua/vim/treesitter/_meta.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 87b4560798..731a5ebf9f 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -2,6 +2,7 @@ ---@class TSNode ---@field id fun(self: TSNode): integer +---@field tree fun(self: TSNode): TSTree ---@field range fun(self: TSNode): integer, integer, integer, integer ---@field start fun(self: TSNode): integer, integer, integer ---@field end_ fun(self: TSNode): integer, integer, integer @@ -9,6 +10,7 @@ ---@field symbol fun(self: TSNode): integer ---@field named fun(self: TSNode): boolean ---@field missing fun(self: TSNode): boolean +---@field extra fun(self: TSNode): boolean ---@field child_count fun(self: TSNode): integer ---@field named_child_count fun(self: TSNode): integer ---@field child fun(self: TSNode, integer): TSNode @@ -21,7 +23,8 @@ ---@field next_named_sibling fun(self: TSNode): TSNode ---@field prev_named_sibling fun(self: TSNode): TSNode ---@field named_children fun(self: TSNode): TSNode[] ----@field has_error fun(self: TSNode): boolean +---@field has_changes fun(self: TSNode): boolean +---@field equal fun(self: TSNode, other: TSNode): boolean ---@field iter_children fun(self: TSNode): fun(): TSNode, string local TSNode = {} @@ -41,8 +44,11 @@ function TSNode:_rawquery(query, captures, start, end_) end ---@class TSParser ---@field parse fun(self: TSParser, tree, source: integer|string): TSTree, integer[] +---@field reset fun(self: TSParser) ---@field included_ranges fun(self: TSParser): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: integer[][]) +---@field set_timeout fun(self: TSParser, timeout: integer) +---@field timeout fun(self: TSParser): integer ---@class TSTree ---@field root fun(self: TSTree): TSNode -- cgit From da56f06037c26180021c1e3b73a77fc990f0c1e3 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 27 Feb 2023 10:49:19 +0000 Subject: fix(treesitter): remove virtual text from playground Implement the range and lang annotations as comments instead --- runtime/lua/vim/treesitter/playground.lua | 46 +++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index 001bc2d5bf..fd5b687195 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -138,6 +138,19 @@ end local decor_ns = api.nvim_create_namespace('ts.playground') +---@private +---@param lnum integer +---@param col integer +---@param end_lnum integer +---@param end_col integer +---@return string +local function get_range_str(lnum, col, end_col, end_lnum) + if lnum == end_lnum then + return string.format('[%d:%d-%d]', lnum + 1, col + 1, end_col) + end + return string.format('[%d:%d-%d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) +end + --- Write the contents of this Playground into {bufnr}. --- ---@param bufnr number Buffer number to write into. @@ -145,26 +158,31 @@ local decor_ns = api.nvim_create_namespace('ts.playground') function TSPlayground:draw(bufnr) vim.bo[bufnr].modifiable = true local lines = {} ---@type string[] + local lang_hl_marks = {} ---@type table[] + for _, item in self:iter() do - lines[#lines + 1] = string.rep(' ', item.depth) .. item.text + local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) + local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + local line = string.rep(' ', item.depth) .. item.text .. '; ' .. range_str .. lang_str + + if self.opts.lang then + lang_hl_marks[#lang_hl_marks + 1] = { + col = #line - #lang_str, + end_col = #line, + } + end + + lines[#lines + 1] = line end + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) - for i, item in self:iter() do - local range_str - if item.lnum == item.end_lnum then - range_str = string.format('[%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) - else - range_str = - string.format('[%d:%d-%d:%d]', item.lnum + 1, item.col + 1, item.end_lnum + 1, item.end_col) - end - - local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' - - api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, 0, { - virt_text = { { range_str, 'Comment' }, { lang_str, 'Title' } }, + for i, m in ipairs(lang_hl_marks) do + api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, { + hl_group = 'Title', + end_col = m.end_col, }) end -- cgit From 5aa37e20e0e6cbabf82c5cf1d35c6f5e6e48f099 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 27 Feb 2023 15:01:09 +0000 Subject: fix(treesitter): ipairs -> pairs Fixes: https://github.com/nvim-treesitter/nvim-treesitter/issues/4349 --- runtime/lua/vim/treesitter/languagetree.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 7ec7bbfa12..43fb866896 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -467,7 +467,7 @@ function LanguageTree:_get_injections() -- Generate a map by lang of node lists. -- Each list is a set of ranges that should be parsed together. - for _, lang_map in ipairs(injections) do + for _, lang_map in pairs(injections) do for lang, patterns in pairs(lang_map) do if not result[lang] then result[lang] = {} -- cgit From f64098a2df774c79dd454f63ac491570cdcaf2b2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 27 Feb 2023 15:33:18 +0000 Subject: fix(treesitter): fixup for health --- runtime/lua/vim/treesitter/health.lua | 22 ++++++++++------------ runtime/lua/vim/treesitter/language.lua | 2 +- 2 files changed, 11 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 1abcdd0b31..fd1188fde4 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -2,28 +2,26 @@ local M = {} local ts = vim.treesitter local health = require('vim.health') ---- Lists the parsers currently installed ---- ----@return string[] list of parser files -function M.list_parsers() - return vim.api.nvim_get_runtime_file('parser/*', true) -end - --- Performs a healthcheck for treesitter integration function M.check() - local parsers = M.list_parsers() + local parsers = vim.api.nvim_get_runtime_file('parser/*', true) health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version)) for _, parser in pairs(parsers) do local parsername = vim.fn.fnamemodify(parser, ':t:r') - local is_loadable, ret = pcall(ts.language.add, parsername) + local is_loadable, err_or_nil = pcall(ts.language.add, parsername) - if not is_loadable or not ret then + if not is_loadable then health.report_error( - string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?') + string.format( + 'Parser "%s" failed to load (path: %s): %s', + parsername, + parser, + err_or_nil or '?' + ) ) - elseif ret then + else local lang = ts.language.inspect_language(parsername) health.report_ok( string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 0796383bf5..5bcc786e88 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -63,7 +63,7 @@ function M.add(lang, opts) M.register(lang, filetype or lang) if vim._ts_has_language(lang) then - return true + return end if path == nil then -- cgit From 2a8e6a2f1a8e047a8efd84764d9a332bbd6ae96d Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Mon, 27 Feb 2023 15:08:31 -0500 Subject: vim-patch:9.0.1360: Cue files are not recognized (#22439) Problem: Cue files are not recognized. Solution: Add patterns for Cue files. (Amaan Qureshi, closes vim/vim#12067) https://github.com/vim/vim/commit/80c5b2c0f78b24e52c73bb162dda3ad85acd7e82 --- 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 a4e078fd04..256c053801 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -276,6 +276,7 @@ local extension = { feature = 'cucumber', cuh = 'cuda', cu = 'cuda', + cue = 'cue', pld = 'cupl', si = 'cuplsim', cyn = 'cynpp', -- cgit From f89e3497c88f59916e3bce2e902be9b0f8bf15ba Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 27 Feb 2023 14:19:41 -0600 Subject: docs(lsp): update cmd_env description (#22438) --- runtime/lua/vim/lsp.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index d215b4c47e..61a06ff7a7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -908,11 +908,11 @@ end --- the `cmd` process. Not related to `root_dir`. --- --- - cmd_env: (table) Environment flags to pass to the LSP on ---- spawn. Can be specified using keys like a map or as a list with `k=v` ---- pairs or both. Non-string values are coerced to string. +--- spawn. Must be specified using a map-like table. +--- Non-string values are coerced to string. --- Example: ---
----                   { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+---                   { PORT = 8080; HOST = "0.0.0.0"; }
 ---       
--- --- - detached: (boolean, default true) Daemonize the server process so that it runs in a -- cgit From 7574d58304aa3b030aca8f5feafc0f92ec1d81e2 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Tue, 28 Feb 2023 12:12:08 +0100 Subject: fix(inspect): alwasy resolve full treesitter lang hl groups --- runtime/lua/vim/_inspector.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index f46a525910..bb6608421b 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -69,7 +69,7 @@ function vim.inspect_pos(bufnr, row, col, filter) -- treesitter if filter.treesitter then for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do - capture.hl_group = '@' .. capture.capture + capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang table.insert(results.treesitter, resolve_hl(capture)) end end -- cgit From 7e19cabeb192d2e7f20d7bb965a3f62e1543d2ac Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 28 Feb 2023 12:38:33 +0100 Subject: perf(lsp): only redraw the windows containing LSP tokens redraw! redraws the entire screen instead of just the windows with the buffer which were actually changed. I considered trying to calculating the range for the delta but it looks tricky. Could a follow-up. --- runtime/lua/vim/lsp/semantic_tokens.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 00b4757ea9..24b5c6c24e 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -334,7 +334,8 @@ function STHighlighter:process_response(response, client, version) current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client) current_result.namespace_cleared = false - api.nvim_command('redraw!') + -- redraw all windows displaying buffer + api.nvim__buf_redraw_range(self.bufnr, 0, -1) end --- on_win handler for the decoration provider (see |nvim_set_decoration_provider|) -- cgit From 96d3616a531b2626e3ab9b4fa2c5dea03a927717 Mon Sep 17 00:00:00 2001 From: Jens Claes Date: Wed, 1 Mar 2023 11:35:16 +0100 Subject: fix(lsp): callHierarchy methods also require the callHierarchyProvider (#22427) --- 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 61a06ff7a7..fed56ff846 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -46,6 +46,8 @@ lsp._request_name_to_capability = { ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' }, ['textDocument/documentSymbol'] = { 'documentSymbolProvider' }, ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' }, + ['callHierarchy/incomingCalls'] = { 'callHierarchyProvider' }, + ['callHierarchy/outgoingCalls'] = { 'callHierarchyProvider' }, ['textDocument/rename'] = { 'renameProvider' }, ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' }, ['textDocument/codeAction'] = { 'codeActionProvider' }, -- cgit From 896d672736b32a8f4a4fa51844b44f266dcdcc6c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 1 Mar 2023 15:33:13 +0100 Subject: fix(lsp): use buffer scheme for files not stored on disk (#22407) Sending `didOpen` with a `file` scheme causes problems with some language servers because they expect the file to exist on disk. See https://github.com/microsoft/language-server-protocol/pull/1679 --- runtime/lua/vim/lsp.lua | 54 ++++++++++++++++++++++++++++++++++---------- runtime/lua/vim/lsp/util.lua | 9 ++++++-- 2 files changed, 49 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fed56ff846..a6d550f48f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -367,7 +367,7 @@ do --- @field offset_encoding "utf-8"|"utf-16"|"utf-32" --- --- @class CTBufferState - --- @field name string name of the buffer + --- @field uri string uri of the buffer --- @field lines string[] snapshot of buffer lines from last didChange --- @field lines_tmp string[] --- @field pending_changes table[] List of debounced changes in incremental sync mode @@ -486,8 +486,12 @@ do if buf_state then buf_state.refs = buf_state.refs + 1 else + local uri = vim.uri_from_bufnr(bufnr) + if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then + uri = uri:gsub('^file://', 'buffer://') + end buf_state = { - name = api.nvim_buf_get_name(bufnr), + uri = uri, lines = {}, lines_tmp = {}, pending_changes = {}, @@ -502,12 +506,26 @@ do end ---@private - function changetracking._get_and_set_name(client, bufnr, name) + ---@param client table + ---@param bufnr integer + ---@return string uri + function changetracking._get_uri(client, bufnr) local state = state_by_group[get_group(client)] or {} local buf_state = (state.buffers or {})[bufnr] - local old_name = buf_state.name - buf_state.name = name - return old_name + return assert(buf_state.uri, 'Must have an URI set') + end + + ---@private + ---@param client table + ---@param bufnr integer + ---@param uri string + ---@return string uri + function changetracking._get_and_set_uri(client, bufnr, uri) + local state = state_by_group[get_group(client)] or {} + local buf_state = (state.buffers or {})[bufnr] + local old_uri = buf_state.uri + buf_state.uri = uri + return old_uri end ---@private @@ -594,7 +612,7 @@ do { text = buf_get_full_text(bufnr) }, } end - local uri = vim.uri_from_bufnr(bufnr) + local uri = buf_state.uri for _, client in pairs(state.clients) do if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then client.notify('textDocument/didChange', { @@ -707,11 +725,14 @@ local function text_document_did_open_handler(bufnr, client) return end local filetype = nvim_buf_get_option(bufnr, 'filetype') - + local uri = vim.uri_from_bufnr(bufnr) + if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then + uri = uri:gsub('^file://', 'buffer://') + end local params = { textDocument = { version = 0, - uri = vim.uri_from_bufnr(bufnr), + uri = uri, languageId = client.config.get_language_id(bufnr, filetype), text = buf_get_full_text(bufnr), }, @@ -1560,8 +1581,13 @@ local function text_document_did_save_handler(bufnr) local text = once(buf_get_full_text) for_each_buffer_client(bufnr, function(client) local name = api.nvim_buf_get_name(bufnr) - local old_name = changetracking._get_and_set_name(client, bufnr, name) - if old_name and name ~= old_name then + local old_uri = changetracking._get_and_set_uri(client, bufnr, uri) + if old_uri and name ~= old_uri then + client.notify('textDocument/didClose', { + textDocument = { + uri = old_uri, + }, + }) client.notify('textDocument/didOpen', { textDocument = { version = 0, @@ -1664,8 +1690,12 @@ function lsp.buf_attach_client(bufnr, client_id) end) end, on_detach = function() - local params = { textDocument = { uri = uri } } for_each_buffer_client(bufnr, function(client, _) + local params = { + textDocument = { + uri = changetracking._get_uri(client, bufnr), + }, + } changetracking.reset_buf(client, bufnr) if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4beb4fc367..554e26022c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2032,7 +2032,12 @@ end ---@returns `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) - return { uri = vim.uri_from_bufnr(bufnr or 0) } + bufnr = bufnr or 0 + local uri = vim.uri_from_bufnr(bufnr) + if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then + uri = uri:gsub('^file://', 'buffer://') + end + return { uri = uri } end --- Create the workspace params @@ -2065,7 +2070,7 @@ function M.make_formatting_params(options) insertSpaces = vim.bo.expandtab, }) return { - textDocument = { uri = vim.uri_from_bufnr(0) }, + textDocument = M.make_text_document_params(0), options = options, } end -- cgit From 014981c9006f9b96b8045e609dc27f4a84da5263 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Wed, 1 Mar 2023 09:47:56 -0700 Subject: fix(lsp): only fire LspDetach for attached buffers (#22468) If the LSP server fails to start then the client never initializes and thus never calls its on_attach function and an LspAttach event is never fired. However, the on_exit function still fires a LspDetach event, so user autocommands that attempt to "clean up" in LspDetach may run into problems if they assume that the buffer was already attached. The solution is to only fire an LspDetach event if the buffer was already attached in the first place. --- runtime/lua/vim/lsp.lua | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a6d550f48f..a56e141c29 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1155,14 +1155,19 @@ function lsp.start_client(config) pcall(config.on_exit, code, signal, client_id) end + local client = active_clients[client_id] and 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() - nvim_exec_autocmds('LspDetach', { - buffer = bufnr, - modeline = false, - data = { client_id = client_id }, - }) + if client and client.attached_buffers[bufnr] then + 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) @@ -1178,8 +1183,6 @@ function lsp.start_client(config) -- Schedule the deletion of the client object so that it exists in the execution of LspDetach -- autocommands vim.schedule(function() - local client = active_clients[client_id] and active_clients[client_id] - or uninitialized_clients[client_id] active_clients[client_id] = nil uninitialized_clients[client_id] = nil -- cgit From bc15b075d14c85098d674a2466d2386e08b0005f Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 1 Mar 2023 10:51:22 -0600 Subject: feat(vim.fs): pass path to find() predicate, lazy evaluate #22378 Problem: No easy way to find files under certain directories (ex: grab all files under `test/`) or exclude the content of certain paths (ex. `build/`, `.git/`) Solution: Pass the full `path` as an arg to the predicate. --- runtime/lua/vim/fs.lua | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index a0d2c4c339..2c3fc64d57 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -144,11 +144,34 @@ end --- The search can be narrowed to find only files or only directories by --- specifying {type} to be "file" or "directory", respectively. --- ----@param names (string|table|fun(name: string): boolean) Names of the files +--- Examples: +---
lua
+--- -- location of Cargo.toml from the current buffer's path
+--- local cargo = vim.fs.find('Cargo.toml', {
+---   upward = true,
+---   stop = vim.loop.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'},
+---   {limit = math.huge, type = 'directory', path = './runtime/'}
+--- )
+---
+--- -- get all files ending with .cpp or .hpp inside lib/
+--- local cpp_hpp = vim.fs.find(function(name, path)
+---   return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$')
+--- end, {limit = math.huge, type = 'file'})
+--- 
+--- +---@param names (string|table|fun(name: string, path: string): boolean) Names of the files --- and directories to find. ---- Must be base names, paths and globs are not supported. ---- The function is called per file and directory within the ---- traversed directories to test if they match {names}. +--- Must be base names, paths and globs are not supported when {names} is a string or a table. +--- If {names} is a function, it is called for each traversed file and directory with args: +--- - name: base name of the current item +--- - path: full path of the current item +--- The function should return `true` if the given file or directory is considered a match. --- ---@param opts (table) Optional keyword arguments: --- - path (string): Path to begin searching from. If @@ -201,7 +224,7 @@ function M.find(names, opts) test = function(p) local t = {} for name, type in M.dir(p) do - if names(name) and (not opts.type or opts.type == type) then + if (not opts.type or opts.type == type) and names(name, p) then table.insert(t, join_paths(p, name)) end end @@ -250,7 +273,7 @@ function M.find(names, opts) for other, type_ in M.dir(dir) do local f = join_paths(dir, other) if type(names) == 'function' then - if names(other) and (not opts.type or opts.type == type_) then + if (not opts.type or opts.type == type_) and names(other, dir) then if add(f) then return matches end -- cgit From b0b4c310973c2b419350e3c05c772596b1c334e9 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 2 Mar 2023 10:01:42 -0500 Subject: refactor: rename show_tree => inspect_tree #22474 Problem: "show" is potentially a new verb that we can avoid (there is already "open" and "echo"). Even if we can't avoid it, the behavior of `show_tree` fits well in the "inspect" family of functions: a way for users to introspect/reflect on the state of Nvim. Existing "inspect" functions: vim.inspect() vim.inspect_pos() vim.treesitter.inspect_language() nvim__inspect_cell Solution: Rename `show_tree` to `inspect_tree`. --- runtime/lua/vim/treesitter.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index fead7b7b1b..412140b757 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -436,7 +436,7 @@ end --- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a --- function, it accepts the buffer number of the source buffer as its only --- argument and should return a string. -function M.show_tree(opts) +function M.inspect_tree(opts) vim.validate({ opts = { opts, 't', true }, }) @@ -622,6 +622,12 @@ function M.show_tree(opts) }) end +---@deprecated +---@private +function M.show_tree() + vim.deprecate('show_tree', 'inspect_tree', '0.9', nil, false) +end + --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': ---
lua
 --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
-- 
cgit 


From f449121764c19cebda7b8b2c970b76bc8121bae7 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Thu, 2 Mar 2023 18:03:11 +0100
Subject: feat(treesitter): add :InspectTree command (#22477)

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

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 412140b757..09e3814a17 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -424,6 +424,8 @@ end
 --- display of the source language of each node, and press  to jump to the node under the
 --- cursor in the source buffer.
 ---
+--- Can also be shown with `:InspectTree`. *:InspectTree*
+---
 ---@param opts table|nil Optional options table with the following possible keys:
 ---                      - lang (string|nil): The language of the source buffer. If omitted, the
 ---                        filetype of the source buffer is used.
-- 
cgit 


From 2eeafc43c43697d18d5634f8f81bf9e60a880e17 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Thu, 2 Mar 2023 14:06:11 -0700
Subject: fix(treesitter): maintain cursor position when toggling anonymous
 nodes

When toggling anonymous nodes in the :InspectTree window, keep the
cursor fixed relative to the node within the tree. This prevents the
cursor from jumping.
---
 runtime/lua/vim/treesitter.lua | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 09e3814a17..b2909f2154 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -506,8 +506,26 @@ function M.inspect_tree(opts)
   a.nvim_buf_set_keymap(b, 'n', 'a', '', {
     desc = 'Toggle anonymous nodes',
     callback = function()
+      local row, col = unpack(a.nvim_win_get_cursor(w))
+      local curnode = pg:get(row)
+      while curnode and not curnode.named do
+        row = row - 1
+        curnode = pg:get(row)
+      end
+
       pg.opts.anon = not pg.opts.anon
       pg:draw(b)
+
+      if not curnode then
+        return
+      end
+
+      local id = curnode.id
+      for i, node in pg:iter() do
+        if node.id == id then
+          a.nvim_win_set_cursor(w, { i, col })
+        end
+      end
     end,
   })
   a.nvim_buf_set_keymap(b, 'n', 'I', '', {
-- 
cgit 


From 86ff239240e955ef6da95bc9c8814cfd4492f5aa Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Thu, 2 Mar 2023 14:15:18 -0700
Subject: refactor(treesitter): use string.format to create lines

---
 runtime/lua/vim/treesitter/playground.lua | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
index fd5b687195..992433961f 100644
--- a/runtime/lua/vim/treesitter/playground.lua
+++ b/runtime/lua/vim/treesitter/playground.lua
@@ -146,9 +146,9 @@ local decor_ns = api.nvim_create_namespace('ts.playground')
 ---@return string
 local function get_range_str(lnum, col, end_col, end_lnum)
   if lnum == end_lnum then
-    return string.format('[%d:%d-%d]', lnum + 1, col + 1, end_col)
+    return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col)
   end
-  return string.format('[%d:%d-%d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
+  return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
 end
 
 --- Write the contents of this Playground into {bufnr}.
@@ -163,7 +163,8 @@ function TSPlayground:draw(bufnr)
   for _, item in self:iter() do
     local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col)
     local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
-    local line = string.rep(' ', item.depth) .. item.text .. '; ' .. range_str .. lang_str
+    local line =
+      string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str)
 
     if self.opts.lang then
       lang_hl_marks[#lang_hl_marks + 1] = {
-- 
cgit 


From fdb6b4d2e729211f0526ce75c6a3fcc636859bfd Mon Sep 17 00:00:00 2001
From: Amaan Qureshi 
Date: Thu, 2 Mar 2023 17:29:03 -0500
Subject: vim-patch:9.0.1368: Bass files are not recognized (#22485)

Problem:    Bass files are not recognized.
Solution:   Add patterns for Bass files. (Amaan Qureshi, closes vim/vim#12088)

https://github.com/vim/vim/commit/4ed914b18a47192f79f342bea5e8f59e120d5260
---
 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 256c053801..8911eafc71 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -156,6 +156,7 @@ local extension = {
   bas = function(path, bufnr)
     return require('vim.filetype.detect').bas(bufnr)
   end,
+  bass = 'bass',
   bi = function(path, bufnr)
     return require('vim.filetype.detect').bas(bufnr)
   end,
-- 
cgit 


From 6d4f48182131c36d57589eefd4cefe3c70256d04 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 3 Mar 2023 09:44:02 +0000
Subject: fix(treesitter): disallow empty filetypes

Fixes #22473
---
 runtime/lua/vim/treesitter.lua          |  3 ++-
 runtime/lua/vim/treesitter/language.lua | 10 ++++++++++
 2 files changed, 12 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 09e3814a17..f80da433b1 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -47,7 +47,8 @@ function M._create_parser(bufnr, lang, opts)
 
   vim.fn.bufload(bufnr)
 
-  language.add(lang, { filetype = vim.bo[bufnr].filetype })
+  local ft = vim.bo[bufnr].filetype
+  language.add(lang, { filetype = ft ~= '' and ft or nil })
 
   local self = LanguageTree.new(bufnr, lang, opts)
 
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 5bcc786e88..5f34d9cd56 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -60,6 +60,16 @@ function M.add(lang, opts)
     filetype = { filetype, { 'string', 'table' }, true },
   })
 
+  if filetype == '' then
+    error(string.format("'%s' is not a valid filetype", filetype))
+  elseif type(filetype) == 'table' then
+    for _, f in ipairs(filetype) do
+      if f == '' then
+        error(string.format("'%s' is not a valid filetype", filetype))
+      end
+    end
+  end
+
   M.register(lang, filetype or lang)
 
   if vim._ts_has_language(lang) then
-- 
cgit 


From f0a2ffab2923202f4454860ba1a7c7bd0e035ed2 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Fri, 3 Mar 2023 20:05:59 +0900
Subject: fix(treesitter): typos in _range.lua

fix(treesitter): typos _range.lua
---
 runtime/lua/vim/treesitter/_range.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua
index b87542c20f..8decd3a1fd 100644
--- a/runtime/lua/vim/treesitter/_range.lua
+++ b/runtime/lua/vim/treesitter/_range.lua
@@ -61,7 +61,7 @@ function M.intercepts(r1, r2)
   local off_1 = #r1 == 6 and 1 or 0
   local off_2 = #r1 == 6 and 1 or 0
 
-  local srow_1, scol_1, erow_1, ecol_1 = r1[1], r2[2], r1[3 + off_1], r1[4 + off_1]
+  local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1]
   local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2]
 
   -- r1 is above r2
@@ -85,7 +85,7 @@ function M.contains(r1, r2)
   local off_1 = #r1 == 6 and 1 or 0
   local off_2 = #r1 == 6 and 1 or 0
 
-  local srow_1, scol_1, erow_1, ecol_1 = r1[1], r2[2], r1[3 + off_1], r1[4 + off_1]
+  local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1]
   local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2]
 
   -- start doesn't fit
-- 
cgit 


From 8414cfe7f4d8888698343cb54a3f373a28b365db Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Thu, 2 Mar 2023 20:46:59 +0100
Subject: docs: fix vim.treesitter tags

Problem:
Help tags like vim.treesitter.language.add() are confusing because
`vim.treesitter.language` is (thankfully) not a user-facing module.

Solution:
Ignore the "fstem" when generating "treesitter" tags.
---
 runtime/lua/vim/treesitter/highlighter.lua  |  4 +++-
 runtime/lua/vim/treesitter/languagetree.lua | 20 +++++++++-----------
 runtime/lua/vim/treesitter/query.lua        |  5 ++---
 3 files changed, 14 insertions(+), 15 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 8adaa4ef2f..e3deaf6ba6 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -58,7 +58,9 @@ function TSHighlighterQuery:query()
   return self._query
 end
 
---- Creates a new highlighter using @param tree
+---@private
+---
+--- Creates a highlighter for `tree`.
 ---
 ---@param tree LanguageTree parser object to use for highlighting
 ---@param opts (table|nil) Configuration of the highlighter:
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 43fb866896..1bc7971eba 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -38,18 +38,16 @@ local LanguageTree = {}
 
 LanguageTree.__index = LanguageTree
 
---- A |LanguageTree| holds the treesitter parser for a given language {lang} used
---- to parse a buffer. As the buffer may contain injected languages, the LanguageTree
---- needs to store parsers for these child languages as well (which in turn may contain
---- child languages themselves, hence the name).
+--- @private
 ---
----@param source (integer|string) Buffer or a string of text to parse
----@param lang string Root language this tree represents
----@param opts (table|nil) Optional keyword arguments:
----             - injections table Mapping language to injection query strings.
----                                This is useful for overriding the built-in
----                                runtime file searching for the injection language
----                                query per language.
+--- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any
+--- "injected" language parsers, which themselves may inject other languages, recursively.
+---
+---@param source (integer|string) Buffer or text string to parse
+---@param lang string Root language of this tree
+---@param opts (table|nil) Optional arguments:
+---             - injections table Map of language to injection query strings. Overrides the
+---                                built-in runtime file searching for language injections.
 ---@return LanguageTree parser object
 function LanguageTree.new(source, lang, opts)
   language.add(lang)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 13d98a0625..4e9871b59d 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -273,8 +273,7 @@ end
 ---@param opts (table|nil) Optional parameters.
 ---          - concat: (boolean) Concatenate result in a string (default true)
 ---          - metadata (table) Metadata of a specific capture. This would be
----            set to `metadata[capture_id]` when using
----            |vim.treesitter.query.add_directive()|.
+---            set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|.
 ---@return (string[]|string|nil)
 function M.get_node_text(node, source, opts)
   opts = opts or {}
@@ -486,7 +485,7 @@ local directive_handlers = {
 ---
 ---@param name string Name of the predicate, without leading #
 ---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[])
----   - see |vim.treesitter.query.add_directive()| for argument meanings
+---   - see |vim.treesitter.add_directive()| for argument meanings
 ---@param force boolean|nil
 function M.add_predicate(name, handler, force)
   if predicate_handlers[name] and not force then
-- 
cgit 


From bf90ceb5488ea2b41eb53335984f017785aefea9 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Fri, 3 Mar 2023 07:52:57 -0700
Subject: fix(treesitter): break early from loop when match is found (#22499)

Fixup to #22484.
---
 runtime/lua/vim/treesitter.lua | 1 +
 1 file changed, 1 insertion(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index b63247aa2c..ee66ba9f9b 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -525,6 +525,7 @@ function M.inspect_tree(opts)
       for i, node in pg:iter() do
         if node.id == id then
           a.nvim_win_set_cursor(w, { i, col })
+          break
         end
       end
     end,
-- 
cgit 


From 128b82103ba477482bdfaf10ec71e0986876cca1 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:04:05 +0900
Subject: docs(treesitter): number → integer (#22513)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/treesitter/playground.lua | 22 +++++++++++-----------
 runtime/lua/vim/treesitter/query.lua      | 12 ++++++------
 2 files changed, 17 insertions(+), 17 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
index 992433961f..7f181c23fd 100644
--- a/runtime/lua/vim/treesitter/playground.lua
+++ b/runtime/lua/vim/treesitter/playground.lua
@@ -1,7 +1,7 @@
 local api = vim.api
 
 ---@class TSPlayground
----@field ns number API namespace
+---@field ns integer API namespace
 ---@field opts table Options table with the following keys:
 ---                  - anon (boolean): If true, display anonymous nodes
 ---                  - lang (boolean): If true, display the language alongside each node
@@ -10,14 +10,14 @@ local api = vim.api
 local TSPlayground = {}
 ---
 ---@class Node
----@field id number Node id
+---@field id integer Node id
 ---@field text string Node text
 ---@field named boolean True if this is a named (non-anonymous) node
----@field depth number Depth of the node within the tree
----@field lnum number Beginning line number of this node in the source buffer
----@field col number Beginning column number of this node in the source buffer
----@field end_lnum number Final line number of this node in the source buffer
----@field end_col number Final column number of this node in the source buffer
+---@field depth integer Depth of the node within the tree
+---@field lnum integer Beginning line number of this node in the source buffer
+---@field col integer Beginning column number of this node in the source buffer
+---@field end_lnum integer Final line number of this node in the source buffer
+---@field end_col integer Final column number of this node in the source buffer
 ---@field lang string Source language of this node
 ---@field root TSNode
 
@@ -34,7 +34,7 @@ local TSPlayground = {}
 --- table maps nodes in the primary tree to root nodes of injected trees.
 ---
 ---@param node TSNode Starting node to begin traversal |tsnode|
----@param depth number Current recursion depth
+---@param depth integer Current recursion depth
 ---@param lang string Language of the tree currently being traversed
 ---@param injections table Mapping of node ids to root nodes of injected language trees (see
 ---                        explanation above)
@@ -153,7 +153,7 @@ end
 
 --- Write the contents of this Playground into {bufnr}.
 ---
----@param bufnr number Buffer number to write into.
+---@param bufnr integer Buffer number to write into.
 ---@private
 function TSPlayground:draw(bufnr)
   vim.bo[bufnr].modifiable = true
@@ -194,7 +194,7 @@ end
 ---
 --- The node number is dependent on whether or not anonymous nodes are displayed.
 ---
----@param i number Node number to get
+---@param i integer Node number to get
 ---@return Node
 ---@private
 function TSPlayground:get(i)
@@ -206,7 +206,7 @@ end
 ---
 ---@return (fun(): integer, Node) Iterator over all nodes in this Playground
 ---@return table
----@return number
+---@return integer
 ---@private
 function TSPlayground:iter()
   return ipairs(self.opts.anon and self.nodes or self.named)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 4e9871b59d..22f706585e 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -56,7 +56,7 @@ local function add_included_lang(base_langs, lang, ilang)
 end
 
 ---@private
----@param buf (number)
+---@param buf (integer)
 ---@param range (table)
 ---@param concat (boolean)
 ---@returns (string[]|string|nil)
@@ -269,7 +269,7 @@ end
 --- Gets the text corresponding to a given node
 ---
 ---@param node TSNode
----@param source (number|string) Buffer or string from which the {node} is extracted
+---@param source (integer|string) Buffer or string from which the {node} is extracted
 ---@param opts (table|nil) Optional parameters.
 ---          - concat: (boolean) Concatenate result in a string (default true)
 ---          - metadata (table) Metadata of a specific capture. This would be
@@ -484,7 +484,7 @@ local directive_handlers = {
 --- Adds a new predicate to be used in queries
 ---
 ---@param name string Name of the predicate, without leading #
----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[])
+---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[])
 ---   - see |vim.treesitter.add_directive()| for argument meanings
 ---@param force boolean|nil
 function M.add_predicate(name, handler, force)
@@ -503,7 +503,7 @@ end
 --- metadata table `metadata[capture_id].key = value`
 ---
 ---@param name string Name of the directive, without leading #
----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table)
+---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[], metadata:table)
 ---   - match: see |treesitter-query|
 ---      - node-level data are accessible via `match[capture_id]`
 ---   - pattern: see |treesitter-query|
@@ -644,8 +644,8 @@ end
 ---
 ---@param node TSNode under which the search will occur
 ---@param source (integer|string) Source buffer or string to extract text from
----@param start number Starting line for the search
----@param stop number Stopping line for the search (end-exclusive)
+---@param start integer Starting line for the search
+---@param stop integer Stopping line for the search (end-exclusive)
 ---
 ---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata
 function Query:iter_captures(node, source, start, stop)
-- 
cgit 


From 6a20c29dcd050fee011ec1c451b5e70940152885 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:05:01 +0900
Subject: docs(filetype): number → integer (#22516)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/filetype.lua | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 8911eafc71..65579d3543 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -28,9 +28,9 @@ end
 --- If only start_lnum is specified, return a single line as a string.
 --- If both start_lnum and end_lnum are omitted, return all lines from the buffer.
 ---
----@param bufnr number|nil The buffer to get the lines from
----@param start_lnum number|nil The line number of the first line (inclusive, 1-based)
----@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
+---@param bufnr integer|nil The buffer to get the lines from
+---@param start_lnum integer|nil The line number of the first line (inclusive, 1-based)
+---@param end_lnum integer|nil The line number of the last line (inclusive, 1-based)
 ---@return table|string Array of lines, or string when end_lnum is omitted
 function M.getlines(bufnr, start_lnum, end_lnum)
   if end_lnum then
@@ -67,8 +67,8 @@ end
 ---@private
 --- Get the next non-whitespace line in the buffer.
 ---
----@param bufnr number The buffer to get the line from
----@param start_lnum number The line number of the first line to start from (inclusive, 1-based)
+---@param bufnr integer The buffer to get the line from
+---@param start_lnum integer The line number of the first line to start from (inclusive, 1-based)
 ---@return string|nil The first non-blank line if found or `nil` otherwise
 function M.nextnonblank(bufnr, start_lnum)
   for _, line in ipairs(M.getlines(bufnr, start_lnum, -1)) do
-- 
cgit 


From ccd2cc1abd69f677edabd0b66dfd9689eeee54b9 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:05:16 +0900
Subject: docs(uri): number → integer (#22515)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index d6b0b7410e..38759fcdc0 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -80,7 +80,7 @@ local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
 local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
 
 --- Get a URI from a bufnr
----@param bufnr number
+---@param bufnr integer
 ---@return string URI
 local function uri_from_bufnr(bufnr)
   local fname = vim.api.nvim_buf_get_name(bufnr)
@@ -123,7 +123,7 @@ end
 --- Creates a new unloaded buffer if no buffer for the uri already exists.
 --
 ---@param uri string
----@return number bufnr
+---@return integer bufnr
 local function uri_to_bufnr(uri)
   return vim.fn.bufadd(uri_to_fname(uri))
 end
-- 
cgit 


From 1f07307aeb6564fb794921cc1b2879f84a822921 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:05:46 +0900
Subject: docs(inspect): number → integer (#22511)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/_inspector.lua | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index bb6608421b..9e91597192 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -14,15 +14,15 @@ local defaults = {
 ---
 ---Can also be pretty-printed with `:Inspect!`. *:Inspect!*
 ---
----@param bufnr? number defaults to the current buffer
----@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
----@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
+---@param bufnr? integer defaults to the current buffer
+---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
+---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
 ---@param filter? InspectorFilter (table|nil) a table with key-value pairs to filter the items
 ---               - syntax (boolean): include syntax based highlight groups (defaults to true)
 ---               - treesitter (boolean): include treesitter based highlight groups (defaults to true)
 ---               - extmarks (boolean|"all"): include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
 ---               - semantic_tokens (boolean): include semantic tokens (defaults to true)
----@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:number,col:number,row:number} (table) a table with the following key-value pairs. Items are in "traversal order":
+---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:integer,col:integer,row:integer} (table) a table with the following key-value pairs. Items are in "traversal order":
 ---               - treesitter: a list of treesitter captures
 ---               - syntax: a list of syntax groups
 ---               - semantic_tokens: a list of semantic tokens
@@ -129,9 +129,9 @@ end
 ---
 ---Can also be shown with `:Inspect`. *:Inspect*
 ---
----@param bufnr? number defaults to the current buffer
----@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
----@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
+---@param bufnr? integer defaults to the current buffer
+---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
+---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
 ---@param filter? InspectorFilter (table|nil) see |vim.inspect_pos()|
 function vim.show_pos(bufnr, row, col, filter)
   local items = vim.inspect_pos(bufnr, row, col, filter)
-- 
cgit 


From 82b77900d7f85088c676be886937628230d0d5c2 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:06:20 +0900
Subject: docs(diagnostic): number → integer (#22512)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/diagnostic.lua | 46 +++++++++++++++++++++---------------------
 1 file changed, 23 insertions(+), 23 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 6fd000a029..56532d0184 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -630,7 +630,7 @@ end
 ---                         Options:
 ---                         * reverse: (boolean) Reverse sort order
 ---
----@param namespace number|nil Update the options for the given namespace. When omitted, update the
+---@param namespace integer|nil Update the options for the given namespace. When omitted, update the
 ---                            global diagnostic options.
 function M.config(opts, namespace)
   vim.validate({
@@ -674,8 +674,8 @@ end
 
 --- Set diagnostics for the given namespace and buffer.
 ---
----@param namespace number The diagnostic namespace
----@param bufnr number Buffer number
+---@param namespace integer The diagnostic namespace
+---@param bufnr integer Buffer number
 ---@param diagnostics table A list of diagnostic items |diagnostic-structure|
 ---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
 function M.set(namespace, bufnr, diagnostics, opts)
@@ -711,7 +711,7 @@ end
 
 --- Get namespace metadata.
 ---
----@param namespace number Diagnostic namespace
+---@param namespace integer Diagnostic namespace
 ---@return table Namespace metadata
 function M.get_namespace(namespace)
   vim.validate({ namespace = { namespace, 'n' } })
@@ -743,11 +743,11 @@ function M.get_namespaces()
 end
 
 ---@class Diagnostic
----@field buffer number
----@field lnum number 0-indexed
----@field end_lnum nil|number 0-indexed
----@field col number 0-indexed
----@field end_col nil|number 0-indexed
+---@field buffer integer
+---@field lnum integer 0-indexed
+---@field end_lnum nil|integer 0-indexed
+---@field col integer 0-indexed
+---@field end_col nil|integer 0-indexed
 ---@field severity DiagnosticSeverity
 ---@field message string
 ---@field source nil|string
@@ -756,7 +756,7 @@ end
 
 --- Get current diagnostics.
 ---
----@param bufnr number|nil Buffer number to get diagnostics from. Use 0 for
+---@param bufnr integer|nil Buffer number to get diagnostics from. Use 0 for
 ---                        current buffer or nil for all buffers.
 ---@param opts table|nil A table with the following keys:
 ---                        - namespace: (number) Limit diagnostics to the given namespace.
@@ -1083,9 +1083,9 @@ end
 --- To hide diagnostics and prevent them from re-displaying, use
 --- |vim.diagnostic.disable()|.
 ---
----@param namespace number|nil Diagnostic namespace. When omitted, hide
+---@param namespace integer|nil Diagnostic namespace. When omitted, hide
 ---                            diagnostics from all namespaces.
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
 ---                        omitted, hide diagnostics in all buffers.
 function M.hide(namespace, bufnr)
   vim.validate({
@@ -1108,8 +1108,8 @@ end
 
 --- Check whether diagnostics are disabled in a given buffer.
 ---
----@param bufnr number|nil Buffer number, or 0 for current buffer.
----@param namespace number|nil Diagnostic namespace. When omitted, checks if
+---@param bufnr integer|nil Buffer number, or 0 for current buffer.
+---@param namespace integer|nil Diagnostic namespace. When omitted, checks if
 ---                            all diagnostics are disabled in {bufnr}.
 ---                            Otherwise, only checks if diagnostics from
 ---                            {namespace} are disabled.
@@ -1129,9 +1129,9 @@ end
 
 --- Display diagnostics for the given namespace and buffer.
 ---
----@param namespace number|nil Diagnostic namespace. When omitted, show
+---@param namespace integer|nil Diagnostic namespace. When omitted, show
 ---                            diagnostics from all namespaces.
----@param bufnr number|nil Buffer number, or 0 for current buffer. When omitted, show
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When omitted, show
 ---                        diagnostics in all buffers.
 ---@param diagnostics table|nil The diagnostics to display. When omitted, use the
 ---                             saved diagnostics for the given namespace and
@@ -1256,7 +1256,7 @@ end
 ---                      Overrides the setting from |vim.diagnostic.config()|.
 ---            - suffix: Same as {prefix}, but appends the text to the diagnostic instead of
 ---                      prepending it. Overrides the setting from |vim.diagnostic.config()|.
----@return number|nil, number|nil: ({float_bufnr}, {win_id})
+---@return integer|nil, integer|nil: ({float_bufnr}, {win_id})
 function M.open_float(opts, ...)
   -- Support old (bufnr, opts) signature
   local bufnr
@@ -1463,9 +1463,9 @@ end
 --- simply remove diagnostic decorations in a way that they can be
 --- re-displayed, use |vim.diagnostic.hide()|.
 ---
----@param namespace number|nil Diagnostic namespace. When omitted, remove
+---@param namespace integer|nil Diagnostic namespace. When omitted, remove
 ---                            diagnostics from all namespaces.
----@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
+---@param bufnr integer|nil Remove diagnostics for the given buffer. When omitted,
 ---             diagnostics are removed for all buffers.
 function M.reset(namespace, bufnr)
   vim.validate({
@@ -1518,9 +1518,9 @@ end
 
 --- Disable diagnostics in the given buffer.
 ---
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
 ---                        omitted, disable diagnostics in all buffers.
----@param namespace number|nil Only disable diagnostics for the given namespace.
+---@param namespace integer|nil Only disable diagnostics for the given namespace.
 function M.disable(bufnr, namespace)
   vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
   if bufnr == nil then
@@ -1555,9 +1555,9 @@ end
 
 --- Enable diagnostics in the given buffer.
 ---
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
 ---                        omitted, enable diagnostics in all buffers.
----@param namespace number|nil Only enable diagnostics for the given namespace.
+---@param namespace integer|nil Only enable diagnostics for the given namespace.
 function M.enable(bufnr, namespace)
   vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
   if bufnr == nil then
-- 
cgit 


From aa16590999a66798eca7d2ba09e971aacdeb54b4 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 4 Mar 2023 22:07:39 +0900
Subject: docs(lua): number → integer (#22517)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/_editor.lua | 10 +++++-----
 runtime/lua/vim/shared.lua  | 10 +++++-----
 2 files changed, 10 insertions(+), 10 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 3f27e61810..ab49e26dde 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -386,7 +386,7 @@ end
 
 --- Get a table of lines with start, end columns for a region marked by two points
 ---
----@param bufnr number of buffer
+---@param bufnr integer number of buffer
 ---@param pos1 integer[] (line, column) tuple marking beginning of region
 ---@param pos2 integer[] (line, column) tuple marking end of region
 ---@param regtype string type of selection, see |setreg()|
@@ -471,7 +471,7 @@ end
 --- writes to |:messages|.
 ---
 ---@param msg string Content of the notification to show to the user.
----@param level number|nil One of the values from |vim.log.levels|.
+---@param level integer|nil One of the values from |vim.log.levels|.
 ---@param opts table|nil Optional parameters. Unused by default.
 function vim.notify(msg, level, opts) -- luacheck: no unused args
   if level == vim.log.levels.ERROR then
@@ -492,7 +492,7 @@ do
   --- display a notification.
   ---
   ---@param msg string Content of the notification to show to the user.
-  ---@param level number|nil One of the values from |vim.log.levels|.
+  ---@param level integer|nil One of the values from |vim.log.levels|.
   ---@param opts table|nil Optional parameters. Unused by default.
   ---@return boolean true if message was displayed, else false
   function vim.notify_once(msg, level, opts)
@@ -521,10 +521,10 @@ local on_key_cbs = {}
 ---@param fn function: Callback function. It should take one string argument.
 ---                   On each key press, Nvim passes the key char to fn(). |i_CTRL-V|
 ---                   If {fn} is nil, it removes the callback for the associated {ns_id}
----@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
+---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a new
 ---                    |nvim_create_namespace()| id.
 ---
----@return number Namespace id associated with {fn}. Or count of all callbacks
+---@return integer Namespace id associated with {fn}. Or count of all callbacks
 ---if on_key() is called without arguments.
 ---
 ---@note {fn} will be removed if an error occurs while calling.
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index cc48e3f193..1c8defc93a 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -418,8 +418,8 @@ end
 ---@generic T: table
 ---@param dst T List which will be modified and appended to
 ---@param src table List from which values will be inserted
----@param start (number|nil) Start index on src. Defaults to 1
----@param finish (number|nil) Final index on src. Defaults to `#src`
+---@param start (integer|nil) Start index on src. Defaults to 1
+---@param finish (integer|nil) Final index on src. Defaults to `#src`
 ---@return T dst
 function vim.list_extend(dst, src, start, finish)
   vim.validate({
@@ -529,7 +529,7 @@ end
 ---
 ---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
 ---@param t table Table
----@return number Number of non-nil values in table
+---@return integer Number of non-nil values in table
 function vim.tbl_count(t)
   vim.validate({ t = { t, 't' } })
 
@@ -544,8 +544,8 @@ end
 ---
 ---@generic T
 ---@param list T[] (list) Table
----@param start number|nil Start range of slice
----@param finish number|nil End range of slice
+---@param start integer|nil Start range of slice
+---@param finish integer|nil End range of slice
 ---@return T[] (list) Copy of table sliced from start to finish (inclusive)
 function vim.list_slice(list, start, finish)
   local new_list = {}
-- 
cgit 


From 59542504b41dfddb6f26524fb4ce811e0f22a785 Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sun, 5 Mar 2023 01:47:30 +0900
Subject: docs(highlight): fix type annotations (#22272)

---
 runtime/lua/vim/highlight.lua | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 20ad48dd27..89eae0def5 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -36,12 +36,12 @@ end
 
 --- Highlight range between two positions
 ---
----@param bufnr number of buffer to apply highlighting to
----@param ns namespace to add highlight to
----@param higroup highlight group to use for highlighting
----@param start first position (tuple {line,col})
----@param finish second position (tuple {line,col})
----@param opts table with options:
+---@param bufnr integer Buffer number to apply highlighting to
+---@param ns integer Namespace to add highlight to
+---@param higroup string Highlight group to use for highlighting
+---@param start { [1]: integer, [2]: integer } Start position {line, col}
+---@param finish { [1]: integer, [2]: integer } Finish position {line, col}
+---@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)
@@ -84,7 +84,7 @@ local yank_timer
 --- customize conditions (here: do not highlight a visual selection) via
 ---   au TextYankPost * lua vim.highlight.on_yank {on_visual=false}
 ---
--- @param opts table with options controlling the highlight:
+-- @param opts table|nil Optional parameters
 --              - higroup   highlight group for yanked region (default "IncSearch")
 --              - timeout   time in ms before highlight is cleared (default 150)
 --              - on_macro  highlight when executing macro (default false)
-- 
cgit 


From ac69ba5fa0081026f2c5e6e29d5788802479b7b9 Mon Sep 17 00:00:00 2001
From: Jon Huhn 
Date: Sun, 5 Mar 2023 00:52:27 -0600
Subject: feat(lsp): implement workspace/didChangeWatchedFiles (#22405)

---
 runtime/lua/vim/_editor.lua         |   1 +
 runtime/lua/vim/_watch.lua          | 174 +++++++++++++++++++++
 runtime/lua/vim/lsp/_watchfiles.lua | 293 ++++++++++++++++++++++++++++++++++++
 runtime/lua/vim/lsp/handlers.lua    |  38 +++--
 runtime/lua/vim/lsp/protocol.lua    |   4 +
 5 files changed, 501 insertions(+), 9 deletions(-)
 create mode 100644 runtime/lua/vim/_watch.lua
 create mode 100644 runtime/lua/vim/lsp/_watchfiles.lua

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index ab49e26dde..c205451ff9 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -37,6 +37,7 @@ for k, v in pairs({
   health = true,
   fs = true,
   secure = true,
+  _watch = true,
 }) do
   vim._submodules[k] = v
 end
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
new file mode 100644
index 0000000000..dba1522ec8
--- /dev/null
+++ b/runtime/lua/vim/_watch.lua
@@ -0,0 +1,174 @@
+local M = {}
+
+--- Enumeration describing the types of events watchers will emit.
+M.FileChangeType = vim.tbl_add_reverse_lookup({
+  Created = 1,
+  Changed = 2,
+  Deleted = 3,
+})
+
+---@private
+--- Joins filepath elements by static '/' separator
+---
+---@param ... (string) The path elements.
+local function filepath_join(...)
+  return table.concat({ ... }, '/')
+end
+
+---@private
+--- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle
+---
+---@param handle (uv_fs_event_t|uv_fs_poll_t) The handle to stop
+local function stop(handle)
+  local _, stop_err = handle:stop()
+  assert(not stop_err, stop_err)
+  local is_closing, close_err = handle:is_closing()
+  assert(not close_err, close_err)
+  if not is_closing then
+    handle:close()
+  end
+end
+
+--- Initializes and starts a |uv_fs_event_t|
+---
+---@param path (string) The path to watch
+---@param opts (table|nil) Additional options
+---     - uvflags (table|nil)
+---                Same flags as accepted by |uv.fs_event_start()|
+---@param callback (function) The function called when new events
+---@returns (function) A function to stop the watch
+function M.watch(path, opts, callback)
+  vim.validate({
+    path = { path, 'string', false },
+    opts = { opts, 'table', true },
+    callback = { callback, 'function', false },
+  })
+
+  path = vim.fs.normalize(path)
+  local uvflags = opts and opts.uvflags or {}
+  local handle, new_err = vim.loop.new_fs_event()
+  assert(not new_err, new_err)
+  local _, start_err = handle:start(path, uvflags, function(err, filename, events)
+    assert(not err, err)
+    local fullpath = path
+    if filename then
+      filename = filename:gsub('\\', '/')
+      fullpath = filepath_join(fullpath, filename)
+    end
+    local change_type = events.change and M.FileChangeType.Changed or 0
+    if events.rename then
+      local _, staterr, staterrname = vim.loop.fs_stat(fullpath)
+      if staterrname == 'ENOENT' then
+        change_type = M.FileChangeType.Deleted
+      else
+        assert(not staterr, staterr)
+        change_type = M.FileChangeType.Created
+      end
+    end
+    callback(fullpath, change_type)
+  end)
+  assert(not start_err, start_err)
+  return function()
+    stop(handle)
+  end
+end
+
+local default_poll_interval_ms = 2000
+
+---@private
+--- Implementation for poll, hiding internally-used parameters.
+---
+---@param watches (table|nil) A tree structure to maintain state for recursive watches.
+---     - handle (uv_fs_poll_t)
+---               The libuv handle
+---     - cancel (function)
+---               A function that cancels the handle and all children's handles
+---     - is_dir (boolean)
+---               Indicates whether the path is a directory (and the poll should
+---               be invoked recursively)
+---     - children (table|nil)
+---               A mapping of directory entry name to its recursive watches
+local function poll_internal(path, opts, callback, watches)
+  path = vim.fs.normalize(path)
+  local interval = opts and opts.interval or default_poll_interval_ms
+  watches = watches or {
+    is_dir = true,
+  }
+
+  if not watches.handle then
+    local poll, new_err = vim.loop.new_fs_poll()
+    assert(not new_err, new_err)
+    watches.handle = poll
+    local _, start_err = poll:start(
+      path,
+      interval,
+      vim.schedule_wrap(function(err)
+        if err == 'ENOENT' then
+          return
+        end
+        assert(not err, err)
+        poll_internal(path, opts, callback, watches)
+        callback(path, M.FileChangeType.Changed)
+      end)
+    )
+    assert(not start_err, start_err)
+    callback(path, M.FileChangeType.Created)
+  end
+
+  watches.cancel = function()
+    if watches.children then
+      for _, w in pairs(watches.children) do
+        w.cancel()
+      end
+    end
+    stop(watches.handle)
+  end
+
+  if watches.is_dir then
+    watches.children = watches.children or {}
+    local exists = {}
+    for name, ftype in vim.fs.dir(path) do
+      exists[name] = true
+      if not watches.children[name] then
+        watches.children[name] = {
+          is_dir = ftype == 'directory',
+        }
+        poll_internal(filepath_join(path, name), opts, callback, watches.children[name])
+      end
+    end
+
+    local newchildren = {}
+    for name, watch in pairs(watches.children) do
+      if exists[name] then
+        newchildren[name] = watch
+      else
+        watch.cancel()
+        watches.children[name] = nil
+        callback(path .. '/' .. name, M.FileChangeType.Deleted)
+      end
+    end
+    watches.children = newchildren
+  end
+
+  return watches.cancel
+end
+
+--- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the
+--- directory at path.
+---
+---@param path (string) The path to watch. Must refer to a directory.
+---@param opts (table|nil) Additional options
+---     - interval (number|nil)
+---                Polling interval in ms as passed to |uv.fs_poll_start()|. Defaults to 2000.
+---@param callback (function) The function called when new events
+---@returns (function) A function to stop the watch.
+function M.poll(path, opts, callback)
+  vim.validate({
+    path = { path, 'string', false },
+    opts = { opts, 'table', true },
+    callback = { callback, 'function', false },
+  })
+  return poll_internal(path, opts, callback, nil)
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
new file mode 100644
index 0000000000..96d7fa1d35
--- /dev/null
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -0,0 +1,293 @@
+local bit = require('bit')
+local watch = require('vim._watch')
+local protocol = require('vim.lsp.protocol')
+
+local M = {}
+
+---@private
+---Parses the raw pattern into a number of Lua-native patterns.
+---
+---@param pattern string The raw glob pattern
+---@return table A list of Lua patterns. A match with any of them matches the input glob pattern.
+local function parse(pattern)
+  local patterns = { '' }
+
+  local path_sep = '[/\\]'
+  local non_path_sep = '[^/\\]'
+
+  local function append(chunks)
+    local new_patterns = {}
+    for _, p in ipairs(patterns) do
+      for _, chunk in ipairs(chunks) do
+        table.insert(new_patterns, p .. chunk)
+      end
+    end
+    patterns = new_patterns
+  end
+
+  local function split(s, sep)
+    local segments = {}
+    local segment = ''
+    local in_braces = false
+    local in_brackets = false
+    for i = 1, #s do
+      local c = string.sub(s, i, i)
+      if c == sep and not in_braces and not in_brackets then
+        table.insert(segments, segment)
+        segment = ''
+      else
+        if c == '{' then
+          in_braces = true
+        elseif c == '}' then
+          in_braces = false
+        elseif c == '[' then
+          in_brackets = true
+        elseif c == ']' then
+          in_brackets = false
+        end
+        segment = segment .. c
+      end
+    end
+    if segment ~= '' then
+      table.insert(segments, segment)
+    end
+    return segments
+  end
+
+  local function escape(c)
+    if
+      c == '?'
+      or c == '.'
+      or c == '('
+      or c == ')'
+      or c == '%'
+      or c == '['
+      or c == ']'
+      or c == '*'
+      or c == '+'
+      or c == '-'
+    then
+      return '%' .. c
+    end
+    return c
+  end
+
+  local segments = split(pattern, '/')
+  for i, segment in ipairs(segments) do
+    local last_seg = i == #segments
+    if segment == '**' then
+      local chunks = {
+        path_sep .. '-',
+        '.-' .. path_sep,
+      }
+      if last_seg then
+        chunks = { '.-' }
+      end
+      append(chunks)
+    else
+      local in_braces = false
+      local brace_val = ''
+      local in_brackets = false
+      local bracket_val = ''
+      for j = 1, #segment do
+        local char = string.sub(segment, j, j)
+        if char ~= '}' and in_braces then
+          brace_val = brace_val .. char
+        else
+          if in_brackets and (char ~= ']' or bracket_val == '') then
+            local res
+            if char == '-' then
+              res = char
+            elseif bracket_val == '' and char == '!' then
+              res = '^'
+            elseif char == '/' then
+              res = ''
+            else
+              res = escape(char)
+            end
+            bracket_val = bracket_val .. res
+          else
+            if char == '{' then
+              in_braces = true
+            elseif char == '[' then
+              in_brackets = true
+            elseif char == '}' then
+              local choices = split(brace_val, ',')
+              local parsed_choices = {}
+              for _, choice in ipairs(choices) do
+                table.insert(parsed_choices, parse(choice))
+              end
+              append(vim.tbl_flatten(parsed_choices))
+              in_braces = false
+              brace_val = ''
+            elseif char == ']' then
+              append({ '[' .. bracket_val .. ']' })
+              in_brackets = false
+              bracket_val = ''
+            elseif char == '?' then
+              append({ non_path_sep })
+            elseif char == '*' then
+              append({ non_path_sep .. '-' })
+            else
+              append({ escape(char) })
+            end
+          end
+        end
+      end
+
+      if not last_seg and (segments[i + 1] ~= '**' or i + 1 < #segments) then
+        append({ path_sep })
+      end
+    end
+  end
+
+  return patterns
+end
+
+---@private
+--- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern
+--- Modeled after VSCode's implementation: https://github.com/microsoft/vscode/blob/0319eed971719ad48e9093daba9d65a5013ec5ab/src/vs/base/common/glob.ts#L509
+---
+---@param pattern string|table The glob pattern (raw or parsed) to match.
+---@param s string The string to match against pattern.
+---@return boolean Whether or not pattern matches s.
+function M._match(pattern, s)
+  if type(pattern) == 'string' then
+    pattern = parse(pattern)
+  end
+  -- Since Lua's built-in string pattern matching does not have an alternate
+  -- operator like '|', `parse` will construct one pattern for each possible
+  -- alternative. Any pattern that matches thus matches the glob.
+  for _, p in ipairs(pattern) do
+    if s:match('^' .. p .. '$') then
+      return true
+    end
+  end
+  return false
+end
+
+M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
+
+---@type table> client id -> registration id -> cancel function
+local cancels = vim.defaulttable()
+
+local queue_timeout_ms = 100
+---@type table client id -> libuv timer which will send queued changes at its timeout
+local queue_timers = {}
+---@type table client id -> set of queued changes to send in a single LSP notification
+local change_queues = {}
+---@type table> client id -> URI -> last type of change processed
+--- Used to prune consecutive events of the same type for the same file
+local change_cache = vim.defaulttable()
+
+local to_lsp_change_type = {
+  [watch.FileChangeType.Created] = protocol.FileChangeType.Created,
+  [watch.FileChangeType.Changed] = protocol.FileChangeType.Changed,
+  [watch.FileChangeType.Deleted] = protocol.FileChangeType.Deleted,
+}
+
+--- Registers the workspace/didChangeWatchedFiles capability dynamically.
+---
+---@param reg table LSP Registration object.
+---@param ctx table Context from the |lsp-handler|.
+function M.register(reg, ctx)
+  local client_id = ctx.client_id
+  local client = vim.lsp.get_client_by_id(client_id)
+  local watch_regs = {}
+  for _, w in ipairs(reg.registerOptions.watchers) do
+    local glob_patterns = {}
+    if type(w.globPattern) == 'string' then
+      for _, folder in ipairs(client.workspace_folders) do
+        table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern })
+      end
+    else
+      table.insert(glob_patterns, w.globPattern)
+    end
+    for _, glob_pattern in ipairs(glob_patterns) do
+      local pattern = parse(glob_pattern.pattern)
+      local base_dir = nil
+      if type(glob_pattern.baseUri) == 'string' then
+        base_dir = glob_pattern.baseUri
+      elseif type(glob_pattern.baseUri) == 'table' then
+        base_dir = glob_pattern.baseUri.uri
+      end
+      assert(base_dir, "couldn't identify root of watch")
+      base_dir = vim.uri_to_fname(base_dir)
+      local kind = w.kind
+        or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete
+
+      table.insert(watch_regs, {
+        base_dir = base_dir,
+        pattern = pattern,
+        kind = kind,
+      })
+    end
+  end
+
+  local callback = function(base_dir)
+    return function(fullpath, change_type)
+      for _, w in ipairs(watch_regs) do
+        change_type = to_lsp_change_type[change_type]
+        -- e.g. match kind with Delete bit (0b0100) to Delete change_type (3)
+        local kind_mask = bit.lshift(1, change_type - 1)
+        local change_type_match = bit.band(w.kind, kind_mask) == kind_mask
+        if base_dir == w.base_dir and M._match(w.pattern, fullpath) and change_type_match then
+          local change = {
+            uri = vim.uri_from_fname(fullpath),
+            type = change_type,
+          }
+
+          local last_type = change_cache[client_id][change.uri]
+          if last_type ~= change.type then
+            change_queues[client_id] = change_queues[client_id] or {}
+            table.insert(change_queues[client_id], change)
+            change_cache[client_id][change.uri] = change.type
+          end
+
+          if not queue_timers[client_id] then
+            queue_timers[client_id] = vim.defer_fn(function()
+              client.notify('workspace/didChangeWatchedFiles', {
+                changes = change_queues[client_id],
+              })
+              queue_timers[client_id] = nil
+              change_queues[client_id] = nil
+              change_cache[client_id] = nil
+            end, queue_timeout_ms)
+          end
+
+          break -- if an event matches multiple watchers, only send one notification
+        end
+      end
+    end
+  end
+
+  local watching = {}
+  for _, w in ipairs(watch_regs) do
+    if not watching[w.base_dir] then
+      watching[w.base_dir] = true
+      table.insert(
+        cancels[client_id][reg.id],
+        M._watchfunc(w.base_dir, { uvflags = { recursive = true } }, callback(w.base_dir))
+      )
+    end
+  end
+end
+
+--- Unregisters the workspace/didChangeWatchedFiles capability dynamically.
+---
+---@param unreg table LSP Unregistration object.
+---@param ctx table Context from the |lsp-handler|.
+function M.unregister(unreg, ctx)
+  local client_id = ctx.client_id
+  local client_cancels = cancels[client_id]
+  local reg_cancels = client_cancels[unreg.id]
+  while #reg_cancels > 0 do
+    table.remove(reg_cancels)()
+  end
+  client_cancels[unreg.id] = nil
+  if not next(cancels[client_id]) then
+    cancels[client_id] = nil
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 5096100a60..ee5b63d260 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -117,15 +117,35 @@ M['window/showMessageRequest'] = function(_, result)
 end
 
 --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
-M['client/registerCapability'] = function(_, _, ctx)
-  local client_id = ctx.client_id
-  local warning_tpl = 'The language server %s triggers a registerCapability '
-    .. 'handler despite dynamicRegistration set to false. '
-    .. 'Report upstream, this warning is harmless'
-  local client = vim.lsp.get_client_by_id(client_id)
-  local client_name = client and client.name or string.format('id=%d', client_id)
-  local warning = string.format(warning_tpl, client_name)
-  log.warn(warning)
+M['client/registerCapability'] = function(_, result, ctx)
+  local log_unsupported = false
+  for _, reg in ipairs(result.registrations) do
+    if reg.method == 'workspace/didChangeWatchedFiles' then
+      require('vim.lsp._watchfiles').register(reg, ctx)
+    else
+      log_unsupported = true
+    end
+  end
+  if log_unsupported then
+    local client_id = ctx.client_id
+    local warning_tpl = 'The language server %s triggers a registerCapability '
+      .. 'handler despite dynamicRegistration set to false. '
+      .. 'Report upstream, this warning is harmless'
+    local client = vim.lsp.get_client_by_id(client_id)
+    local client_name = client and client.name or string.format('id=%d', client_id)
+    local warning = string.format(warning_tpl, client_name)
+    log.warn(warning)
+  end
+  return vim.NIL
+end
+
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
+M['client/unregisterCapability'] = function(_, result, ctx)
+  for _, unreg in ipairs(result.unregisterations) do
+    if unreg.method == 'workspace/didChangeWatchedFiles' then
+      require('vim.lsp._watchfiles').unregister(unreg, ctx)
+    end
+  end
   return vim.NIL
 end
 
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 41dfc9e00e..27dd68645a 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -834,6 +834,10 @@ function protocol.make_client_capabilities()
       semanticTokens = {
         refreshSupport = true,
       },
+      didChangeWatchedFiles = {
+        dynamicRegistration = false,
+        relativePatternSupport = true,
+      },
     },
     experimental = nil,
     window = {
-- 
cgit 


From ed05d38d9fa643c7e562b754c6cfed8b9da5c4d8 Mon Sep 17 00:00:00 2001
From: Mathias Fußenegger 
Date: Sun, 5 Mar 2023 08:42:15 +0100
Subject: fix(lsp): don't monitor files if workspace_folders is nil (#22531)

Fixes:

    Error SERVER_REQUEST_HANDLER_ERROR: "...di/dev/neovim/neovim/runtime/lua/vim/lsp/_watchfiles.lua
    :200: bad argument #1 to 'ipairs' (table expected, got nil)"

Language servers can be started without root_dir or workspace_folders.
---
 runtime/lua/vim/lsp/_watchfiles.lua | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 96d7fa1d35..533a955925 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -168,11 +168,11 @@ end
 
 M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
 
----@type table> client id -> registration id -> cancel function
+---@type table> client id -> registration id -> cancel function
 local cancels = vim.defaulttable()
 
 local queue_timeout_ms = 100
----@type table client id -> libuv timer which will send queued changes at its timeout
+---@type table client id -> libuv timer which will send queued changes at its timeout
 local queue_timers = {}
 ---@type table client id -> set of queued changes to send in a single LSP notification
 local change_queues = {}
@@ -193,6 +193,9 @@ local to_lsp_change_type = {
 function M.register(reg, ctx)
   local client_id = ctx.client_id
   local client = vim.lsp.get_client_by_id(client_id)
+  if not client.workspace_folders then
+    return
+  end
   local watch_regs = {}
   for _, w in ipairs(reg.registerOptions.watchers) do
     local glob_patterns = {}
-- 
cgit 


From 533d671271eb76373d9940161e7bfd201b7e7c2b Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Sun, 5 Mar 2023 18:15:29 -0500
Subject: docs: module-level docstrings (@defgroup) #22498

Problem:
gen_vimdoc.py / lua2dox.lua does not support @defgroup or \defgroup
except for "api-foo" modules.

Solution:
Modify `gen_vimdoc.py` to look for section names based on `helptag_fmt`.

TODO:
- Support @module ?
  https://github.com/LuaLS/lua-language-server/wiki/Annotations#module
---
 runtime/lua/vim/treesitter/languagetree.lua | 34 +++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 1bc7971eba..b8b0dd867e 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1,3 +1,37 @@
+--- @defgroup lua-treesitter-languagetree
+---
+--- @brief A \*LanguageTree\* contains a tree of parsers: the root treesitter parser for {lang} and
+--- any "injected" language parsers, which themselves may inject other languages, recursively.
+--- For example a Lua buffer containing some Vimscript commands needs multiple parsers to fully
+--- understand its contents.
+---
+--- To create a LanguageTree (parser object) for a given buffer and language, use:
+---
+--- 
lua
+---     local parser = vim.treesitter.get_parser(bufnr, lang)
+--- 
+--- +--- (where `bufnr=0` means current buffer). `lang` defaults to 'filetype'. +--- Note: currently the parser is retained for the lifetime of a buffer but this may change; +--- a plugin should keep a reference to the parser object if it wants incremental updates. +--- +--- Whenever you need to access the current syntax tree, parse the buffer: +--- +---
lua
+---     local tree = parser:parse()
+--- 
+--- +--- This returns a table of immutable |treesitter-tree| objects representing the current state of +--- the buffer. When the plugin wants to access the state after a (possible) edit it must call +--- `parse()` again. If the buffer wasn't edited, the same tree will be returned again without extra +--- work. If the buffer was parsed before, incremental parsing will be done of the changed parts. +--- +--- Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback, you must call +--- |vim.treesitter.get_parser()| before you register your callback. But preferably parsing +--- shouldn't be done directly in the change callback anyway as they will be very frequent. Rather +--- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent +--- updates. + local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') -- cgit From f9a46391ab5961fe6c6b7d1efdc96befdd495c11 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 6 Mar 2023 10:57:14 +0000 Subject: refactor(treesitter): simplify some range functions --- runtime/lua/vim/treesitter.lua | 26 +++++++------------------- runtime/lua/vim/treesitter/_range.lua | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index ee66ba9f9b..d13824076e 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -2,6 +2,7 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') +local Range = require('vim.treesitter._range') ---@type table local parsers = setmetatable({}, { __mode = 'v' }) @@ -190,20 +191,7 @@ end --- ---@return boolean True if the position is in node range function M.is_in_node_range(node, line, col) - local start_line, start_col, end_line, end_col = M.get_node_range(node) - if line >= start_line and line <= end_line then - if line == start_line and line == end_line then - return col >= start_col and col < end_col - elseif line == start_line then - return col >= start_col - elseif line == end_line then - return col < end_col - else - return true - end - else - return false - end + return M.node_contains(node, { line, col, line, col }) end --- Determines if a node contains a range @@ -213,11 +201,11 @@ end --- ---@return boolean True if the {node} contains the {range} function M.node_contains(node, range) - local start_row, start_col, end_row, end_col = node:range() - local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2]) - local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4]) - - return start_fits and end_fits + vim.validate({ + node = { node, 'userdata' }, + range = { range, Range.validate, 'integer list with 4 or 6 elements' }, + }) + return Range.contains({ node:range() }, range) end --- Returns a list of highlight captures at the given position diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 8decd3a1fd..bec24a23a5 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -53,6 +53,26 @@ M.cmp_pos = { setmetatable(M.cmp_pos, { __call = cmp_pos }) +---@private +---Check if a variable is a valid range object +---@param r any +---@return boolean +function M.validate(r) + if type(r) ~= 'table' or #r ~= 6 and #r ~= 4 then + return false + end + + for _, e in + ipairs(r --[[@as any[] ]]) + do + if type(e) ~= 'number' then + return false + end + end + + return true +end + ---@private ---@param r1 Range4|Range6 ---@param r2 Range4|Range6 -- cgit From 0e7196438d8f856eecd7c90e160b79cbc8fb08dc Mon Sep 17 00:00:00 2001 From: Kelly Lin Date: Sun, 19 Feb 2023 22:33:57 +1100 Subject: feat(lua): add semver api --- runtime/lua/vim/_init_packages.lua | 3 + runtime/lua/vim/version.lua | 297 +++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 runtime/lua/vim/version.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index e3a442af5e..d032026796 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -69,6 +69,9 @@ setmetatable(vim, { t[key] = val return t[key] end + elseif key == 'version' then + t[key] = require('vim.version') + return t[key] end end, }) diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua new file mode 100644 index 0000000000..49ef019295 --- /dev/null +++ b/runtime/lua/vim/version.lua @@ -0,0 +1,297 @@ +local M = {} + +---@private +--- Compares the prerelease component of the two versions. +---@param v1_parsed table Parsed version. +---@param v2_parsed table Parsed version. +---@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. +local function cmp_prerelease(v1_parsed, v2_parsed) + if v1_parsed.prerelease and not v2_parsed.prerelease then + return -1 + end + if not v1_parsed.prerelease and v2_parsed.prerelease then + return 1 + end + if not v1_parsed.prerelease and not v2_parsed.prerelease then + return 0 + end + + local v1_identifiers = vim.split(v1_parsed.prerelease, '.', { plain = true }) + local v2_identifiers = vim.split(v2_parsed.prerelease, '.', { plain = true }) + local i = 1 + local max = math.max(vim.tbl_count(v1_identifiers), vim.tbl_count(v2_identifiers)) + while i <= max do + local v1_identifier = v1_identifiers[i] + local v2_identifier = v2_identifiers[i] + if v1_identifier ~= v2_identifier then + local v1_num = tonumber(v1_identifier) + local v2_num = tonumber(v2_identifier) + local is_number = v1_num and v2_num + if is_number then + -- Number comparisons + if not v1_num and v2_num then + return -1 + end + if v1_num and not v2_num then + return 1 + end + if v1_num == v2_num then + return 0 + end + if v1_num > v2_num then + return 1 + end + if v1_num < v2_num then + return -1 + end + else + -- String comparisons + if v1_identifier and not v2_identifier then + return 1 + end + if not v1_identifier and v2_identifier then + return -1 + end + if v1_identifier < v2_identifier then + return -1 + end + if v1_identifier > v2_identifier then + return 1 + end + if v1_identifier == v2_identifier then + return 0 + end + end + end + i = i + 1 + end + + return 0 +end + +---@private +--- Compares the version core component of the two versions. +---@param v1_parsed table Parsed version. +---@param v2_parsed table Parsed version. +---@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. +local function cmp_version_core(v1_parsed, v2_parsed) + if + v1_parsed.major == v2_parsed.major + and v1_parsed.minor == v2_parsed.minor + and v1_parsed.patch == v2_parsed.patch + then + return 0 + end + + if + v1_parsed.major > v2_parsed.major + or v1_parsed.minor > v2_parsed.minor + or v1_parsed.patch > v2_parsed.patch + then + return 1 + end + + return -1 +end + +--- Compares two strings (`v1` and `v2`) in semver format. +---@param v1 string Version. +---@param v2 string Version to be compared with v1. +---@param opts table|nil Optional keyword arguments: +--- - strict (boolean): see `semver.parse` for details. Defaults to false. +---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. +function M.cmp(v1, v2, opts) + opts = opts or { strict = false } + local v1_parsed = M.parse(v1, opts) + local v2_parsed = M.parse(v2, opts) + + local result = cmp_version_core(v1_parsed, v2_parsed) + if result == 0 then + result = cmp_prerelease(v1_parsed, v2_parsed) + end + return result +end + +---@private +---@param labels string Prerelease and build component of semantic version string e.g. "-rc1+build.0". +---@return string|nil +local function parse_prerelease(labels) + -- This pattern matches "-(alpha)+build.15". + -- '^%-[%w%.]+$' + local result = labels:match('^%-([%w%.]+)+.+$') + if result then + return result + end + -- This pattern matches "-(alpha)". + result = labels:match('^%-([%w%.]+)') + if result then + return result + end + + return nil +end + +---@private +---@param labels string Prerelease and build component of semantic version string e.g. "-rc1+build.0". +---@return string|nil +local function parse_build(labels) + -- Pattern matches "-alpha+(build.15)". + local result = labels:match('^%-[%w%.]+%+([%w%.]+)$') + if result then + return result + end + + -- Pattern matches "+(build.15)". + result = labels:match('^%+([%w%.]+)$') + if result then + return result + end + + return nil +end + +---@private +--- Extracts the major, minor, patch and preprelease and build components from +--- `version`. +---@param version string Version string +local function extract_components_strict(version) + local major, minor, patch, prerelease_and_build = version:match('^v?(%d+)%.(%d+)%.(%d+)(.*)$') + return tonumber(major), tonumber(minor), tonumber(patch), prerelease_and_build +end + +---@private +--- Extracts the major, minor, patch and preprelease and build components from +--- `version`. When `minor` and `patch` components are not found (nil), coerce +--- them to 0. +---@param version string Version string +local function extract_components_loose(version) + local major, minor, patch, prerelease_and_build = version:match('^v?(%d+)%.?(%d*)%.?(%d*)(.*)$') + major = tonumber(major) + minor = tonumber(minor) or 0 + patch = tonumber(patch) or 0 + return major, minor, patch, prerelease_and_build +end + +---@private +--- Validates the prerelease and build string e.g. "-rc1+build.0". If the +--- prerelease, build or both are valid forms then it will return true, if it +--- is not of any valid form, it will return false. +---@param prerelease_and_build string +---@return boolean +local function is_prerelease_and_build_valid(prerelease_and_build) + if prerelease_and_build == '' then + return true + end + local has_build = parse_build(prerelease_and_build) ~= nil + local has_prerelease = parse_prerelease(prerelease_and_build) ~= nil + local has_prerelease_and_build = has_prerelease and has_build + return has_build or has_prerelease or has_prerelease_and_build +end + +---@private +---@param version string +---@return string +local function create_err_msg(version) + return string.format('invalid version: "%s"', version) +end + +--- Parses a semantically formatted version string into a table. +--- +--- Supports leading "v" and leading and trailing whitespace in the version +--- string. e.g. `" v1.0.1-rc1+build.2"` , `"1.0.1-rc1+build.2"`, `"v1.0.1-rc1+build.2"` +--- and `"v1.0.1-rc1+build.2 "` will be parsed as: +--- +--- `{ major = 1, minor = 0, patch = 1, prerelease = 'rc1', build = 'build.2' }` +--- +---@param version string Version string to be parsed. +---@param opts table|nil Optional keyword arguments: +--- - strict (boolean): when set to `true` an error will be thrown for version +--- strings that do not conform to the semver specification (v2.0.0) (see +--- semver.org/spec/v2.0.0.html for details). This means that +--- `semver.parse('v1.2)` will throw an error. When set to `false`, +--- `semver.parse('v1.2)` will coerce 'v1.2' to 'v1.2.0' and return the table: +--- `{ major = 1, minor = 2, patch = 0 }`. Defaults to false. +---@return table|nil parsed_version Parsed version table or `nil` if `version` is not valid. +function M.parse(version, opts) + if type(version) ~= 'string' then + error(create_err_msg(version)) + end + + opts = opts or { strict = false } + + version = vim.trim(version) + + local extract_components = opts.strict and extract_components_strict or extract_components_loose + local major, minor, patch, prerelease_and_build = extract_components(version) + + -- If major is nil then that means that the version does not begin with a + -- digit with or without a "v" prefix. + if major == nil or not is_prerelease_and_build_valid(prerelease_and_build) then + return nil + end + + local prerelease = nil + local build = nil + if prerelease_and_build ~= nil then + prerelease = parse_prerelease(prerelease_and_build) + build = parse_build(prerelease_and_build) + end + + return { + major = major, + minor = minor, + patch = patch, + prerelease = prerelease, + build = build, + } +end + +---@private +--- Throws an error if `version` cannot be parsed. +---@param version string +local function assert_version(version) + if M.parse(version) == nil then + error(create_err_msg(version)) + end +end + +---Returns `true` if `v1` are `v2` are equal versions. +---@param version_1 string +---@param version_2 string +---@return boolean +function M.eq(version_1, version_2) + assert_version(version_1) + assert_version(version_2) + + return M.cmp(version_1, version_2) == 0 +end + +---Returns `true` if `v1` is less than `v2`. +---@param version_1 string +---@param version_2 string +---@return boolean +function M.lt(version_1, version_2) + assert_version(version_1) + assert_version(version_2) + + return M.cmp(version_1, version_2) == -1 +end + +---Returns `true` if `v1` is greater than `v2`. +---@param version_1 string +---@param version_2 string +---@return boolean +function M.gt(version_1, version_2) + assert_version(version_1) + assert_version(version_2) + + return M.cmp(version_1, version_2) == 1 +end + +setmetatable(M, { + __call = function() + return vim.fn.api_info().version + end, +}) + +return M -- cgit From e31e49a8e3aac25e923dce15cc76dca4a447947f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 6 Mar 2023 13:23:03 +0100 Subject: refactor(vim.version): cleanup - version.cmp(): assert valid version - add test for loading vim.version (the other tests use shared.lua in the test runner) - reduce test scopes, reword test descriptions --- runtime/lua/vim/_init_packages.lua | 8 +-- runtime/lua/vim/version.lua | 134 ++++++++++++++++--------------------- 2 files changed, 62 insertions(+), 80 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index d032026796..57c0fc9122 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -51,7 +51,10 @@ end -- builtin functions which always should be available require('vim.shared') -vim._submodules = { inspect = true } +vim._submodules = { + inspect = true, + version = true, +} -- These are for loading runtime modules in the vim namespace lazily. setmetatable(vim, { @@ -69,9 +72,6 @@ setmetatable(vim, { t[key] = val return t[key] end - elseif key == 'version' then - t[key] = require('vim.version') - return t[key] end end, }) diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 49ef019295..ddbe228244 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -1,23 +1,41 @@ local M = {} +---@private +---@param version string +---@return string +local function create_err_msg(version) + return string.format('invalid version: "%s"', version) +end + +---@private +--- Throws an error if `version` cannot be parsed. +---@param version string +local function assert_version(version, opt) + local rv = M.parse(version, opt) + if rv == nil then + error(create_err_msg(version)) + end + return rv +end + ---@private --- Compares the prerelease component of the two versions. ----@param v1_parsed table Parsed version. ----@param v2_parsed table Parsed version. ----@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. -local function cmp_prerelease(v1_parsed, v2_parsed) - if v1_parsed.prerelease and not v2_parsed.prerelease then +---@param v1 table Parsed version. +---@param v2 table Parsed version. +---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. +local function cmp_prerelease(v1, v2) + if v1.prerelease and not v2.prerelease then return -1 end - if not v1_parsed.prerelease and v2_parsed.prerelease then + if not v1.prerelease and v2.prerelease then return 1 end - if not v1_parsed.prerelease and not v2_parsed.prerelease then + if not v1.prerelease and not v2.prerelease then return 0 end - local v1_identifiers = vim.split(v1_parsed.prerelease, '.', { plain = true }) - local v2_identifiers = vim.split(v2_parsed.prerelease, '.', { plain = true }) + local v1_identifiers = vim.split(v1.prerelease, '.', { plain = true }) + local v2_identifiers = vim.split(v2.prerelease, '.', { plain = true }) local i = 1 local max = math.max(vim.tbl_count(v1_identifiers), vim.tbl_count(v2_identifiers)) while i <= max do @@ -71,23 +89,15 @@ end ---@private --- Compares the version core component of the two versions. ----@param v1_parsed table Parsed version. ----@param v2_parsed table Parsed version. ----@return integer `-1` if `v1_parsed < v2_parsed`, `0` if `v1_parsed == v2_parsed`, `1` if `v1_parsed > v2_parsed`. -local function cmp_version_core(v1_parsed, v2_parsed) - if - v1_parsed.major == v2_parsed.major - and v1_parsed.minor == v2_parsed.minor - and v1_parsed.patch == v2_parsed.patch - then +---@param v1 table Parsed version. +---@param v2 table Parsed version. +---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. +local function cmp_version_core(v1, v2) + if v1.major == v2.major and v1.minor == v2.minor and v1.patch == v2.patch then return 0 end - if - v1_parsed.major > v2_parsed.major - or v1_parsed.minor > v2_parsed.minor - or v1_parsed.patch > v2_parsed.patch - then + if v1.major > v2.major or v1.minor > v2.minor or v1.patch > v2.patch then return 1 end @@ -96,14 +106,14 @@ end --- Compares two strings (`v1` and `v2`) in semver format. ---@param v1 string Version. ----@param v2 string Version to be compared with v1. +---@param v2 string Version to compare with v1. ---@param opts table|nil Optional keyword arguments: --- - strict (boolean): see `semver.parse` for details. Defaults to false. ---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. function M.cmp(v1, v2, opts) opts = opts or { strict = false } - local v1_parsed = M.parse(v1, opts) - local v2_parsed = M.parse(v2, opts) + local v1_parsed = assert_version(v1, opts) + local v2_parsed = assert_version(v2, opts) local result = cmp_version_core(v1_parsed, v2_parsed) if result == 0 then @@ -188,30 +198,20 @@ local function is_prerelease_and_build_valid(prerelease_and_build) return has_build or has_prerelease or has_prerelease_and_build end ----@private ----@param version string ----@return string -local function create_err_msg(version) - return string.format('invalid version: "%s"', version) -end - ---- Parses a semantically formatted version string into a table. ---- ---- Supports leading "v" and leading and trailing whitespace in the version ---- string. e.g. `" v1.0.1-rc1+build.2"` , `"1.0.1-rc1+build.2"`, `"v1.0.1-rc1+build.2"` ---- and `"v1.0.1-rc1+build.2 "` will be parsed as: +--- Parses a semantic version string. --- ---- `{ major = 1, minor = 0, patch = 1, prerelease = 'rc1', build = 'build.2' }` +--- Ignores leading "v" and surrounding whitespace, e.g. " v1.0.1-rc1+build.2", +--- "1.0.1-rc1+build.2", "v1.0.1-rc1+build.2" and "v1.0.1-rc1+build.2 " are all parsed as: +---
+---   { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" }
+--- 
--- ---@param version string Version string to be parsed. ---@param opts table|nil Optional keyword arguments: ---- - strict (boolean): when set to `true` an error will be thrown for version ---- strings that do not conform to the semver specification (v2.0.0) (see ---- semver.org/spec/v2.0.0.html for details). This means that ---- `semver.parse('v1.2)` will throw an error. When set to `false`, ---- `semver.parse('v1.2)` will coerce 'v1.2' to 'v1.2.0' and return the table: ---- `{ major = 1, minor = 2, patch = 0 }`. Defaults to false. ----@return table|nil parsed_version Parsed version table or `nil` if `version` is not valid. +--- - strict (boolean): Default false. If `true`, no coercion is attempted on +--- input not strictly conforming to semver v2.0.0 +--- (https://semver.org/spec/v2.0.0.html). E.g. `parse("v1.2")` returns nil. +---@return table|nil parsed_version Parsed version table or `nil` if `version` is invalid. function M.parse(version, opts) if type(version) ~= 'string' then error(create_err_msg(version)) @@ -246,46 +246,28 @@ function M.parse(version, opts) } end ----@private ---- Throws an error if `version` cannot be parsed. ----@param version string -local function assert_version(version) - if M.parse(version) == nil then - error(create_err_msg(version)) - end -end - ---Returns `true` if `v1` are `v2` are equal versions. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.eq(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == 0 +function M.eq(v1, v2) + return M.cmp(v1, v2) == 0 end ---Returns `true` if `v1` is less than `v2`. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.lt(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == -1 +function M.lt(v1, v2) + return M.cmp(v1, v2) == -1 end ---Returns `true` if `v1` is greater than `v2`. ----@param version_1 string ----@param version_2 string +---@param v1 string +---@param v2 string ---@return boolean -function M.gt(version_1, version_2) - assert_version(version_1) - assert_version(version_2) - - return M.cmp(version_1, version_2) == 1 +function M.gt(v1, v2) + return M.cmp(v1, v2) == 1 end setmetatable(M, { -- cgit From 74ffebf8ec725a25c2ae1dde81cf26b83fc7ae61 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 6 Mar 2023 15:08:22 +0100 Subject: fix(vim.version): incorrect version.cmp() Problem: If majorminor, cmp_version_core returns 1 Solution: - Fix logic in cmp_version_core - Delete most eq()/gt()/lt() tests, they are redundant. --- runtime/lua/vim/version.lua | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index ddbe228244..35629c461f 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -3,8 +3,11 @@ local M = {} ---@private ---@param version string ---@return string -local function create_err_msg(version) - return string.format('invalid version: "%s"', version) +local function create_err_msg(v) + if type(v) == 'string' then + return string.format('invalid version: "%s"', tostring(v)) + end + return string.format('invalid version: %s (%s)', tostring(v), type(v)) end ---@private @@ -20,9 +23,6 @@ end ---@private --- Compares the prerelease component of the two versions. ----@param v1 table Parsed version. ----@param v2 table Parsed version. ----@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. local function cmp_prerelease(v1, v2) if v1.prerelease and not v2.prerelease then return -1 @@ -88,19 +88,17 @@ local function cmp_prerelease(v1, v2) end ---@private ---- Compares the version core component of the two versions. ----@param v1 table Parsed version. ----@param v2 table Parsed version. ----@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. local function cmp_version_core(v1, v2) if v1.major == v2.major and v1.minor == v2.minor and v1.patch == v2.patch then return 0 end - - if v1.major > v2.major or v1.minor > v2.minor or v1.patch > v2.patch then + if + v1.major > v2.major + or (v1.major == v2.major and v1.minor > v2.minor) + or (v1.major == v2.major and v1.minor == v2.minor and v1.patch > v2.patch) + then return 1 end - return -1 end -- cgit From 1cc23e1109ed88275df5c986c352f73b99a0301c Mon Sep 17 00:00:00 2001 From: swarn Date: Mon, 6 Mar 2023 12:03:13 -0600 Subject: feat(lsp)!: add rule-based sem token highlighting (#22022) feat(lsp)!: change semantic token highlighting Change the default highlights used, and add more highlights per token. Add an LspTokenUpdate event and a highlight_token function. :Inspect now shows any highlights applied by token highlighting rules, default or user-defined. BREAKING CHANGE: change the default highlight groups used by semantic token highlighting. --- runtime/lua/vim/_inspector.lua | 97 +++++++++-------- runtime/lua/vim/lsp/semantic_tokens.lua | 183 ++++++++++++++++++++++---------- 2 files changed, 178 insertions(+), 102 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 9e91597192..92d380b08c 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -2,7 +2,7 @@ ---@field syntax boolean include syntax based highlight groups (defaults to true) ---@field treesitter boolean include treesitter based highlight groups (defaults to true) ---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true) ----@field semantic_tokens boolean include semantic tokens (defaults to true) +---@field semantic_tokens boolean include semantic token highlights (defaults to true) local defaults = { syntax = true, treesitter = true, @@ -81,47 +81,54 @@ function vim.inspect_pos(bufnr, row, col, filter) end end - -- semantic tokens - if filter.semantic_tokens then - for _, token in ipairs(vim.lsp.semantic_tokens.get_at_pos(bufnr, row, col) or {}) do - token.hl_groups = { - type = resolve_hl({ hl_group = '@' .. token.type }), - modifiers = vim.tbl_map(function(modifier) - return resolve_hl({ hl_group = '@' .. modifier }) - end, token.modifiers or {}), - } - table.insert(results.semantic_tokens, token) + --- Convert an extmark tuple into a map-like table + --- @private + local function to_map(extmark) + extmark = { + id = extmark[1], + row = extmark[2], + col = extmark[3], + opts = resolve_hl(extmark[4]), + } + extmark.end_row = extmark.opts.end_row or extmark.row -- inclusive + extmark.end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive + return extmark + end + + --- Check if an extmark overlaps this position + --- @private + local function is_here(extmark) + return (row >= extmark.row and row <= extmark.end_row) -- within the rows of the extmark + and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col + and (row < extmark.end_row or col < extmark.end_col) -- either not in the last row or in range of the col + end + + -- all extmarks at this position + local extmarks = {} + for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do + local ns_marks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) + ns_marks = vim.tbl_map(to_map, ns_marks) + ns_marks = vim.tbl_filter(is_here, ns_marks) + for _, mark in ipairs(ns_marks) do + mark.ns_id = nsid + mark.ns = ns end + vim.list_extend(extmarks, ns_marks) + end + + if filter.semantic_tokens then + results.semantic_tokens = vim.tbl_filter(function(extmark) + return extmark.ns:find('vim_lsp_semantic_tokens') == 1 + end, extmarks) end - -- extmarks if filter.extmarks then - for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do - if ns:find('vim_lsp_semantic_tokens') ~= 1 then - local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) - for _, extmark in ipairs(extmarks) do - extmark = { - ns_id = nsid, - ns = ns, - id = extmark[1], - row = extmark[2], - col = extmark[3], - opts = resolve_hl(extmark[4]), - } - local end_row = extmark.opts.end_row or extmark.row -- inclusive - local end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive - if - (filter.extmarks == 'all' or extmark.opts.hl_group) -- filter hl_group - and (row >= extmark.row and row <= end_row) -- within the rows of the extmark - and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col - and (row < end_row or col < end_col) -- either not in the last row or in range of the col - then - table.insert(results.extmarks, extmark) - end - end - end - end + results.extmarks = vim.tbl_filter(function(extmark) + return extmark.ns:find('vim_lsp_semantic_tokens') ~= 1 + and (filter.extmarks == 'all' or extmark.opts.hl_group) + end, extmarks) end + return results end @@ -174,16 +181,17 @@ function vim.show_pos(bufnr, row, col, filter) nl() end + -- semantic tokens if #items.semantic_tokens > 0 then append('Semantic Tokens', 'Title') nl() - for _, token in ipairs(items.semantic_tokens) do - local client = vim.lsp.get_client_by_id(token.client_id) - client = client and (' (' .. client.name .. ')') or '' - item(token.hl_groups.type, 'type' .. client) - for _, modifier in ipairs(token.hl_groups.modifiers) do - item(modifier, 'modifier' .. client) - end + local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right) + local left_first = left.opts.priority < right.opts.priority + or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group + return left_first and -1 or 1 + end) + for _, extmark in ipairs(sorted_marks) do + item(extmark.opts, 'priority: ' .. extmark.opts.priority) end nl() end @@ -197,6 +205,7 @@ function vim.show_pos(bufnr, row, col, filter) end nl() end + -- extmarks if #items.extmarks > 0 then append('Extmarks', 'Title') diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 24b5c6c24e..7983d066b8 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -8,8 +8,8 @@ local bit = require('bit') --- @field start_col number start column 0-based --- @field end_col number end column 0-based --- @field type string token type as string ---- @field modifiers string[] token modifiers as strings ---- @field extmark_added boolean whether this extmark has been added to the buffer yet +--- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true } +--- @field marked boolean whether this token has had extmarks applied --- --- @class STCurrentResult --- @field version number document version associated with this result @@ -36,10 +36,13 @@ local bit = require('bit') ---@field client_state table local STHighlighter = { active = {} } +--- Do a binary search of the tokens in the half-open range [lo, hi). +--- +--- Return the index i in range such that tokens[j].line < line for all j < i, and +--- tokens[j].line >= line for all j >= i, or return hi if no such index is found. +--- ---@private -local function binary_search(tokens, line) - local lo = 1 - local hi = #tokens +local function lower_bound(tokens, line, lo, hi) while lo < hi do local mid = math.floor((lo + hi) / 2) if tokens[mid].line < line then @@ -51,16 +54,34 @@ local function binary_search(tokens, line) return lo end +--- Do a binary search of the tokens in the half-open range [lo, hi). +--- +--- Return the index i in range such that tokens[j].line <= line for all j < i, and +--- tokens[j].line > line for all j >= i, or return hi if no such index is found. +--- +---@private +local function upper_bound(tokens, line, lo, hi) + while lo < hi do + local mid = math.floor((lo + hi) / 2) + if line < tokens[mid].line then + hi = mid + else + lo = mid + 1 + end + end + return lo +end + --- Extracts modifier strings from the encoded number in the token array --- ---@private ----@return string[] +---@return table local function modifiers_from_number(x, modifiers_table) local modifiers = {} local idx = 1 while x > 0 do if bit.band(x, 1) == 1 then - modifiers[#modifiers + 1] = modifiers_table[idx] + modifiers[modifiers_table[idx]] = true end x = bit.rshift(x, 1) idx = idx + 1 @@ -109,7 +130,7 @@ local function tokens_to_ranges(data, bufnr, client) end_col = end_col, type = token_type, modifiers = modifiers, - extmark_added = false, + marked = false, } end end @@ -355,7 +376,7 @@ end --- ---@private function STHighlighter:on_win(topline, botline) - for _, state in pairs(self.client_state) do + for client_id, state in pairs(self.client_state) do local current_result = state.current_result if current_result.version and current_result.version == util.buf_versions[self.bufnr] then if not current_result.namespace_cleared then @@ -372,52 +393,55 @@ function STHighlighter:on_win(topline, botline) -- -- Instead, we have to use normal extmarks that can attach to locations -- in the buffer and are persisted between redraws. + -- + -- `strict = false` is necessary here for the 1% of cases where the + -- current result doesn't actually match the buffer contents. Some + -- LSP servers can respond with stale tokens on requests if they are + -- still processing changes from a didChange notification. + -- + -- LSP servers that do this _should_ follow up known stale responses + -- with a refresh notification once they've finished processing the + -- didChange notification, which would re-synchronize the tokens from + -- our end. + -- + -- The server I know of that does this is clangd when the preamble of + -- a file changes and the token request is processed with a stale + -- preamble while the new one is still being built. Once the preamble + -- finishes, clangd sends a refresh request which lets the client + -- re-synchronize the tokens. + + local set_mark = function(token, hl_group, delta) + vim.api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { + hl_group = hl_group, + end_col = token.end_col, + priority = vim.highlight.priorities.semantic_tokens + delta, + strict = false, + }) + end + + local ft = vim.bo[self.bufnr].filetype local highlights = current_result.highlights - local idx = binary_search(highlights, topline) + local first = lower_bound(highlights, topline, 1, #highlights + 1) + local last = upper_bound(highlights, botline, first, #highlights + 1) - 1 - for i = idx, #highlights do + for i = first, last do local token = highlights[i] - - if token.line > botline then - break - end - - if not token.extmark_added then - -- `strict = false` is necessary here for the 1% of cases where the - -- current result doesn't actually match the buffer contents. Some - -- LSP servers can respond with stale tokens on requests if they are - -- still processing changes from a didChange notification. - -- - -- LSP servers that do this _should_ follow up known stale responses - -- with a refresh notification once they've finished processing the - -- didChange notification, which would re-synchronize the tokens from - -- our end. - -- - -- The server I know of that does this is clangd when the preamble of - -- a file changes and the token request is processed with a stale - -- preamble while the new one is still being built. Once the preamble - -- finishes, clangd sends a refresh request which lets the client - -- re-synchronize the tokens. - api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { - hl_group = '@' .. token.type, - end_col = token.end_col, - priority = vim.highlight.priorities.semantic_tokens, - strict = false, - }) - - -- TODO(bfredl) use single extmark when hl_group supports table - if #token.modifiers > 0 then - for _, modifier in pairs(token.modifiers) do - api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, { - hl_group = '@' .. modifier, - end_col = token.end_col, - priority = vim.highlight.priorities.semantic_tokens + 1, - strict = false, - }) - end + if not token.marked then + set_mark(token, string.format('@lsp.type.%s.%s', token.type, ft), 0) + for modifier, _ in pairs(token.modifiers) do + set_mark(token, string.format('@lsp.mod.%s.%s', modifier, ft), 1) + set_mark(token, string.format('@lsp.typemod.%s.%s.%s', token.type, modifier, ft), 2) end - - token.extmark_added = true + token.marked = true + + api.nvim_exec_autocmds('LspTokenUpdate', { + pattern = vim.api.nvim_buf_get_name(self.bufnr), + modeline = false, + data = { + token = token, + client_id = client_id, + }, + }) end end end @@ -588,7 +612,13 @@ end ---@param row number|nil Position row (default cursor position) ---@param col number|nil Position column (default cursor position) --- ----@return table|nil (table|nil) List of tokens at position +---@return table|nil (table|nil) List of tokens at position. Each token has +--- the following fields: +--- - line (number) line number, 0-based +--- - start_col (number) start column, 0-based +--- - end_col (number) end column, 0-based +--- - type (string) token type as string, e.g. "variable" +--- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true } function M.get_at_pos(bufnr, row, col) if bufnr == nil or bufnr == 0 then bufnr = api.nvim_get_current_buf() @@ -608,7 +638,7 @@ function M.get_at_pos(bufnr, row, col) for client_id, client in pairs(highlighter.client_state) do local highlights = client.current_result.highlights if highlights then - local idx = binary_search(highlights, row) + local idx = lower_bound(highlights, row, 1, #highlights + 1) for i = idx, #highlights do local token = highlights[i] @@ -631,23 +661,60 @@ end --- Only has an effect if the buffer is currently active for semantic token --- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) --- ----@param bufnr (nil|number) default: current buffer +---@param bufnr (number|nil) filter by buffer. All buffers if nil, current +--- buffer if 0 function M.force_refresh(bufnr) vim.validate({ bufnr = { bufnr, 'n', true }, }) - if bufnr == nil or bufnr == 0 then - bufnr = api.nvim_get_current_buf() + local buffers = bufnr == nil and vim.tbl_keys(STHighlighter.active) + or bufnr == 0 and { api.nvim_get_current_buf() } + or { bufnr } + + for _, buffer in ipairs(buffers) do + local highlighter = STHighlighter.active[buffer] + if highlighter then + highlighter:reset() + highlighter:send_request() + end end +end +--- Highlight a semantic token. +--- +--- Apply an extmark with a given highlight group for a semantic token. The +--- 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 (number) the buffer to highlight +---@param client_id (number) The ID of the |vim.lsp.client| +---@param hl_group (string) Highlight group name +---@param opts (table|nil) Optional parameters. +--- - priority: (number|nil) Priority for the applied extmark. Defaults +--- to `vim.highlight.priorities.semantic_tokens + 3` +function M.highlight_token(token, bufnr, client_id, hl_group, opts) local highlighter = STHighlighter.active[bufnr] if not highlighter then return end - highlighter:reset() - highlighter:send_request() + local state = highlighter.client_state[client_id] + if not state then + return + end + + opts = opts or {} + local priority = opts.priority or vim.highlight.priorities.semantic_tokens + 3 + + vim.api.nvim_buf_set_extmark(bufnr, state.namespace, token.line, token.start_col, { + hl_group = hl_group, + end_col = token.end_col, + priority = priority, + strict = false, + }) end --- |lsp-handler| for the method `workspace/semanticTokens/refresh` -- cgit From 79571b92ced968ad27bee2a7515a4a04e84dbad2 Mon Sep 17 00:00:00 2001 From: Björn Linse Date: Wed, 27 Jan 2021 09:00:28 +0100 Subject: feat(lua): omnifunc for builting lua interpreter also make implicit submodules "uri" and "_inspector" work with completion this is needed for `:lua=vim.uri_` wildmenu completion to work even before uri or _inspector functions are used. --- runtime/lua/vim/_editor.lua | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index c205451ff9..9516233b45 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -42,6 +42,18 @@ for k, v in pairs({ vim._submodules[k] = v end +-- There are things which have special rules in vim._init_packages +-- for legacy reasons (uri) or for performance (_inspector). +-- most new things should go into a submodule namespace ( vim.foobar.do_thing() ) +vim._extra = { + uri_from_fname = true, + uri_from_bufnr = true, + uri_to_fname = true, + uri_to_bufnr = true, + show_pos = true, + inspect_pos = true, +} + vim.log = { levels = { TRACE = 0, @@ -575,15 +587,13 @@ function vim._on_key(char) end --- Generate a list of possible completions for the string. ---- String starts with ^ and then has the pattern. +--- String has the pattern. --- --- 1. Can we get it to just return things in the global namespace with that name prefix --- 2. Can we get it to return things from global namespace even with `print(` in front. function vim._expand_pat(pat, env) env = env or _G - pat = string.sub(pat, 2, #pat) - if pat == '' then local result = vim.tbl_keys(env) table.sort(result) @@ -644,7 +654,7 @@ function vim._expand_pat(pat, env) local mt = getmetatable(final_env) if mt and type(mt.__index) == 'table' then field = rawget(mt.__index, key) - elseif final_env == vim and vim._submodules[key] then + elseif final_env == vim and (vim._submodules[key] or vim._extra[key]) then field = vim[key] end end @@ -674,6 +684,7 @@ function vim._expand_pat(pat, env) end if final_env == vim then insert_keys(vim._submodules) + insert_keys(vim._extra) end keys = vim.tbl_keys(keys) @@ -745,6 +756,28 @@ vim._expand_pat_get_parts = function(lua_string) return parts, search_index end +do + -- Ideally we should just call complete() inside omnifunc, though there are + -- some bugs, so fake the two-step dance for now. + local matches + + --- Omnifunc for completing lua values from from the runtime lua interpreter, + --- similar to the builtin completion for the `:lua` command. + --- + --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a lua buffer. + function vim.lua_omnifunc(find_start, _) + if find_start == 1 then + local line = vim.api.nvim_get_current_line() + local prefix = string.sub(line, 1, vim.api.nvim_win_get_cursor(0)[2]) + local pos + matches, pos = vim._expand_pat(prefix) + return (#matches > 0 and pos) or -1 + else + return matches + end + end +end + ---Prints given arguments in human-readable format. ---Example: ---
lua
-- 
cgit 


From 706bcab75eaad2c370d61bf828531054439d3a3e Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Tue, 7 Mar 2023 15:17:52 +0900
Subject: docs(lsp): change type annotations from number → integer (#22510)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 runtime/lua/vim/lsp.lua                 | 72 +++++++++++++++----------------
 runtime/lua/vim/lsp/_snippet.lua        |  2 +-
 runtime/lua/vim/lsp/codelens.lua        | 14 +++---
 runtime/lua/vim/lsp/diagnostic.lua      | 12 +++---
 runtime/lua/vim/lsp/log.lua             |  4 +-
 runtime/lua/vim/lsp/rpc.lua             | 16 +++----
 runtime/lua/vim/lsp/semantic_tokens.lua | 60 +++++++++++++-------------
 runtime/lua/vim/lsp/sync.lua            |  6 +--
 runtime/lua/vim/lsp/util.lua            | 76 ++++++++++++++++-----------------
 9 files changed, 131 insertions(+), 131 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index a56e141c29..9a0b3f3100 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -363,7 +363,7 @@ do
   --- smallest debounce interval is used and we don't group clients by different intervals.
   ---
   --- @class CTGroup
-  --- @field sync_kind number TextDocumentSyncKind, considers config.flags.allow_incremental_sync
+  --- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync
   --- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
   ---
   --- @class CTBufferState
@@ -374,12 +374,12 @@ do
   --- @field timer nil|uv.uv_timer_t uv_timer
   --- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
   --- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
-  --- @field refs number how many clients are using this group
+  --- @field refs integer how many clients are using this group
   ---
   --- @class CTGroupState
-  --- @field buffers table
-  --- @field debounce number debounce duration in ms
-  --- @field clients table clients using this state. {client_id, client}
+  --- @field buffers table
+  --- @field debounce integer debounce duration in ms
+  --- @field clients table clients using this state. {client_id, client}
 
   ---@private
   ---@param group CTGroup
@@ -568,7 +568,7 @@ do
   --
   -- This turns the debounce into a kind of client rate limiting
   --
-  ---@param debounce number
+  ---@param debounce integer
   ---@param buf_state CTBufferState
   ---@return number
   local function next_debounce(debounce, buf_state)
@@ -585,8 +585,8 @@ do
   end
 
   ---@private
-  ---@param bufnr number
-  ---@param sync_kind number protocol.TextDocumentSyncKind
+  ---@param bufnr integer
+  ---@param sync_kind integer protocol.TextDocumentSyncKind
   ---@param state CTGroupState
   ---@param buf_state CTBufferState
   local function send_changes(bufnr, sync_kind, state, buf_state)
@@ -714,7 +714,7 @@ end
 ---@private
 --- Default handler for the 'textDocument/didOpen' LSP notification.
 ---
----@param bufnr number Number of the buffer, or 0 for current
+---@param bufnr integer Number of the buffer, or 0 for current
 ---@param client table Client object
 local function text_document_did_open_handler(bufnr, client)
   changetracking.init(client, bufnr)
@@ -1095,7 +1095,7 @@ function lsp.start_client(config)
   ---@private
   --- Invoked when the client operation throws an error.
   ---
-  ---@param code (number) Error code
+  ---@param code (integer) Error code
   ---@param err (...) Other arguments may be passed depending on the error kind
   ---@see `vim.lsp.rpc.client_errors` for possible errors. Use
   ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
@@ -1148,8 +1148,8 @@ function lsp.start_client(config)
   ---@private
   --- Invoked on client exit.
   ---
-  ---@param code (number) exit code of the process
-  ---@param signal (number) the signal used to terminate (if any)
+  ---@param code (integer) exit code of the process
+  ---@param signal (integer) the signal used to terminate (if any)
   function dispatch.on_exit(code, signal)
     if config.on_exit then
       pcall(config.on_exit, code, signal, client_id)
@@ -1414,9 +1414,9 @@ function lsp.start_client(config)
   ---
   ---@param method (string) LSP method name.
   ---@param params (table) LSP request params.
-  ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for
+  ---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
   ---                               a result. Defaults to 1000
-  ---@param bufnr (number) Buffer handle (0 for current).
+  ---@param bufnr (integer) Buffer handle (0 for current).
   ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
   --- `err` and `result` come from the |lsp-handler|.
   --- On timeout, cancel or error, returns `(nil, err)` where `err` is a
@@ -1465,7 +1465,7 @@ function lsp.start_client(config)
   ---@private
   --- Cancels a request with a given request id.
   ---
-  ---@param id (number) id of request to cancel
+  ---@param id (integer) id of request to cancel
   ---@return boolean status true if notification was successful. false otherwise
   ---@see |vim.lsp.client.notify()|
   function client.cancel_request(id)
@@ -1622,8 +1622,8 @@ end
 ---
 --- Without calling this, the server won't be notified of changes to a buffer.
 ---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) Client id
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param client_id (integer) Client id
 function lsp.buf_attach_client(bufnr, client_id)
   validate({
     bufnr = { bufnr, 'n', true },
@@ -1734,8 +1734,8 @@ end
 --- Note: While the server is notified that the text document (buffer)
 --- was closed, it is still able to send notifications should it ignore this notification.
 ---
----@param bufnr number Buffer handle, or 0 for current
----@param client_id number Client id
+---@param bufnr integer Buffer handle, or 0 for current
+---@param client_id integer Client id
 function lsp.buf_detach_client(bufnr, client_id)
   validate({
     bufnr = { bufnr, 'n', true },
@@ -1785,8 +1785,8 @@ end
 
 --- Checks if a buffer is attached for a particular client.
 ---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) the client id
+---@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
 end
@@ -1794,7 +1794,7 @@ end
 --- Gets a client by id, or nil if the id is invalid.
 --- The returned client may not yet be fully initialized.
 ---
----@param client_id number client id
+---@param client_id integer client id
 ---
 ---@returns |vim.lsp.client| object, or nil
 function lsp.get_client_by_id(client_id)
@@ -1821,7 +1821,7 @@ end
 --- By default asks the server to shutdown, unless stop was requested
 --- already for this client, then force-shutdown is attempted.
 ---
----@param client_id number|table id or |vim.lsp.client| object, or list thereof
+---@param client_id integer|table id or |vim.lsp.client| object, or list thereof
 ---@param force boolean|nil shutdown forcefully
 function lsp.stop_client(client_id, force)
   local ids = type(client_id) == 'table' and client_id or { client_id }
@@ -1837,8 +1837,8 @@ function lsp.stop_client(client_id, force)
 end
 
 ---@class vim.lsp.get_active_clients.filter
----@field id number|nil Match clients by id
----@field bufnr number|nil match clients attached to the given buffer
+---@field id integer|nil Match clients by id
+---@field bufnr integer|nil match clients attached to the given buffer
 ---@field name string|nil match clients by name
 
 --- Get active clients.
@@ -1932,7 +1932,7 @@ api.nvim_create_autocmd('VimLeavePre', {
 --- Sends an async request for all active clients attached to the
 --- buffer.
 ---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@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 function|nil See |lsp-handler|
@@ -1992,7 +1992,7 @@ end
 ---Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are
 ---different.
 ---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@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 callback (function) The callback to call when all requests are finished.
@@ -2034,10 +2034,10 @@ end
 --- Parameters are the same as |vim.lsp.buf_request()| but the return result is
 --- different. Wait maximum of {timeout_ms} (default 1000) ms.
 ---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@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 timeout_ms (number|nil) Maximum time in milliseconds to wait for a
+---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for a
 ---                               result. Defaults to 1000
 ---
 ---@return table|nil result, string|nil err Map of client_id:request_result.
@@ -2106,10 +2106,10 @@ end
 ---@see |complete-items|
 ---@see |CompleteDone|
 ---
----@param findstart number 0 or 1, decides behavior
----@param base number findstart=0, text to match against
+---@param findstart integer 0 or 1, decides behavior
+---@param base integer findstart=0, text to match against
 ---
----@returns (number) Decided by {findstart}:
+---@returns (integer) Decided by {findstart}:
 --- - findstart=0: column where the completion starts, or -2 or -3
 --- - findstart=1: list of matches (actually just calls |complete()|)
 function lsp.omnifunc(findstart, base)
@@ -2246,7 +2246,7 @@ end
 
 ---Checks whether a client is stopped.
 ---
----@param client_id (number)
+---@param client_id (integer)
 ---@return boolean stopped true if client is stopped, false otherwise.
 function lsp.client_is_stopped(client_id)
   return active_clients[client_id] == nil
@@ -2255,7 +2255,7 @@ end
 --- Gets a map of client_id:client pairs for the given buffer, where each value
 --- is a |vim.lsp.client| object.
 ---
----@param bufnr (number|nil): Buffer handle, or 0 for current
+---@param bufnr (integer|nil): Buffer handle, or 0 for current
 ---@returns (table) Table of (client_id, client) pairs
 ---@deprecated Use |vim.lsp.get_active_clients()| instead.
 function lsp.buf_get_clients(bufnr)
@@ -2284,7 +2284,7 @@ lsp.log_levels = log.levels
 ---
 ---@see |vim.lsp.log_levels|
 ---
----@param level (number|string) the case insensitive level name or number
+---@param level (integer|string) the case insensitive level name or number
 function lsp.set_log_level(level)
   if type(level) == 'string' or type(level) == 'number' then
     log.set_level(level)
@@ -2301,7 +2301,7 @@ end
 
 --- Invokes a function for each LSP client attached to a buffer.
 ---
----@param bufnr number Buffer number
+---@param bufnr integer Buffer number
 ---@param fn function Function to run on each client attached to buffer
 ---                   {bufnr}. The function takes the client, client ID, and
 ---                   buffer number as arguments. Example:
diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua
index 3488639fb4..797d8960d5 100644
--- a/runtime/lua/vim/lsp/_snippet.lua
+++ b/runtime/lua/vim/lsp/_snippet.lua
@@ -483,7 +483,7 @@ end)
 local M = {}
 
 ---The snippet node type enum
----@types table
+---@types table
 M.NodeType = Node.Type
 
 ---Parse snippet string and returns the AST
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 17489ed84d..81cac6a511 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -61,7 +61,7 @@ end
 
 --- Return all lenses for the given buffer
 ---
----@param bufnr number  Buffer number. 0 can be used for the current buffer.
+---@param bufnr integer  Buffer number. 0 can be used for the current buffer.
 ---@return table (`CodeLens[]`)
 function M.get(bufnr)
   local lenses_by_client = lens_cache_by_buf[bufnr or 0]
@@ -115,8 +115,8 @@ end
 
 --- Clear the lenses
 ---
----@param client_id number|nil filter by client_id. All clients if nil
----@param bufnr number|nil filter by buffer. All buffers if nil
+---@param client_id integer|nil filter by client_id. All clients if nil
+---@param bufnr integer|nil filter by buffer. All buffers if nil
 function M.clear(client_id, bufnr)
   local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf)
   for _, iter_bufnr in pairs(buffers) do
@@ -132,8 +132,8 @@ end
 --- Display the lenses using virtual text
 ---
 ---@param lenses table of lenses to display (`CodeLens[] | null`)
----@param bufnr number
----@param client_id number
+---@param bufnr integer
+---@param client_id integer
 function M.display(lenses, bufnr, client_id)
   local ns = namespaces[client_id]
   if not lenses or not next(lenses) then
@@ -177,8 +177,8 @@ end
 --- Store lenses for a specific buffer and client
 ---
 ---@param lenses table of lenses to store (`CodeLens[] | null`)
----@param bufnr number
----@param client_id number
+---@param bufnr integer
+---@param client_id integer
 function M.save(lenses, bufnr, client_id)
   local lenses_by_client = lens_cache_by_buf[bufnr]
   if not lenses_by_client then
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 5e2bf75f1b..b27bf6e425 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -4,7 +4,7 @@
 ---@field range Range
 ---@field message string
 ---@field severity DiagnosticSeverity|nil
----@field code number | string
+---@field code integer | string
 ---@field source string
 ---@field tags DiagnosticTag[]
 ---@field relatedInformation DiagnosticRelatedInformation[]
@@ -135,7 +135,7 @@ local _client_namespaces = {}
 
 --- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|.
 ---
----@param client_id number The id of the LSP client
+---@param client_id integer The id of the LSP client
 function M.get_namespace(client_id)
   vim.validate({ client_id = { client_id, 'n' } })
   if not _client_namespaces[client_id] then
@@ -212,7 +212,7 @@ end
 --- this method signature is still used internally in some parts of the LSP
 --- implementation so it's simply marked @private rather than @deprecated.
 ---
----@param client_id number
+---@param client_id integer
 ---@param buffer_client_map table map of buffers to active clients
 ---@private
 function M.reset(client_id, buffer_client_map)
@@ -232,14 +232,14 @@ end
 --- Marked private as this is used internally by the LSP subsystem, but
 --- most users should instead prefer |vim.diagnostic.get()|.
 ---
----@param bufnr number|nil The buffer number
----@param line_nr number|nil The line number
+---@param bufnr integer|nil The buffer number
+---@param line_nr integer|nil The line number
 ---@param opts table|nil Configuration keys
 ---         - severity: (DiagnosticSeverity, default nil)
 ---             - Only return diagnostics with this severity. Overrides severity_limit
 ---         - severity_limit: (DiagnosticSeverity, default nil)
 ---             - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
----@param client_id|nil number the client id
+---@param client_id integer|nil the client id
 ---@return table Table with map of line number to list of diagnostics.
 ---              Structured: { [1] = {...}, [5] = {.... } }
 ---@private
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index d1a78572aa..51dcb7d21d 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -141,7 +141,7 @@ end
 vim.tbl_add_reverse_lookup(log.levels)
 
 --- Sets the current log level.
----@param level (string|number) One of `vim.lsp.log.levels`
+---@param level (string|integer) One of `vim.lsp.log.levels`
 function log.set_level(level)
   if type(level) == 'string' then
     current_log_level =
@@ -167,7 +167,7 @@ function log.set_format_func(handle)
 end
 
 --- Checks whether the level is sufficient for logging.
----@param level number log level
+---@param level integer log level
 ---@returns (bool) true if would log, false if not
 function log.should_log(level)
   return level >= current_log_level
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index aa833deb99..30b61d01d6 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -186,7 +186,7 @@ end
 
 --- Creates an RPC response object/table.
 ---
----@param code number RPC error code defined in `vim.lsp.protocol.ErrorCodes`
+---@param code integer RPC error code defined in `vim.lsp.protocol.ErrorCodes`
 ---@param message string|nil arbitrary message to send to server
 ---@param data any|nil arbitrary data to send to server
 local function rpc_response_error(code, message, data)
@@ -224,8 +224,8 @@ end
 ---@private
 --- Default dispatcher for when a client exits.
 ---
----@param code (number): Exit code
----@param signal (number): Number describing the signal used to terminate (if
+---@param code (integer): Exit code
+---@param signal (integer): Number describing the signal used to terminate (if
 ---any)
 function default_dispatchers.on_exit(code, signal)
   local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
@@ -233,7 +233,7 @@ end
 ---@private
 --- Default dispatcher for client errors.
 ---
----@param code (number): Error code
+---@param code (integer): Error code
 ---@param err (any): Details about the error
 ---any)
 function default_dispatchers.on_error(code, err)
@@ -270,7 +270,7 @@ local function create_read_loop(handle_body, on_no_chunk, on_error)
 end
 
 ---@class RpcClient
----@field message_index number
+---@field message_index integer
 ---@field message_callbacks table
 ---@field notify_reply_callbacks table
 ---@field transport table
@@ -588,7 +588,7 @@ end
 --- and port
 ---
 ---@param host string
----@param port number
+---@param port integer
 ---@return function
 local function connect(host, port)
   return function(dispatchers)
@@ -692,8 +692,8 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
 
   ---@private
   --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher.
-  ---@param code (number) Exit code
-  ---@param signal (number) Signal that was used to terminate (if any)
+  ---@param code (integer) Exit code
+  ---@param signal (integer) Signal that was used to terminate (if any)
   local function onexit(code, signal)
     stdin:close()
     stdout:close()
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 7983d066b8..9eaccd539f 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -4,36 +4,36 @@ local util = require('vim.lsp.util')
 local bit = require('bit')
 
 --- @class STTokenRange
---- @field line number line number 0-based
---- @field start_col number start column 0-based
---- @field end_col number end column 0-based
+--- @field line integer line number 0-based
+--- @field start_col integer start column 0-based
+--- @field end_col integer end column 0-based
 --- @field type string token type as string
 --- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true }
 --- @field marked boolean whether this token has had extmarks applied
 ---
 --- @class STCurrentResult
---- @field version number document version associated with this result
+--- @field version integer document version associated with this result
 --- @field result_id string resultId from the server; used with delta requests
 --- @field highlights STTokenRange[] cache of highlight ranges for this document version
---- @field tokens number[] raw token array as received by the server. used for calculating delta responses
+--- @field tokens integer[] raw token array as received by the server. used for calculating delta responses
 --- @field namespace_cleared boolean whether the namespace was cleared for this result yet
 ---
 --- @class STActiveRequest
---- @field request_id number the LSP request ID of the most recent request sent to the server
---- @field version number the document version associated with the most recent request
+--- @field request_id integer the LSP request ID of the most recent request sent to the server
+--- @field version integer the document version associated with the most recent request
 ---
 --- @class STClientState
---- @field namespace number
+--- @field namespace integer
 --- @field active_request STActiveRequest
 --- @field current_result STCurrentResult
 
 ---@class STHighlighter
----@field active table
----@field bufnr number
----@field augroup number augroup for buffer events
----@field debounce number milliseconds to debounce requests for new tokens
+---@field active table
+---@field bufnr integer
+---@field augroup integer augroup for buffer events
+---@field debounce integer milliseconds to debounce requests for new tokens
 ---@field timer table uv_timer for debouncing requests for new tokens
----@field client_state table
+---@field client_state table
 local STHighlighter = { active = {} }
 
 --- Do a binary search of the tokens in the half-open range [lo, hi).
@@ -141,7 +141,7 @@ end
 --- Construct a new STHighlighter for the buffer
 ---
 ---@private
----@param bufnr number
+---@param bufnr integer
 function STHighlighter.new(bufnr)
   local self = setmetatable({}, { __index = STHighlighter })
 
@@ -470,7 +470,7 @@ end
 --- in case the server supports delta requests.
 ---
 ---@private
----@param client_id number
+---@param client_id integer
 function STHighlighter:mark_dirty(client_id)
   local state = self.client_state[client_id]
   assert(state)
@@ -529,10 +529,10 @@ local M = {}
 ---   client.server_capabilities.semanticTokensProvider = nil
 --- 
--- ----@param bufnr number ----@param client_id number +---@param bufnr integer +---@param client_id integer ---@param opts (nil|table) Optional keyword arguments ---- - debounce (number, default: 200): Debounce token requests +--- - debounce (integer, default: 200): Debounce token requests --- to the server by the given number in milliseconds function M.start(bufnr, client_id, opts) vim.validate({ @@ -585,8 +585,8 @@ 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 number ----@param client_id number +---@param bufnr integer +---@param client_id integer function M.stop(bufnr, client_id) vim.validate({ bufnr = { bufnr, 'n', false }, @@ -608,15 +608,15 @@ end --- Return the semantic token(s) at the given position. --- If called without arguments, returns the token under the cursor. --- ----@param bufnr number|nil Buffer number (0 for current buffer, default) ----@param row number|nil Position row (default cursor position) ----@param col number|nil Position column (default cursor position) +---@param bufnr integer|nil Buffer number (0 for current buffer, default) +---@param row integer|nil Position row (default cursor position) +---@param col integer|nil Position column (default cursor position) --- ---@return table|nil (table|nil) List of tokens at position. Each token has --- the following fields: ---- - line (number) line number, 0-based ---- - start_col (number) start column, 0-based ---- - end_col (number) end column, 0-based +--- - line (integer) line number, 0-based +--- - start_col (integer) start column, 0-based +--- - end_col (integer) end column, 0-based --- - type (string) token type as string, e.g. "variable" --- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true } function M.get_at_pos(bufnr, row, col) @@ -661,7 +661,7 @@ end --- Only has an effect if the buffer is currently active for semantic token --- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it) --- ----@param bufnr (number|nil) filter by buffer. All buffers if nil, current +---@param bufnr (integer|nil) filter by buffer. All buffers if nil, current --- buffer if 0 function M.force_refresh(bufnr) vim.validate({ @@ -689,11 +689,11 @@ end --- use inside |LspTokenUpdate| callbacks. ---@param token (table) a semantic token, found as `args.data.token` in --- |LspTokenUpdate|. ----@param bufnr (number) the buffer to highlight ----@param client_id (number) The ID of the |vim.lsp.client| +---@param bufnr (integer) the buffer to highlight +---@param client_id (integer) The ID of the |vim.lsp.client| ---@param hl_group (string) Highlight group name ---@param opts (table|nil) Optional parameters. ---- - priority: (number|nil) Priority for the applied extmark. Defaults +--- - priority: (integer|nil) Priority for the applied extmark. Defaults --- to `vim.highlight.priorities.semantic_tokens + 3` function M.highlight_token(token, bufnr, client_id, hl_group, opts) local highlighter = STHighlighter.active[bufnr] diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index 826352f036..fb5b0b3194 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -388,9 +388,9 @@ end --- Returns the range table for the difference between prev and curr lines ---@param prev_lines table list of lines ---@param curr_lines table list of lines ----@param firstline number line to begin search for first difference ----@param lastline number line to begin search in old_lines for last difference ----@param new_lastline number line to begin search in new_lines for last difference +---@param firstline integer line to begin search for first difference +---@param lastline integer line to begin search in old_lines for last difference +---@param new_lastline integer line to begin search in new_lines for last difference ---@param offset_encoding string encoding requested by language server ---@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent function M.compute_diff( diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 554e26022c..c9613dc7a7 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -121,9 +121,9 @@ end --- Convert byte index to `encoding` index. --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed ----@param index number|nil byte index (utf-8), or `nil` for length +---@param index integer|nil byte index (utf-8), or `nil` for length ---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 ----@return number `encoding` index of `index` in `line` +---@return integer `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) if not encoding then encoding = 'utf-16' @@ -149,9 +149,9 @@ end --- Convenience wrapper around vim.str_byteindex ---Alternative to vim.str_byteindex that takes an encoding. ---@param line string line to be indexed ----@param index number UTF index +---@param index integer UTF index ---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 ----@return number byte (utf-8) index of `encoding` index `index` in `line` +---@return integer byte (utf-8) index of `encoding` index `index` in `line` function M._str_byteindex_enc(line, index, encoding) if not encoding then encoding = 'utf-16' @@ -239,9 +239,9 @@ end --- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. --- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. --- ----@param bufnr number bufnr to get the lines from ----@param rows number[] zero-indexed line numbers ----@return table a table mapping rows to lines +---@param bufnr integer bufnr to get the lines from +---@param rows integer[] zero-indexed line numbers +---@return table a table mapping rows to lines local function get_lines(bufnr, rows) rows = type(rows) == 'table' and rows or { rows } @@ -321,8 +321,8 @@ end --- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. --- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. --- ----@param bufnr number ----@param row number zero-indexed line number +---@param bufnr integer +---@param row integer zero-indexed line number ---@return string the line at row in filename local function get_line(bufnr, row) return get_lines(bufnr, { row })[row] @@ -386,7 +386,7 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits table list of `TextEdit` objects ----@param bufnr number Buffer id +---@param bufnr integer Buffer id ---@param offset_encoding string utf-8|utf-16|utf-32 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit function M.apply_text_edits(text_edits, bufnr, offset_encoding) @@ -571,7 +571,7 @@ end --- document. --- ---@param text_document_edit table: a `TextDocumentEdit` object ----@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) +---@param index integer: Optional index of the edit, if from a list of edits (or nil, if not from a list) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit function M.apply_text_document_edit(text_document_edit, index, offset_encoding) local text_document = text_document_edit.textDocument @@ -1009,11 +1009,11 @@ end --- Creates a table with sensible default options for a floating window. The --- table can be passed to |nvim_open_win()|. --- ----@param width (number) window width (in character cells) ----@param height (number) window height (in character cells) +---@param width (integer) window width (in character cells) +---@param height (integer) window height (in character cells) ---@param opts (table, optional) ---- - offset_x (number) offset to add to `col` ---- - offset_y (number) offset to add to `row` +--- - offset_x (integer) offset to add to `col` +--- - offset_y (integer) offset to add to `row` --- - border (string or table) override `border` --- - focusable (string or table) override `focusable` --- - zindex (string or table) override `zindex`, defaults to 50 @@ -1429,7 +1429,7 @@ end ---@private --- Closes the preview window --- ----@param winnr number window id of preview window +---@param winnr integer window id of preview window ---@param bufnrs table|nil optional list of ignored buffers local function close_preview_window(winnr, bufnrs) vim.schedule(function() @@ -1448,7 +1448,7 @@ end --- Creates autocommands to close a preview window when events happen. --- ---@param events table list of events ----@param winnr number window id of preview window +---@param winnr integer window id of preview window ---@param bufnrs table list of buffers where the preview window will remain visible ---@see |autocmd-events| local function close_preview_autocmd(events, winnr, bufnrs) @@ -1556,14 +1556,14 @@ end ---@param contents table of lines to show in window ---@param syntax string of syntax to set for opened buffer ---@param opts table with optional fields (additional keys are passed on to |nvim_open_win()|) ---- - height: (number) height of floating window ---- - width: (number) width of floating window +--- - height: (integer) height of floating window +--- - width: (integer) width of floating window --- - wrap: (boolean, default true) wrap long lines ---- - wrap_at: (number) character to wrap at for computing height when wrap is enabled ---- - max_width: (number) maximal width of floating window ---- - max_height: (number) maximal height of floating window ---- - pad_top: (number) number of lines to pad contents at top ---- - pad_bottom: (number) number of lines to pad contents at bottom +--- - wrap_at: (integer) character to wrap at for computing height when wrap is enabled +--- - max_width: (integer) maximal width of floating window +--- - max_height: (integer) maximal height of floating window +--- - pad_top: (integer) number of lines to pad contents at top +--- - pad_bottom: (integer) number of lines to pad contents at bottom --- - focus_id: (string) if a popup with this id is opened, then focus it --- - close_events: (table) list of events that closes the floating window --- - focusable: (boolean, default true) Make float focusable @@ -1672,7 +1672,7 @@ do --[[ References ]] --- Removes document highlights from a buffer. --- - ---@param bufnr number Buffer id + ---@param bufnr integer Buffer id function M.buf_clear_references(bufnr) validate({ bufnr = { bufnr, 'n', true } }) api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1) @@ -1680,7 +1680,7 @@ do --[[ References ]] --- Shows a list of document highlights for a certain buffer. --- - ---@param bufnr number Buffer id + ---@param bufnr integer Buffer id ---@param references table List of `DocumentHighlight` objects to highlight ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent @@ -1893,7 +1893,7 @@ function M.try_trim_markdown_code_blocks(lines) end ---@private ----@param window number|nil: window handle or 0 for current, defaults to current +---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` local function make_position_param(window, offset_encoding) window = window or 0 @@ -1913,7 +1913,7 @@ end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- ----@param window number|nil: window handle or 0 for current, defaults to current +---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns `TextDocumentPositionParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams @@ -1928,7 +1928,7 @@ function M.make_position_params(window, offset_encoding) end --- Utility function for getting the encoding of the first LSP client on the given buffer. ----@param bufnr (number) buffer handle or 0 for current, defaults to current +---@param bufnr (integer) buffer handle or 0 for current, defaults to current ---@returns (string) encoding first client if there is one, nil otherwise function M._get_offset_encoding(bufnr) validate({ @@ -1966,7 +1966,7 @@ end --- `textDocument/codeAction`, `textDocument/colorPresentation`, --- `textDocument/rangeFormatting`. --- ----@param window number|nil: window handle or 0 for current, defaults to current +---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`current_position`, end = `current_position` } } @@ -1983,11 +1983,11 @@ end --- Using the given range in the current buffer, creates an object that --- is similar to |vim.lsp.util.make_range_params()|. --- ----@param start_pos number[]|nil {row, col} mark-indexed position. +---@param start_pos integer[]|nil {row, col} mark-indexed position. --- Defaults to the start of the last visual selection. ----@param end_pos number[]|nil {row, col} mark-indexed position. +---@param end_pos integer[]|nil {row, col} mark-indexed position. --- Defaults to the end of the last visual selection. ----@param bufnr number|nil buffer handle or 0 for current, defaults to current +---@param bufnr integer|nil buffer handle or 0 for current, defaults to current ---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of `bufnr` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`start_position`, end = `end_position` } } @@ -2028,7 +2028,7 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- ----@param bufnr number|nil: Buffer handle, defaults to current +---@param bufnr integer|nil: Buffer handle, defaults to current ---@returns `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) @@ -2049,8 +2049,8 @@ end --- Returns indentation size. --- ---@see 'shiftwidth' ----@param bufnr (number|nil): Buffer handle, defaults to current ----@returns (number) indentation size +---@param bufnr (integer|nil): Buffer handle, defaults to current +---@returns (integer) indentation size function M.get_effective_tabstop(bufnr) validate({ bufnr = { bufnr, 'n', true } }) local bo = bufnr and vim.bo[bufnr] or vim.bo @@ -2077,11 +2077,11 @@ end --- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. --- ----@param buf number buffer number (0 for current) +---@param buf integer buffer number (0 for current) ---@param row 0-indexed line ---@param col 0-indexed byte offset in line ---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `buf` ----@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} +---@returns (integer, integer) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col, offset_encoding) local line = get_line(buf, row) if offset_encoding == nil then -- cgit From 4385f8a7430f8181189f385a6cfb4e295d30b21e Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Tue, 7 Mar 2023 10:35:12 -0600 Subject: fix(lsp): change LspTokenUpdate to use buffer instead of pattern (#22559) --- runtime/lua/vim/lsp/semantic_tokens.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 9eaccd539f..a5e007a011 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -435,7 +435,7 @@ function STHighlighter:on_win(topline, botline) token.marked = true api.nvim_exec_autocmds('LspTokenUpdate', { - pattern = vim.api.nvim_buf_get_name(self.bufnr), + buffer = self.bufnr, modeline = false, data = { token = token, -- cgit From ddd257f75301a50c177fc24a693d39a45b47a689 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 11:03:11 +0000 Subject: feat(treesitter): use upstream format for injection queries --- runtime/lua/vim/treesitter/_meta.lua | 8 +- runtime/lua/vim/treesitter/languagetree.lua | 240 ++++++++++++++++++++-------- runtime/lua/vim/treesitter/query.lua | 21 +-- 3 files changed, 181 insertions(+), 88 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 731a5ebf9f..ad0854706b 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -14,7 +14,7 @@ ---@field child_count fun(self: TSNode): integer ---@field named_child_count fun(self: TSNode): integer ---@field child fun(self: TSNode, integer): TSNode ----@field name_child fun(self: TSNode, integer): TSNode +---@field named_child fun(self: TSNode, integer): TSNode ---@field descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode ---@field named_descendant_for_range fun(self: TSNode, integer, integer, integer, integer): TSNode ---@field parent fun(self: TSNode): TSNode @@ -43,10 +43,10 @@ function TSNode:_rawquery(query, captures, start, end_) end function TSNode:_rawquery(query, captures, start, end_) end ---@class TSParser ----@field parse fun(self: TSParser, tree, source: integer|string): TSTree, integer[] +---@field parse fun(self: TSParser, tree, source: integer|string): TSTree, Range4[] ---@field reset fun(self: TSParser) ----@field included_ranges fun(self: TSParser): integer[] ----@field set_included_ranges fun(self: TSParser, ranges: integer[][]) +---@field included_ranges fun(self: TSParser): Range4[] +---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b8b0dd867e..fbc602486b 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -399,6 +399,169 @@ local function get_range_from_metadata(node, id, metadata) return { node:range() } end +---@private +--- TODO(lewis6991): cleanup of the node_range interface +---@param node TSNode +---@param id integer +---@param metadata TSMetadata +---@return Range4[] +local function get_node_ranges(node, id, metadata, include_children) + local range = get_range_from_metadata(node, id, metadata) + + if include_children then + return { range } + end + + local ranges = {} ---@type Range4[] + + local srow, scol, erow, ecol = range[1], range[2], range[3], range[4] + + for i = 0, node:named_child_count() - 1 do + local child = node:named_child(i) + local child_srow, child_scol, child_erow, child_ecol = child:range() + if child_srow > srow or child_scol > scol then + table.insert(ranges, { srow, scol, child_srow, child_scol }) + end + srow = child_erow + scol = child_ecol + end + + if erow > srow or ecol > scol then + table.insert(ranges, { srow, scol, erow, ecol }) + end + + return ranges +end + +---@alias TSInjection table> + +---@private +---@param t table +---@param tree_index integer +---@param pattern integer +---@param lang string +---@param combined boolean +---@param ranges Range4[] +local function add_injection(t, tree_index, pattern, lang, combined, ranges) + assert(type(lang) == 'string') + + -- Each tree index should be isolated from the other nodes. + if not t[tree_index] then + t[tree_index] = {} + end + + if not t[tree_index][lang] then + t[tree_index][lang] = {} + end + + -- Key this by pattern. If combined is set to true all captures of this pattern + -- will be parsed by treesitter as the same "source". + -- If combined is false, each "region" will be parsed as a single source. + if not t[tree_index][lang][pattern] then + t[tree_index][lang][pattern] = { combined = combined, regions = {} } + end + + table.insert(t[tree_index][lang][pattern].regions, ranges) +end + +---@private +---Get node text +--- +---Note: `query.get_node_text` returns string|string[]|nil so use this simple alias function +---to annotate it returns string. +--- +---TODO(lewis6991): use [at]overload annotations on `query.get_node_text` +---@param node TSNode +---@param source integer|string +---@param metadata table +---@return string +local function get_node_text(node, source, metadata) + return query.get_node_text(node, source, { metadata = metadata }) --[[@as string]] +end + +---@private +--- Extract injections according to: +--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection +---@param match table +---@param metadata table +---@return string, boolean, Range4[] +function LanguageTree:_get_injection(match, metadata) + local ranges = {} ---@type Range4[] + local combined = metadata['injection.combined'] ~= nil + local lang = metadata['injection.language'] ---@type string + local include_children = metadata['injection.include-children'] ~= nil + + for id, node in pairs(match) do + local name = self._injection_query.captures[id] + + -- Lang should override any other language tag + if name == 'injection.language' then + lang = get_node_text(node, self._source, metadata[id]) + elseif name == 'injection.content' then + ranges = get_node_ranges(node, id, metadata, include_children) + end + end + + return lang, combined, ranges +end + +---@private +---@param match table +---@param metadata table +---@return string, boolean, Range4[] +function LanguageTree:_get_injection_deprecated(match, metadata) + local lang = nil ---@type string + local ranges = {} ---@type Range4[] + local combined = metadata.combined ~= nil + + -- Directives can configure how injections are captured as well as actual node captures. + -- This allows more advanced processing for determining ranges and language resolution. + if metadata.content then + local content = metadata.content ---@type any + + -- Allow for captured nodes to be used + if type(content) == 'number' then + content = { match[content]:range() } + end + + if type(content) == 'table' and #content >= 4 then + vim.list_extend(ranges, content) + end + end + + if metadata.language then + lang = metadata.language ---@type string + end + + -- You can specify the content and language together + -- using a tag with the language, for example + -- @javascript + for id, node in pairs(match) do + local name = self._injection_query.captures[id] + + -- Lang should override any other language tag + if name == 'language' and not lang then + lang = get_node_text(node, self._source, metadata[id]) + elseif name == 'combined' then + combined = true + elseif name == 'content' and #ranges == 0 then + table.insert(ranges, get_range_from_metadata(node, id, metadata)) + -- Ignore any tags that start with "_" + -- Allows for other tags to be used in matches + elseif string.sub(name, 1, 1) ~= '_' then + if not lang then + lang = name + end + + if #ranges == 0 then + table.insert(ranges, get_range_from_metadata(node, id, metadata)) + end + end + end + + return lang, combined, ranges +end + --- Gets language injection points by language. --- --- This is where most of the injection processing occurs. @@ -406,13 +569,13 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private ----@return table +---@return table function LanguageTree:_get_injections() if not self._injection_query then return {} end - ---@type table>> + ---@type table local injections = {} for tree_index, tree in ipairs(self._trees) do @@ -422,75 +585,12 @@ function LanguageTree:_get_injections() for pattern, match, metadata in self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do - local lang = nil ---@type string - local ranges = {} ---@type Range4[] - local combined = metadata.combined ---@type boolean - - -- Directives can configure how injections are captured as well as actual node captures. - -- This allows more advanced processing for determining ranges and language resolution. - if metadata.content then - local content = metadata.content ---@type any - - -- Allow for captured nodes to be used - if type(content) == 'number' then - content = { match[content]:range() } - end - - if type(content) == 'table' and #content >= 4 then - vim.list_extend(ranges, content) - end - end - - if metadata.language then - lang = metadata.language ---@type string - end - - -- You can specify the content and language together - -- using a tag with the language, for example - -- @javascript - for id, node in pairs(match) do - local name = self._injection_query.captures[id] - - -- Lang should override any other language tag - if name == 'language' and not lang then - ---@diagnostic disable-next-line - lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) - elseif name == 'combined' then - combined = true - elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) - -- Ignore any tags that start with "_" - -- Allows for other tags to be used in matches - elseif string.sub(name, 1, 1) ~= '_' then - if not lang then - lang = name - end - - if #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) - end - end + local lang, combined, ranges = self:_get_injection(match, metadata) + if not lang then + -- TODO(lewis6991): remove after 0.9 (#20434) + lang, combined, ranges = self:_get_injection_deprecated(match, metadata) end - - assert(type(lang) == 'string') - - -- Each tree index should be isolated from the other nodes. - if not injections[tree_index] then - injections[tree_index] = {} - end - - if not injections[tree_index][lang] then - injections[tree_index][lang] = {} - end - - -- Key this by pattern. If combined is set to true all captures of this pattern - -- will be parsed by treesitter as the same "source". - -- If combined is false, each "region" will be parsed as a single source. - if not injections[tree_index][lang][pattern] then - injections[tree_index][lang][pattern] = { combined = combined, regions = {} } - end - - table.insert(injections[tree_index][lang][pattern].regions, ranges) + add_injection(injections, tree_index, pattern, lang, combined, ranges) end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 22f706585e..59894cc7f5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -407,7 +407,7 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@field [string] integer|string ---@field range Range4 ----@alias TSDirective fun(match: TSMatch, _, _, predicate: any[], metadata: TSMetadata) +---@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata) -- Predicate handler receive the following arguments -- (match, pattern, bufnr, predicate) @@ -419,24 +419,17 @@ predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@type table local directive_handlers = { ['set!'] = function(_, _, _, pred, metadata) - if #pred == 4 then - -- (#set! @capture "key" "value") - ---@diagnostic disable-next-line:no-unknown - local _, capture_id, key, value = unpack(pred) - ---@cast value integer|string - ---@cast capture_id integer - ---@cast key string + if #pred >= 3 and type(pred[2]) == 'number' then + -- (#set! @capture key value) + local capture_id, key, value = pred[2], pred[3], pred[4] if not metadata[capture_id] then metadata[capture_id] = {} end metadata[capture_id][key] = value else - ---@diagnostic disable-next-line:no-unknown - local _, key, value = unpack(pred) - ---@cast value integer|string - ---@cast key string - -- (#set! "key" "value") - metadata[key] = value + -- (#set! key value) + local key, value = pred[2], pred[3] + metadata[key] = value or true end end, -- Shifts the range of a node. -- cgit From 276b647fdba07bf1762d8dd371c4b655b8a418df Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 17:22:28 +0000 Subject: refactor(treesitter): delegate region calculation to treesitter (#22553) --- runtime/lua/vim/treesitter/_meta.lua | 7 +- runtime/lua/vim/treesitter/_range.lua | 28 +- runtime/lua/vim/treesitter/languagetree.lua | 410 +++++++++++++++++----------- runtime/lua/vim/treesitter/query.lua | 1 + 4 files changed, 271 insertions(+), 175 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index ad0854706b..72823ccf26 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -3,7 +3,7 @@ ---@class TSNode ---@field id fun(self: TSNode): integer ---@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode): integer, integer, integer, integer +---@field range fun(self: TSNode, include_bytes: boolean?): integer, integer, integer, integer, integer, integer ---@field start fun(self: TSNode): integer, integer, integer ---@field end_ fun(self: TSNode): integer, integer, integer ---@field type fun(self: TSNode): string @@ -43,9 +43,9 @@ function TSNode:_rawquery(query, captures, start, end_) end function TSNode:_rawquery(query, captures, start, end_) end ---@class TSParser ----@field parse fun(self: TSParser, tree, source: integer|string): TSTree, Range4[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] ---@field reset fun(self: TSParser) ----@field included_ranges fun(self: TSParser): Range4[] +---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer @@ -54,6 +54,7 @@ function TSNode:_rawquery(query, captures, start, end_) end ---@field root fun(self: TSTree): TSNode ---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) ---@field copy fun(self: TSTree): TSTree +---@field included_ranges fun(self: TSTree, include_bytes: boolean?): integer[] ---@return integer vim._ts_get_language_version = function() end diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index bec24a23a5..21e46a560a 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -78,11 +78,8 @@ end ---@param r2 Range4|Range6 ---@return boolean function M.intercepts(r1, r2) - local off_1 = #r1 == 6 and 1 or 0 - local off_2 = #r1 == 6 and 1 or 0 - - local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] - local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) + local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) -- r1 is above r2 if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then @@ -97,16 +94,21 @@ function M.intercepts(r1, r2) return true end +---@private +---@param r Range4|Range6 +---@return integer, integer, integer, integer +function M.unpack4(r) + local off_1 = #r == 6 and 1 or 0 + return r[1], r[2], r[3 + off_1], r[4 + off_1] +end + ---@private ---@param r1 Range4|Range6 ---@param r2 Range4|Range6 ---@return boolean whether r1 contains r2 function M.contains(r1, r2) - local off_1 = #r1 == 6 and 1 or 0 - local off_2 = #r1 == 6 and 1 or 0 - - local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] - local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) + local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) -- start doesn't fit if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then @@ -123,9 +125,13 @@ end ---@private ---@param source integer|string ----@param range Range4 +---@param range Range4|Range6 ---@return Range6 function M.add_bytes(source, range) + if type(range) == 'table' and #range == 6 then + return range --[[@as Range6]] + end + local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] local start_byte = 0 local end_byte = 0 diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index fbc602486b..57a60bf774 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -57,13 +57,13 @@ local Range = require('vim.treesitter._range') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language ----@field private _regions Range6[][] List of regions this tree should manage and parse +---@field private _regions Range6[][]? +---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 _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ---@field private _valid boolean|table If the parsed tree is valid ---- TODO(lewis6991): combine _regions, _valid and _trees ----@field private _is_child boolean local LanguageTree = {} ---@class LanguageTreeOpts @@ -98,7 +98,6 @@ function LanguageTree.new(source, lang, opts) _source = source, _lang = lang, _children = {}, - _regions = {}, _trees = {}, _opts = opts, _injection_query = injections[lang] and query.parse_query(lang, injections[lang]) @@ -117,6 +116,48 @@ function LanguageTree.new(source, lang, opts) return self end +---@private +---Measure execution time of a function +---@generic R1, R2, R3 +---@param f fun(): R1, R2, R2 +---@return integer, R1, R2, R3 +local function tcall(f, ...) + local start = vim.loop.hrtime() + ---@diagnostic disable-next-line + local r = { f(...) } + local duration = (vim.loop.hrtime() - start) / 1000000 + return duration, unpack(r) +end + +---@private +---@vararg any +function LanguageTree:_log(...) + if vim.g.__ts_debug == nil then + return + end + + local args = { ... } + if type(args[1]) == 'function' then + args = { args[1]() } + end + + local info = debug.getinfo(2, 'nl') + local nregions = #self:included_regions() + local prefix = + string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) + + a.nvim_out_write(prefix) + for _, x in ipairs(args) do + if type(x) == 'string' then + a.nvim_out_write(x) + else + a.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) + end + a.nvim_out_write(' ') + end + a.nvim_out_write('\n') +end + --- Invalidates this parser and all its children ---@param reload boolean|nil function LanguageTree:invalidate(reload) @@ -146,7 +187,9 @@ end --- Determines whether this tree is valid. --- If the tree is invalid, call `parse()`. --- This will return the updated tree. -function LanguageTree:is_valid() +---@param exclude_children boolean|nil +---@return boolean +function LanguageTree:is_valid(exclude_children) local valid = self._valid if type(valid) == 'table' then @@ -155,9 +198,18 @@ function LanguageTree:is_valid() return false end end - return true end + if not exclude_children then + for _, child in pairs(self._children) do + if not child:is_valid(exclude_children) then + return false + end + end + end + + assert(type(valid) == 'boolean') + return valid end @@ -171,16 +223,6 @@ function LanguageTree:source() return self._source end ----@private ----This is only exposed so it can be wrapped for profiling ----@param old_tree TSTree ----@return TSTree, integer[] -function LanguageTree:_parse_tree(old_tree) - local tree, tree_changes = self._parser:parse(old_tree, self._source) - self:_do_callback('changedtree', tree_changes, tree) - return tree, tree_changes -end - --- Parses all defined regions using a treesitter parser --- for the language this tree represents. --- This will run the injection query for this language to @@ -190,31 +232,39 @@ end ---@return table|nil Change list function LanguageTree:parse() if self:is_valid() then + self:_log('valid') return self._trees end local changes = {} - -- If there are no ranges, set to an empty list - -- so the included ranges in the parser are cleared. - if #self._regions > 0 then - for i, ranges in ipairs(self._regions) do + -- Collect some stats + local regions_parsed = 0 + local total_parse_time = 0 + + --- At least 1 region is invalid + if not self:is_valid(true) then + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser are cleared. + for i, ranges in ipairs(self:included_regions()) do if not self._valid or not self._valid[i] then self._parser:set_included_ranges(ranges) - local tree, tree_changes = self:_parse_tree(self._trees[i]) + local parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[i], self._source) + + self:_do_callback('changedtree', tree_changes, tree) self._trees[i] = tree vim.list_extend(changes, tree_changes) + + total_parse_time = total_parse_time + parse_time + regions_parsed = regions_parsed + 1 end end - else - local tree, tree_changes = self:_parse_tree(self._trees[1]) - self._trees = { tree } - changes = tree_changes end - local injections_by_lang = self:_get_injections() local seen_langs = {} ---@type table + local query_time, injections_by_lang = tcall(self._get_injections, self) for lang, injection_ranges in pairs(injections_by_lang) do local has_lang = pcall(language.add, lang) @@ -229,15 +279,6 @@ function LanguageTree:parse() end child:set_included_regions(injection_ranges) - - local _, child_changes = child:parse() - - -- Propagate any child changes so they are included in the - -- the change list for the callback. - if child_changes then - vim.list_extend(changes, child_changes) - end - seen_langs[lang] = true end end @@ -248,6 +289,23 @@ function LanguageTree:parse() end end + self:_log({ + changes = changes, + regions_parsed = regions_parsed, + parse_time = total_parse_time, + query_time = query_time, + }) + + self:for_each_child(function(child) + local _, child_changes = child:parse() + + -- Propagate any child changes so they are included in the + -- the change list for the callback. + if child_changes then + vim.list_extend(changes, child_changes) + end + end) + self._valid = true return self._trees, changes @@ -295,8 +353,6 @@ function LanguageTree:add_child(lang) end self._children[lang] = LanguageTree.new(self._source, lang, self._opts) - self._children[lang]._is_child = true - self:invalidate() self:_do_callback('child_added', self._children[lang]) @@ -331,6 +387,53 @@ function LanguageTree:destroy() end end +---@private +---@param region Range6[] +local function region_tostr(region) + local srow, scol = region[1][1], region[1][2] + local erow, ecol = region[#region][4], region[#region][5] + return string.format('[%d:%d-%d:%d]', srow, scol, erow, ecol) +end + +---@private +---Sets self._valid properly and efficiently +---@param fn fun(index: integer, region: Range6[]): boolean +function LanguageTree:_validate_regions(fn) + if not self._valid then + return + end + + if type(self._valid) ~= 'table' then + self._valid = {} + end + + local all_valid = true + + for i, region in ipairs(self:included_regions()) do + if self._valid[i] == nil then + self._valid[i] = true + end + + if self._valid[i] then + self._valid[i] = fn(i, region) + if not self._valid[i] then + self:_log(function() + return 'invalidating region', i, region_tostr(region) + end) + end + end + + if not self._valid[i] then + all_valid = false + end + end + + -- Compress the valid value to 'true' if there are no invalid regions + if all_valid then + self._valid = all_valid + end +end + --- Sets the included regions that should be parsed by this |LanguageTree|. --- A region is a set of nodes and/or ranges that will be parsed in the same context. --- @@ -357,56 +460,57 @@ function LanguageTree:set_included_regions(regions) end end - if #self._regions ~= #regions then + if #self:included_regions() ~= #regions then self._trees = {} self:invalidate() - elseif self._valid ~= false then - if self._valid == true then - self._valid = {} - for i = 1, #regions do - self._valid[i] = true - end - end - - for i = 1, #regions do - if not vim.deep_equal(self._regions[i], regions[i]) then - self._valid[i] = false - end - - if not self._valid[i] then - self._trees[i] = nil - end - end + else + self:_validate_regions(function(i, region) + return vim.deep_equal(regions[i], region) + end) end - self._regions = regions end ---- Gets the set of included regions +---Gets the set of included regions +---@return integer[][] function LanguageTree:included_regions() - return self._regions + if self._regions then + return self._regions + end + + if #self._trees == 0 then + return { {} } + end + + local regions = {} ---@type Range6[][] + for i, _ in ipairs(self._trees) do + regions[i] = self._trees[i]:included_ranges(true) + end + + self._regions = regions + return regions end ---@private ---@param node TSNode ----@param id integer +---@param source integer|string ---@param metadata TSMetadata ----@return Range4 -local function get_range_from_metadata(node, id, metadata) - if metadata[id] and metadata[id].range then - return metadata[id].range --[[@as Range4]] +---@return Range6 +local function get_range_from_metadata(node, source, metadata) + if metadata and metadata.range then + return Range.add_bytes(source, metadata.range --[[@as Range4|Range6]]) end - return { node:range() } + return { node:range(true) } end ---@private --- TODO(lewis6991): cleanup of the node_range interface ---@param node TSNode ----@param id integer +---@param source string|integer ---@param metadata TSMetadata ---@return Range4[] -local function get_node_ranges(node, id, metadata, include_children) - local range = get_range_from_metadata(node, id, metadata) +local function get_node_ranges(node, source, metadata, include_children) + local range = get_range_from_metadata(node, source, metadata) if include_children then return { range } @@ -414,7 +518,7 @@ local function get_node_ranges(node, id, metadata, include_children) local ranges = {} ---@type Range4[] - local srow, scol, erow, ecol = range[1], range[2], range[3], range[4] + local srow, scol, erow, ecol = Range.unpack4(range) for i = 0, node:named_child_count() - 1 do local child = node:named_child(i) @@ -498,7 +602,7 @@ function LanguageTree:_get_injection(match, metadata) if name == 'injection.language' then lang = get_node_text(node, self._source, metadata[id]) elseif name == 'injection.content' then - ranges = get_node_ranges(node, id, metadata, include_children) + ranges = get_node_ranges(node, self._source, metadata[id], include_children) end end @@ -545,7 +649,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, self._source, metadata[id])) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -554,7 +658,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end if #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, self._source, metadata[id])) end end end @@ -569,7 +673,7 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private ----@return table +---@return table function LanguageTree:_get_injections() if not self._injection_query then return {} @@ -594,7 +698,7 @@ function LanguageTree:_get_injections() end end - ---@type table + ---@type table local result = {} -- Generate a map by lang of node lists. @@ -634,42 +738,51 @@ function LanguageTree:_do_callback(cb_name, ...) end ---@private ----@param regions Range6[][] ----@param old_range Range6 ----@param new_range Range6 ----@return table region indices to invalidate -local function update_regions(regions, old_range, new_range) - ---@type table - local valid = {} - - for i, ranges in ipairs(regions or {}) do - valid[i] = true - for j, r in ipairs(ranges) do - if Range.intercepts(r, old_range) then - valid[i] = false - break - end +function LanguageTree:_edit( + start_byte, + end_byte_old, + end_byte_new, + start_row, + start_col, + end_row_old, + end_col_old, + end_row_new, + end_col_new +) + for _, tree in ipairs(self._trees) do + tree:edit( + start_byte, + end_byte_old, + end_byte_new, + start_row, + start_col, + end_row_old, + end_col_old, + end_row_new, + end_col_new + ) + end + + self._regions = nil + + local changed_range = { + start_row, + start_col, + start_byte, + end_row_old, + end_col_old, + end_byte_old, + } - -- Range after change. Adjust - if Range.cmp_pos.gt(r[1], r[2], old_range[4], old_range[5]) then - local byte_offset = new_range[6] - old_range[6] - local row_offset = new_range[4] - old_range[4] - - -- Update the range to avoid invalidation in set_included_regions() - -- which will compare the regions against the parsed injection regions - ranges[j] = { - r[1] + row_offset, - r[2], - r[3] + byte_offset, - r[4] + row_offset, - r[5], - r[6] + byte_offset, - } + -- Validate regions after editing the tree + self:_validate_regions(function(_, region) + for _, r in ipairs(region) do + if Range.intercepts(r, changed_range) then + return false end end - end - - return valid + return true + end) end ---@private @@ -700,49 +813,26 @@ function LanguageTree:_on_bytes( local old_end_col = old_col + ((old_row == 0) and start_col or 0) local new_end_col = new_col + ((new_row == 0) and start_col or 0) - local old_range = { - start_row, - start_col, - start_byte, - start_row + old_row, - old_end_col, - start_byte + old_byte, - } - - local new_range = { + self:_log( + 'on_bytes', + bufnr, + changed_tick, start_row, start_col, start_byte, - start_row + new_row, - new_end_col, - start_byte + new_byte, - } - - if #self._regions == 0 then - self._valid = false - else - self._valid = update_regions(self._regions, old_range, new_range) - end - - for _, child in pairs(self._children) do - child:_on_bytes( - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) - end + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) -- Edit trees together BEFORE emitting a bytes callback. - for _, tree in ipairs(self._trees) do - tree:edit( + ---@private + self:for_each_child(function(child) + ---@diagnostic disable-next-line:invisible + child:_edit( start_byte, start_byte + old_byte, start_byte + new_byte, @@ -753,24 +843,22 @@ function LanguageTree:_on_bytes( start_row + new_row, new_end_col ) - end + end, true) - if not self._is_child then - self:_do_callback( - 'bytes', - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) - end + self:_do_callback( + 'bytes', + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) end ---@private diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 59894cc7f5..e7cf42283d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,6 +277,7 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} + -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From b9f19d3e286d95d9209afbc479fa2eb908067fb1 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 17:59:45 +0000 Subject: Revert "refactor(treesitter): delegate region calculation to treesitter" (#22575) Revert "refactor(treesitter): delegate region calculation to treesitter (#22553)" This reverts commit 276b647fdba07bf1762d8dd371c4b655b8a418df. --- runtime/lua/vim/treesitter/_meta.lua | 7 +- runtime/lua/vim/treesitter/_range.lua | 28 +- runtime/lua/vim/treesitter/languagetree.lua | 410 +++++++++++----------------- runtime/lua/vim/treesitter/query.lua | 1 - 4 files changed, 175 insertions(+), 271 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 72823ccf26..ad0854706b 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -3,7 +3,7 @@ ---@class TSNode ---@field id fun(self: TSNode): integer ---@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode, include_bytes: boolean?): integer, integer, integer, integer, integer, integer +---@field range fun(self: TSNode): integer, integer, integer, integer ---@field start fun(self: TSNode): integer, integer, integer ---@field end_ fun(self: TSNode): integer, integer, integer ---@field type fun(self: TSNode): string @@ -43,9 +43,9 @@ function TSNode:_rawquery(query, captures, start, end_) end function TSNode:_rawquery(query, captures, start, end_) end ---@class TSParser ----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] +---@field parse fun(self: TSParser, tree, source: integer|string): TSTree, Range4[] ---@field reset fun(self: TSParser) ----@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] +---@field included_ranges fun(self: TSParser): Range4[] ---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer @@ -54,7 +54,6 @@ function TSNode:_rawquery(query, captures, start, end_) end ---@field root fun(self: TSTree): TSNode ---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) ---@field copy fun(self: TSTree): TSTree ----@field included_ranges fun(self: TSTree, include_bytes: boolean?): integer[] ---@return integer vim._ts_get_language_version = function() end diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 21e46a560a..bec24a23a5 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -78,8 +78,11 @@ end ---@param r2 Range4|Range6 ---@return boolean function M.intercepts(r1, r2) - local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) - local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) + local off_1 = #r1 == 6 and 1 or 0 + local off_2 = #r1 == 6 and 1 or 0 + + local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] + local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] -- r1 is above r2 if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then @@ -94,21 +97,16 @@ function M.intercepts(r1, r2) return true end ----@private ----@param r Range4|Range6 ----@return integer, integer, integer, integer -function M.unpack4(r) - local off_1 = #r == 6 and 1 or 0 - return r[1], r[2], r[3 + off_1], r[4 + off_1] -end - ---@private ---@param r1 Range4|Range6 ---@param r2 Range4|Range6 ---@return boolean whether r1 contains r2 function M.contains(r1, r2) - local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) - local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) + local off_1 = #r1 == 6 and 1 or 0 + local off_2 = #r1 == 6 and 1 or 0 + + local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] + local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] -- start doesn't fit if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then @@ -125,13 +123,9 @@ end ---@private ---@param source integer|string ----@param range Range4|Range6 +---@param range Range4 ---@return Range6 function M.add_bytes(source, range) - if type(range) == 'table' and #range == 6 then - return range --[[@as Range6]] - end - local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] local start_byte = 0 local end_byte = 0 diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 57a60bf774..fbc602486b 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -57,13 +57,13 @@ local Range = require('vim.treesitter._range') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language ----@field private _regions Range6[][]? ----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 _regions Range6[][] List of regions this tree should manage and parse ---@field private _lang string Language name ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ---@field private _valid boolean|table If the parsed tree is valid +--- TODO(lewis6991): combine _regions, _valid and _trees +---@field private _is_child boolean local LanguageTree = {} ---@class LanguageTreeOpts @@ -98,6 +98,7 @@ function LanguageTree.new(source, lang, opts) _source = source, _lang = lang, _children = {}, + _regions = {}, _trees = {}, _opts = opts, _injection_query = injections[lang] and query.parse_query(lang, injections[lang]) @@ -116,48 +117,6 @@ function LanguageTree.new(source, lang, opts) return self end ----@private ----Measure execution time of a function ----@generic R1, R2, R3 ----@param f fun(): R1, R2, R2 ----@return integer, R1, R2, R3 -local function tcall(f, ...) - local start = vim.loop.hrtime() - ---@diagnostic disable-next-line - local r = { f(...) } - local duration = (vim.loop.hrtime() - start) / 1000000 - return duration, unpack(r) -end - ----@private ----@vararg any -function LanguageTree:_log(...) - if vim.g.__ts_debug == nil then - return - end - - local args = { ... } - if type(args[1]) == 'function' then - args = { args[1]() } - end - - local info = debug.getinfo(2, 'nl') - local nregions = #self:included_regions() - local prefix = - string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) - - a.nvim_out_write(prefix) - for _, x in ipairs(args) do - if type(x) == 'string' then - a.nvim_out_write(x) - else - a.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) - end - a.nvim_out_write(' ') - end - a.nvim_out_write('\n') -end - --- Invalidates this parser and all its children ---@param reload boolean|nil function LanguageTree:invalidate(reload) @@ -187,9 +146,7 @@ end --- Determines whether this tree is valid. --- If the tree is invalid, call `parse()`. --- This will return the updated tree. ----@param exclude_children boolean|nil ----@return boolean -function LanguageTree:is_valid(exclude_children) +function LanguageTree:is_valid() local valid = self._valid if type(valid) == 'table' then @@ -198,18 +155,9 @@ function LanguageTree:is_valid(exclude_children) return false end end + return true end - if not exclude_children then - for _, child in pairs(self._children) do - if not child:is_valid(exclude_children) then - return false - end - end - end - - assert(type(valid) == 'boolean') - return valid end @@ -223,6 +171,16 @@ function LanguageTree:source() return self._source end +---@private +---This is only exposed so it can be wrapped for profiling +---@param old_tree TSTree +---@return TSTree, integer[] +function LanguageTree:_parse_tree(old_tree) + local tree, tree_changes = self._parser:parse(old_tree, self._source) + self:_do_callback('changedtree', tree_changes, tree) + return tree, tree_changes +end + --- Parses all defined regions using a treesitter parser --- for the language this tree represents. --- This will run the injection query for this language to @@ -232,39 +190,31 @@ end ---@return table|nil Change list function LanguageTree:parse() if self:is_valid() then - self:_log('valid') return self._trees end local changes = {} - -- Collect some stats - local regions_parsed = 0 - local total_parse_time = 0 - - --- At least 1 region is invalid - if not self:is_valid(true) then - -- If there are no ranges, set to an empty list - -- so the included ranges in the parser are cleared. - for i, ranges in ipairs(self:included_regions()) do + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser are cleared. + if #self._regions > 0 then + for i, ranges in ipairs(self._regions) do if not self._valid or not self._valid[i] then self._parser:set_included_ranges(ranges) - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source) - - self:_do_callback('changedtree', tree_changes, tree) + local tree, tree_changes = self:_parse_tree(self._trees[i]) self._trees[i] = tree vim.list_extend(changes, tree_changes) - - total_parse_time = total_parse_time + parse_time - regions_parsed = regions_parsed + 1 end end + else + local tree, tree_changes = self:_parse_tree(self._trees[1]) + self._trees = { tree } + changes = tree_changes end + local injections_by_lang = self:_get_injections() local seen_langs = {} ---@type table - local query_time, injections_by_lang = tcall(self._get_injections, self) for lang, injection_ranges in pairs(injections_by_lang) do local has_lang = pcall(language.add, lang) @@ -279,6 +229,15 @@ function LanguageTree:parse() end child:set_included_regions(injection_ranges) + + local _, child_changes = child:parse() + + -- Propagate any child changes so they are included in the + -- the change list for the callback. + if child_changes then + vim.list_extend(changes, child_changes) + end + seen_langs[lang] = true end end @@ -289,23 +248,6 @@ function LanguageTree:parse() end end - self:_log({ - changes = changes, - regions_parsed = regions_parsed, - parse_time = total_parse_time, - query_time = query_time, - }) - - self:for_each_child(function(child) - local _, child_changes = child:parse() - - -- Propagate any child changes so they are included in the - -- the change list for the callback. - if child_changes then - vim.list_extend(changes, child_changes) - end - end) - self._valid = true return self._trees, changes @@ -353,6 +295,8 @@ function LanguageTree:add_child(lang) end self._children[lang] = LanguageTree.new(self._source, lang, self._opts) + self._children[lang]._is_child = true + self:invalidate() self:_do_callback('child_added', self._children[lang]) @@ -387,53 +331,6 @@ function LanguageTree:destroy() end end ----@private ----@param region Range6[] -local function region_tostr(region) - local srow, scol = region[1][1], region[1][2] - local erow, ecol = region[#region][4], region[#region][5] - return string.format('[%d:%d-%d:%d]', srow, scol, erow, ecol) -end - ----@private ----Sets self._valid properly and efficiently ----@param fn fun(index: integer, region: Range6[]): boolean -function LanguageTree:_validate_regions(fn) - if not self._valid then - return - end - - if type(self._valid) ~= 'table' then - self._valid = {} - end - - local all_valid = true - - for i, region in ipairs(self:included_regions()) do - if self._valid[i] == nil then - self._valid[i] = true - end - - if self._valid[i] then - self._valid[i] = fn(i, region) - if not self._valid[i] then - self:_log(function() - return 'invalidating region', i, region_tostr(region) - end) - end - end - - if not self._valid[i] then - all_valid = false - end - end - - -- Compress the valid value to 'true' if there are no invalid regions - if all_valid then - self._valid = all_valid - end -end - --- Sets the included regions that should be parsed by this |LanguageTree|. --- A region is a set of nodes and/or ranges that will be parsed in the same context. --- @@ -460,57 +357,56 @@ function LanguageTree:set_included_regions(regions) end end - if #self:included_regions() ~= #regions then + if #self._regions ~= #regions then self._trees = {} self:invalidate() - else - self:_validate_regions(function(i, region) - return vim.deep_equal(regions[i], region) - end) - end - self._regions = regions -end - ----Gets the set of included regions ----@return integer[][] -function LanguageTree:included_regions() - if self._regions then - return self._regions - end + elseif self._valid ~= false then + if self._valid == true then + self._valid = {} + for i = 1, #regions do + self._valid[i] = true + end + end - if #self._trees == 0 then - return { {} } - end + for i = 1, #regions do + if not vim.deep_equal(self._regions[i], regions[i]) then + self._valid[i] = false + end - local regions = {} ---@type Range6[][] - for i, _ in ipairs(self._trees) do - regions[i] = self._trees[i]:included_ranges(true) + if not self._valid[i] then + self._trees[i] = nil + end + end end self._regions = regions - return regions +end + +--- Gets the set of included regions +function LanguageTree:included_regions() + return self._regions end ---@private ---@param node TSNode ----@param source integer|string +---@param id integer ---@param metadata TSMetadata ----@return Range6 -local function get_range_from_metadata(node, source, metadata) - if metadata and metadata.range then - return Range.add_bytes(source, metadata.range --[[@as Range4|Range6]]) +---@return Range4 +local function get_range_from_metadata(node, id, metadata) + if metadata[id] and metadata[id].range then + return metadata[id].range --[[@as Range4]] end - return { node:range(true) } + return { node:range() } end ---@private --- TODO(lewis6991): cleanup of the node_range interface ---@param node TSNode ----@param source string|integer +---@param id integer ---@param metadata TSMetadata ---@return Range4[] -local function get_node_ranges(node, source, metadata, include_children) - local range = get_range_from_metadata(node, source, metadata) +local function get_node_ranges(node, id, metadata, include_children) + local range = get_range_from_metadata(node, id, metadata) if include_children then return { range } @@ -518,7 +414,7 @@ local function get_node_ranges(node, source, metadata, include_children) local ranges = {} ---@type Range4[] - local srow, scol, erow, ecol = Range.unpack4(range) + local srow, scol, erow, ecol = range[1], range[2], range[3], range[4] for i = 0, node:named_child_count() - 1 do local child = node:named_child(i) @@ -602,7 +498,7 @@ function LanguageTree:_get_injection(match, metadata) if name == 'injection.language' then lang = get_node_text(node, self._source, metadata[id]) elseif name == 'injection.content' then - ranges = get_node_ranges(node, self._source, metadata[id], include_children) + ranges = get_node_ranges(node, id, metadata, include_children) end end @@ -649,7 +545,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, self._source, metadata[id])) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -658,7 +554,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end if #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, self._source, metadata[id])) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) end end end @@ -673,7 +569,7 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private ----@return table +---@return table function LanguageTree:_get_injections() if not self._injection_query then return {} @@ -698,7 +594,7 @@ function LanguageTree:_get_injections() end end - ---@type table + ---@type table local result = {} -- Generate a map by lang of node lists. @@ -738,51 +634,42 @@ function LanguageTree:_do_callback(cb_name, ...) end ---@private -function LanguageTree:_edit( - start_byte, - end_byte_old, - end_byte_new, - start_row, - start_col, - end_row_old, - end_col_old, - end_row_new, - end_col_new -) - for _, tree in ipairs(self._trees) do - tree:edit( - start_byte, - end_byte_old, - end_byte_new, - start_row, - start_col, - end_row_old, - end_col_old, - end_row_new, - end_col_new - ) - end - - self._regions = nil - - local changed_range = { - start_row, - start_col, - start_byte, - end_row_old, - end_col_old, - end_byte_old, - } +---@param regions Range6[][] +---@param old_range Range6 +---@param new_range Range6 +---@return table region indices to invalidate +local function update_regions(regions, old_range, new_range) + ---@type table + local valid = {} + + for i, ranges in ipairs(regions or {}) do + valid[i] = true + for j, r in ipairs(ranges) do + if Range.intercepts(r, old_range) then + valid[i] = false + break + end - -- Validate regions after editing the tree - self:_validate_regions(function(_, region) - for _, r in ipairs(region) do - if Range.intercepts(r, changed_range) then - return false + -- Range after change. Adjust + if Range.cmp_pos.gt(r[1], r[2], old_range[4], old_range[5]) then + local byte_offset = new_range[6] - old_range[6] + local row_offset = new_range[4] - old_range[4] + + -- Update the range to avoid invalidation in set_included_regions() + -- which will compare the regions against the parsed injection regions + ranges[j] = { + r[1] + row_offset, + r[2], + r[3] + byte_offset, + r[4] + row_offset, + r[5], + r[6] + byte_offset, + } end end - return true - end) + end + + return valid end ---@private @@ -813,26 +700,49 @@ function LanguageTree:_on_bytes( local old_end_col = old_col + ((old_row == 0) and start_col or 0) local new_end_col = new_col + ((new_row == 0) and start_col or 0) - self:_log( - 'on_bytes', - bufnr, - changed_tick, + local old_range = { + start_row, + start_col, + start_byte, + start_row + old_row, + old_end_col, + start_byte + old_byte, + } + + local new_range = { start_row, start_col, start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) + start_row + new_row, + new_end_col, + start_byte + new_byte, + } + + if #self._regions == 0 then + self._valid = false + else + self._valid = update_regions(self._regions, old_range, new_range) + end + + for _, child in pairs(self._children) do + child:_on_bytes( + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) + end -- Edit trees together BEFORE emitting a bytes callback. - ---@private - self:for_each_child(function(child) - ---@diagnostic disable-next-line:invisible - child:_edit( + for _, tree in ipairs(self._trees) do + tree:edit( start_byte, start_byte + old_byte, start_byte + new_byte, @@ -843,22 +753,24 @@ function LanguageTree:_on_bytes( start_row + new_row, new_end_col ) - end, true) + end - self:_do_callback( - 'bytes', - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) + if not self._is_child then + self:_do_callback( + 'bytes', + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) + end end ---@private diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e7cf42283d..59894cc7f5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,7 +277,6 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} - -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From 6dd9770baf16abfedba667e93d92899602864645 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Wed, 8 Mar 2023 18:19:10 -0500 Subject: vim-patch:9.0.1394: Unx Tal files are not recognized (#22579) Problem: Unx Tal files are not recognized. Solution: Add a pattern for Unx Tal files. (Amaan Qureshi, closes vim/vim#12117) https://github.com/vim/vim/commit/cde1f8714ed2c046aa770c46229e781380122bd7 --- 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 65579d3543..33324f580a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1015,6 +1015,7 @@ local extension = { svh = 'systemverilog', sv = 'systemverilog', tak = 'tak', + tal = 'tal', task = 'taskedit', tm = 'tcl', tcl = 'tcl', -- cgit From ae70e946eeaec792c9a87a89fea7141b5ee6a33c Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Wed, 8 Mar 2023 18:19:18 -0500 Subject: vim-patch:9.0.1395: Odin files are not recognized (#22580) Problem: Odin files are not recognized. Solution: Add a pattern for Odin files. (Amaan Qureshi, closes vim/vim#12122) https://github.com/vim/vim/commit/638388b8ef37684e36a7f5d9286bab2d31c28f36 --- 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 33324f580a..8238fec2cc 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -751,6 +751,7 @@ local extension = { mli = 'ocaml', ml = 'ocaml', occ = 'occam', + odin = 'odin', xom = 'omnimark', xin = 'omnimark', opam = 'opam', -- cgit From be0461e3c216c2e4e2c3397c739b7727a5bf6df8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 8 Mar 2023 23:45:43 +0000 Subject: fix(treesitter): is_in_node_range (#22582) TS ranges are end column exclusive, so fix is_in_node_range to account for that. --- runtime/lua/vim/treesitter.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index d13824076e..ab9f8968c8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -191,7 +191,7 @@ end --- ---@return boolean True if the position is in node range function M.is_in_node_range(node, line, col) - return M.node_contains(node, { line, col, line, col }) + return M.node_contains(node, { line, col, line, col + 1 }) end --- Determines if a node contains a range @@ -202,7 +202,8 @@ end ---@return boolean True if the {node} contains the {range} function M.node_contains(node, range) vim.validate({ - node = { node, 'userdata' }, + -- allow a table so nodes can be mocked + node = { node, { 'userdata', 'table' } }, range = { range, Range.validate, 'integer list with 4 or 6 elements' }, }) return Range.contains({ node:range() }, range) -- cgit From 9ef7297ef142354ace8b1f3f277d0eee3cfdc6d4 Mon Sep 17 00:00:00 2001 From: Michal Liszcz Date: Thu, 9 Mar 2023 15:12:56 +0100 Subject: feat(lsp): overwrite omnifunc/tagfunc set by ftplugin #22267 Problem: Some built-in ftplugins set omnifunc/tagfunc/formatexpr which causes lsp.lua:set_defaults() to skip setup of defaults for those filetypes. For example the C++ ftplugin has: omnifunc=ccomplete#Complete Last set from /usr/share/nvim/runtime/ftplugin/c.vim line 30 so the changes done in #95c65a6b221fe6e1cf91e8322e7d7571dc511a71 will always be skipped for C++ files. Solution: Overwrite omnifunc/tagfunc/formatexpr options that were set by stock ftplugin. Fixes #21001 --- runtime/lua/vim/lsp.lua | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 9a0b3f3100..cb77e9636f 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1112,19 +1112,43 @@ function lsp.start_client(config) end end + ---@private + -- Determines whether the given option can be set by `set_defaults`. + local function is_empty_or_default(bufnr, option) + if vim.bo[bufnr][option] == '' then + return true + end + + local old_bufnr = vim.fn.bufnr('') + local last_set_from = vim.fn.gettext('\n\tLast set from ') + local line = vim.fn.gettext(' line ') + + vim.cmd.buffer(bufnr) + local scriptname = vim.fn + .execute('verbose set ' .. option .. '?') + :match(last_set_from .. '(.*)' .. line .. '%d+') + vim.cmd.buffer(old_bufnr) + + if not scriptname then + return false + end + local vimruntime = vim.fn.getenv('VIMRUNTIME') + return vim.startswith(vim.fn.expand(scriptname), vim.fn.expand(vimruntime)) + end + ---@private local function set_defaults(client, bufnr) local capabilities = client.server_capabilities - if capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then + if capabilities.definitionProvider and is_empty_or_default(bufnr, 'tagfunc') then vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc' end - if capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then + if capabilities.completionProvider and is_empty_or_default(bufnr, 'omnifunc') then vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' end if capabilities.documentRangeFormattingProvider - and vim.bo[bufnr].formatprg == '' - and vim.bo[bufnr].formatexpr == '' + and is_empty_or_default(bufnr, 'formatprg') + and is_empty_or_default(bufnr, 'formatexpr') then vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()' end -- cgit From ae263aff9547b8b513c4fedaceb4cbf93c57b866 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Mar 2023 16:09:39 +0000 Subject: refactor(treesitter): use byte ranges from treesitter (#22589) --- runtime/lua/vim/treesitter/_meta.lua | 7 +- runtime/lua/vim/treesitter/_range.lua | 35 ++- runtime/lua/vim/treesitter/languagetree.lua | 440 +++++++++++++++++----------- runtime/lua/vim/treesitter/query.lua | 1 + 4 files changed, 305 insertions(+), 178 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index ad0854706b..72823ccf26 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -3,7 +3,7 @@ ---@class TSNode ---@field id fun(self: TSNode): integer ---@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode): integer, integer, integer, integer +---@field range fun(self: TSNode, include_bytes: boolean?): integer, integer, integer, integer, integer, integer ---@field start fun(self: TSNode): integer, integer, integer ---@field end_ fun(self: TSNode): integer, integer, integer ---@field type fun(self: TSNode): string @@ -43,9 +43,9 @@ function TSNode:_rawquery(query, captures, start, end_) end function TSNode:_rawquery(query, captures, start, end_) end ---@class TSParser ----@field parse fun(self: TSParser, tree, source: integer|string): TSTree, Range4[] +---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] ---@field reset fun(self: TSParser) ----@field included_ranges fun(self: TSParser): Range4[] +---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[] ---@field set_included_ranges fun(self: TSParser, ranges: Range6[]) ---@field set_timeout fun(self: TSParser, timeout: integer) ---@field timeout fun(self: TSParser): integer @@ -54,6 +54,7 @@ function TSNode:_rawquery(query, captures, start, end_) end ---@field root fun(self: TSTree): TSNode ---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer) ---@field copy fun(self: TSTree): TSTree +---@field included_ranges fun(self: TSTree, include_bytes: boolean?): integer[] ---@return integer vim._ts_get_language_version = function() end diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index bec24a23a5..02918da23f 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -78,11 +78,8 @@ end ---@param r2 Range4|Range6 ---@return boolean function M.intercepts(r1, r2) - local off_1 = #r1 == 6 and 1 or 0 - local off_2 = #r1 == 6 and 1 or 0 - - local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] - local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) + local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) -- r1 is above r2 if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then @@ -97,16 +94,28 @@ function M.intercepts(r1, r2) return true end +---@private +---@param r Range4|Range6 +---@return integer, integer, integer, integer +function M.unpack4(r) + local off_1 = #r == 6 and 1 or 0 + return r[1], r[2], r[3 + off_1], r[4 + off_1] +end + +---@private +---@param r Range6 +---@return integer, integer, integer, integer, integer, integer +function M.unpack6(r) + return r[1], r[2], r[3], r[4], r[5], r[6] +end + ---@private ---@param r1 Range4|Range6 ---@param r2 Range4|Range6 ---@return boolean whether r1 contains r2 function M.contains(r1, r2) - local off_1 = #r1 == 6 and 1 or 0 - local off_2 = #r1 == 6 and 1 or 0 - - local srow_1, scol_1, erow_1, ecol_1 = r1[1], r1[2], r1[3 + off_1], r1[4 + off_1] - local srow_2, scol_2, erow_2, ecol_2 = r2[1], r2[2], r2[3 + off_2], r2[4 + off_2] + local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1) + local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2) -- start doesn't fit if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then @@ -123,9 +132,13 @@ end ---@private ---@param source integer|string ----@param range Range4 +---@param range Range4|Range6 ---@return Range6 function M.add_bytes(source, range) + if type(range) == 'table' and #range == 6 then + return range --[[@as Range6]] + end + local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4] local start_byte = 0 local end_byte = 0 diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index fbc602486b..c89419085f 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -62,8 +62,6 @@ local Range = require('vim.treesitter._range') ---@field private _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) ---@field private _valid boolean|table If the parsed tree is valid ---- TODO(lewis6991): combine _regions, _valid and _trees ----@field private _is_child boolean local LanguageTree = {} ---@class LanguageTreeOpts @@ -117,6 +115,48 @@ function LanguageTree.new(source, lang, opts) return self end +---@private +---Measure execution time of a function +---@generic R1, R2, R3 +---@param f fun(): R1, R2, R2 +---@return integer, R1, R2, R3 +local function tcall(f, ...) + local start = vim.loop.hrtime() + ---@diagnostic disable-next-line + local r = { f(...) } + local duration = (vim.loop.hrtime() - start) / 1000000 + return duration, unpack(r) +end + +---@private +---@vararg any +function LanguageTree:_log(...) + if vim.g.__ts_debug == nil then + return + end + + local args = { ... } + if type(args[1]) == 'function' then + args = { args[1]() } + end + + local info = debug.getinfo(2, 'nl') + local nregions = #self:included_regions() + local prefix = + string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) + + a.nvim_out_write(prefix) + for _, x in ipairs(args) do + if type(x) == 'string' then + a.nvim_out_write(x) + else + a.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) + end + a.nvim_out_write(' ') + end + a.nvim_out_write('\n') +end + --- Invalidates this parser and all its children ---@param reload boolean|nil function LanguageTree:invalidate(reload) @@ -146,7 +186,9 @@ end --- Determines whether this tree is valid. --- If the tree is invalid, call `parse()`. --- This will return the updated tree. -function LanguageTree:is_valid() +---@param exclude_children boolean|nil +---@return boolean +function LanguageTree:is_valid(exclude_children) local valid = self._valid if type(valid) == 'table' then @@ -155,9 +197,18 @@ function LanguageTree:is_valid() return false end end - return true end + if not exclude_children then + for _, child in pairs(self._children) do + if not child:is_valid(exclude_children) then + return false + end + end + end + + assert(type(valid) == 'boolean') + return valid end @@ -171,16 +222,6 @@ function LanguageTree:source() return self._source end ----@private ----This is only exposed so it can be wrapped for profiling ----@param old_tree TSTree ----@return TSTree, integer[] -function LanguageTree:_parse_tree(old_tree) - local tree, tree_changes = self._parser:parse(old_tree, self._source) - self:_do_callback('changedtree', tree_changes, tree) - return tree, tree_changes -end - --- Parses all defined regions using a treesitter parser --- for the language this tree represents. --- This will run the injection query for this language to @@ -190,31 +231,45 @@ end ---@return table|nil Change list function LanguageTree:parse() if self:is_valid() then + self:_log('valid') return self._trees end local changes = {} - -- If there are no ranges, set to an empty list - -- so the included ranges in the parser are cleared. - if #self._regions > 0 then - for i, ranges in ipairs(self._regions) do - if not self._valid or not self._valid[i] then - self._parser:set_included_ranges(ranges) - local tree, tree_changes = self:_parse_tree(self._trees[i]) - self._trees[i] = tree - vim.list_extend(changes, tree_changes) + -- Collect some stats + local regions_parsed = 0 + local total_parse_time = 0 + + --- At least 1 region is invalid + if not self:is_valid(true) then + local function _parsetree(index) + local parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[index], self._source) + + self:_do_callback('changedtree', tree_changes, tree) + self._trees[index] = tree + vim.list_extend(changes, tree_changes) + + total_parse_time = total_parse_time + parse_time + regions_parsed = regions_parsed + 1 + end + + if #self._regions > 0 then + for i, ranges in ipairs(self._regions) do + if not self._valid or not self._valid[i] then + self._parser:set_included_ranges(ranges) + _parsetree(i) + end end + else + _parsetree(1) end - else - local tree, tree_changes = self:_parse_tree(self._trees[1]) - self._trees = { tree } - changes = tree_changes end - local injections_by_lang = self:_get_injections() local seen_langs = {} ---@type table + local query_time, injections_by_lang = tcall(self._get_injections, self) for lang, injection_ranges in pairs(injections_by_lang) do local has_lang = pcall(language.add, lang) @@ -229,15 +284,6 @@ function LanguageTree:parse() end child:set_included_regions(injection_ranges) - - local _, child_changes = child:parse() - - -- Propagate any child changes so they are included in the - -- the change list for the callback. - if child_changes then - vim.list_extend(changes, child_changes) - end - seen_langs[lang] = true end end @@ -248,6 +294,23 @@ function LanguageTree:parse() end end + self:_log({ + changes = changes, + regions_parsed = regions_parsed, + parse_time = total_parse_time, + query_time = query_time, + }) + + self:for_each_child(function(child) + local _, child_changes = child:parse() + + -- Propagate any child changes so they are included in the + -- the change list for the callback. + if child_changes then + vim.list_extend(changes, child_changes) + end + end) + self._valid = true return self._trees, changes @@ -295,8 +358,6 @@ function LanguageTree:add_child(lang) end self._children[lang] = LanguageTree.new(self._source, lang, self._opts) - self._children[lang]._is_child = true - self:invalidate() self:_do_callback('child_added', self._children[lang]) @@ -331,6 +392,54 @@ function LanguageTree:destroy() end end +---@private +---@param region Range6[] +local function region_tostr(region) + local srow, scol = region[1][1], region[1][2] + local erow, ecol = region[#region][4], region[#region][5] + return string.format('[%d:%d-%d:%d]', srow, scol, erow, ecol) +end + +---@private +---Iterate through all the regions. fn returns a boolean to indicate if the +---region is valid or not. +---@param fn fun(index: integer, region: Range6[]): boolean +function LanguageTree:_iter_regions(fn) + if not self._valid then + return + end + + if type(self._valid) ~= 'table' then + self._valid = {} + end + + local all_valid = true + + for i, region in ipairs(self._regions) do + if self._valid[i] == nil then + self._valid[i] = true + end + + if self._valid[i] then + self._valid[i] = fn(i, region) + if not self._valid[i] then + self:_log(function() + return 'invalidating region', i, region_tostr(region) + end) + end + end + + if not self._valid[i] then + all_valid = false + end + end + + -- Compress the valid value to 'true' if there are no invalid regions + if all_valid then + self._valid = all_valid + end +end + --- Sets the included regions that should be parsed by this |LanguageTree|. --- A region is a set of nodes and/or ranges that will be parsed in the same context. --- @@ -346,10 +455,10 @@ end --- nodes, which is useful for templating languages like ERB and EJS. --- ---@private ----@param regions Range4[][] List of regions this tree should manage and parse. -function LanguageTree:set_included_regions(regions) +---@param new_regions Range4[][] List of regions this tree should manage and parse. +function LanguageTree:set_included_regions(new_regions) -- Transform the tables from 4 element long to 6 element long (with byte offset) - for _, region in ipairs(regions) do + for _, region in ipairs(new_regions) do for i, range in ipairs(region) do if type(range) == 'table' and #range == 4 then region[i] = Range.add_bytes(self._source, range) @@ -357,83 +466,75 @@ function LanguageTree:set_included_regions(regions) end end - if #self._regions ~= #regions then + if #self._regions ~= #new_regions then self._trees = {} self:invalidate() - elseif self._valid ~= false then - if self._valid == true then - self._valid = {} - for i = 1, #regions do - self._valid[i] = true - end - end - - for i = 1, #regions do - if not vim.deep_equal(self._regions[i], regions[i]) then - self._valid[i] = false - end - - if not self._valid[i] then - self._trees[i] = nil - end - end + else + self:_iter_regions(function(i, region) + return vim.deep_equal(new_regions[i], region) + end) end - - self._regions = regions + self._regions = new_regions end ---- Gets the set of included regions +---Gets the set of included regions +---@return integer[][] function LanguageTree:included_regions() return self._regions end ---@private ---@param node TSNode ----@param id integer +---@param source integer|string ---@param metadata TSMetadata ----@return Range4 -local function get_range_from_metadata(node, id, metadata) - if metadata[id] and metadata[id].range then - return metadata[id].range --[[@as Range4]] +---@return Range6 +local function get_range_from_metadata(node, source, metadata) + if metadata and metadata.range then + return Range.add_bytes(source, metadata.range --[[@as Range4|Range6]]) end - return { node:range() } + return { node:range(true) } end ---@private --- TODO(lewis6991): cleanup of the node_range interface ---@param node TSNode ----@param id integer +---@param source string|integer ---@param metadata TSMetadata ----@return Range4[] -local function get_node_ranges(node, id, metadata, include_children) - local range = get_range_from_metadata(node, id, metadata) +---@return Range6[] +local function get_node_ranges(node, source, metadata, include_children) + local range = get_range_from_metadata(node, source, metadata) if include_children then return { range } end - local ranges = {} ---@type Range4[] + local ranges = {} ---@type Range6[] - local srow, scol, erow, ecol = range[1], range[2], range[3], range[4] + local srow, scol, sbyte, erow, ecol, ebyte = Range.unpack6(range) for i = 0, node:named_child_count() - 1 do local child = node:named_child(i) - local child_srow, child_scol, child_erow, child_ecol = child:range() - if child_srow > srow or child_scol > scol then - table.insert(ranges, { srow, scol, child_srow, child_scol }) + local c_srow, c_scol, c_sbyte, c_erow, c_ecol, c_ebyte = child:range(true) + if c_srow > srow or c_scol > scol then + ranges[#ranges + 1] = { srow, scol, sbyte, c_srow, c_scol, c_sbyte } end - srow = child_erow - scol = child_ecol + srow = c_erow + scol = c_ecol + sbyte = c_ebyte end if erow > srow or ecol > scol then - table.insert(ranges, { srow, scol, erow, ecol }) + ranges[#ranges + 1] = Range.add_bytes(source, { srow, scol, sbyte, erow, ecol, ebyte }) end return ranges end ----@alias TSInjection table> +---@class TSInjectionElem +---@field combined boolean +---@field regions Range6[][] + +---@alias TSInjection table> ---@private ---@param t table @@ -498,7 +599,7 @@ function LanguageTree:_get_injection(match, metadata) if name == 'injection.language' then lang = get_node_text(node, self._source, metadata[id]) elseif name == 'injection.content' then - ranges = get_node_ranges(node, id, metadata, include_children) + ranges = get_node_ranges(node, self._source, metadata[id], include_children) end end @@ -545,7 +646,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) + ranges[#ranges + 1] = get_range_from_metadata(node, self._source, metadata[id]) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -554,7 +655,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end if #ranges == 0 then - table.insert(ranges, get_range_from_metadata(node, id, metadata)) + ranges[#ranges + 1] = get_range_from_metadata(node, self._source, metadata[id]) end end end @@ -569,7 +670,7 @@ end --- TODO: Allow for an offset predicate to tailor the injection range --- instead of using the entire nodes range. ---@private ----@return table +---@return table function LanguageTree:_get_injections() if not self._injection_query then return {} @@ -594,7 +695,7 @@ function LanguageTree:_get_injections() end end - ---@type table + ---@type table local result = {} -- Generate a map by lang of node lists. @@ -613,7 +714,6 @@ function LanguageTree:_get_injections() end, entry.regions) table.insert(result[lang], regions) else - ---@diagnostic disable-next-line:no-unknown for _, ranges in ipairs(entry.regions) do table.insert(result[lang], ranges) end @@ -634,30 +734,68 @@ function LanguageTree:_do_callback(cb_name, ...) end ---@private ----@param regions Range6[][] ----@param old_range Range6 ----@param new_range Range6 ----@return table region indices to invalidate -local function update_regions(regions, old_range, new_range) - ---@type table - local valid = {} - - for i, ranges in ipairs(regions or {}) do - valid[i] = true - for j, r in ipairs(ranges) do - if Range.intercepts(r, old_range) then - valid[i] = false - break +function LanguageTree:_edit( + start_byte, + end_byte_old, + end_byte_new, + start_row, + start_col, + end_row_old, + end_col_old, + end_row_new, + end_col_new +) + for _, tree in ipairs(self._trees) do + tree:edit( + start_byte, + end_byte_old, + end_byte_new, + start_row, + start_col, + end_row_old, + end_col_old, + end_row_new, + end_col_new + ) + end + + local changed_range = { + start_row, + start_col, + start_byte, + end_row_old, + end_col_old, + end_byte_old, + } + + local new_range = { + start_row, + start_col, + start_byte, + end_row_new, + end_col_new, + end_byte_new, + } + + if #self._regions == 0 then + self._valid = false + end + + -- Validate regions after editing the tree + self:_iter_regions(function(_, region) + for i, r in ipairs(region) do + if Range.intercepts(r, changed_range) then + return false end -- Range after change. Adjust - if Range.cmp_pos.gt(r[1], r[2], old_range[4], old_range[5]) then - local byte_offset = new_range[6] - old_range[6] - local row_offset = new_range[4] - old_range[4] + if Range.cmp_pos.gt(r[1], r[2], changed_range[4], changed_range[5]) then + local byte_offset = new_range[6] - changed_range[6] + local row_offset = new_range[4] - changed_range[4] -- Update the range to avoid invalidation in set_included_regions() -- which will compare the regions against the parsed injection regions - ranges[j] = { + region[i] = { r[1] + row_offset, r[2], r[3] + byte_offset, @@ -667,9 +805,8 @@ local function update_regions(regions, old_range, new_range) } end end - end - - return valid + return true + end) end ---@private @@ -700,49 +837,26 @@ function LanguageTree:_on_bytes( local old_end_col = old_col + ((old_row == 0) and start_col or 0) local new_end_col = new_col + ((new_row == 0) and start_col or 0) - local old_range = { - start_row, - start_col, - start_byte, - start_row + old_row, - old_end_col, - start_byte + old_byte, - } - - local new_range = { + self:_log( + 'on_bytes', + bufnr, + changed_tick, start_row, start_col, start_byte, - start_row + new_row, - new_end_col, - start_byte + new_byte, - } - - if #self._regions == 0 then - self._valid = false - else - self._valid = update_regions(self._regions, old_range, new_range) - end - - for _, child in pairs(self._children) do - child:_on_bytes( - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) - end + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) -- Edit trees together BEFORE emitting a bytes callback. - for _, tree in ipairs(self._trees) do - tree:edit( + ---@private + self:for_each_child(function(child) + ---@diagnostic disable-next-line:invisible + child:_edit( start_byte, start_byte + old_byte, start_byte + new_byte, @@ -753,24 +867,22 @@ function LanguageTree:_on_bytes( start_row + new_row, new_end_col ) - end + end, true) - if not self._is_child then - self:_do_callback( - 'bytes', - bufnr, - changed_tick, - start_row, - start_col, - start_byte, - old_row, - old_col, - old_byte, - new_row, - new_col, - new_byte - ) - end + self:_do_callback( + 'bytes', + bufnr, + changed_tick, + start_row, + start_col, + start_byte, + old_row, + old_col, + old_byte, + new_row, + new_col, + new_byte + ) end ---@private diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 59894cc7f5..e7cf42283d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -277,6 +277,7 @@ end ---@return (string[]|string|nil) function M.get_node_text(node, source, opts) opts = opts or {} + -- TODO(lewis6991): concat only works when source is number. local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} -- cgit From 68aa2857dd6bc1b33add02121cd36ef1ab32728c Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Thu, 9 Mar 2023 12:13:44 -0500 Subject: vim-patch:9.0.1393: Cairo files are not recognized (#22578) Problem: Cairo files are not recognized. Solution: Add a pattern for Cairo files. (Amaan Qureshi, closes vim/vim#12118) https://github.com/vim/vim/commit/ff226d49fed2d8fc668084324c7b0f00117c5e74 --- 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 8238fec2cc..1732623223 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -191,6 +191,7 @@ local extension = { BUILD = 'bzl', qc = 'c', cabal = 'cabal', + cairo = 'cairo', capnp = 'capnp', cdc = 'cdc', cdl = 'cdl', -- cgit From 0ecb4d725e9e2086b045670e4c2fa8962b63d99c Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Fri, 10 Mar 2023 06:17:08 +0900 Subject: docs(lsp): type annotation for lsp.client (#22509) * Also fix newly found type mismatch. * Note that it generates new warnings about using @private client methods. A proper fix would be to revamp the lsp client documentation altogether. --- runtime/lua/vim/lsp.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index cb77e9636f..c85d38a50e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -154,6 +154,8 @@ local all_buffer_active_clients = {} local uninitialized_clients = {} ---@private +---@param bufnr? integer +---@param fn fun(client: lsp.Client, client_id: integer, bufnr: integer) local function for_each_buffer_client(bufnr, fn, restrict_client_ids) validate({ fn = { fn, 'f' }, @@ -1240,6 +1242,7 @@ function lsp.start_client(config) return end + ---@class lsp.Client local client = { id = client_id, name = name, @@ -1390,7 +1393,7 @@ function lsp.start_client(config) --- checks for capabilities and handler availability. --- ---@param method string LSP method name. - ---@param params table LSP request params. + ---@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 @@ -2087,7 +2090,7 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms) end --- Send a notification to a server ----@param bufnr (number|nil) The number of the buffer +---@param bufnr (integer|nil) The number of the buffer ---@param method (string) Name of the request method ---@param params (any) Arguments to send to the server --- -- cgit From adfa9de8ebc4bce96d212280eccddc0306d1b013 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 10:12:57 +0000 Subject: fix(treesitter): do not error on empty filetype Ignore instead --- runtime/lua/vim/treesitter/language.lua | 14 +++----------- 1 file changed, 3 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 5f34d9cd56..47375fd5e6 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -60,16 +60,6 @@ function M.add(lang, opts) filetype = { filetype, { 'string', 'table' }, true }, }) - if filetype == '' then - error(string.format("'%s' is not a valid filetype", filetype)) - elseif type(filetype) == 'table' then - for _, f in ipairs(filetype) do - if f == '' then - error(string.format("'%s' is not a valid filetype", filetype)) - end - end - end - M.register(lang, filetype or lang) if vim._ts_has_language(lang) then @@ -109,7 +99,9 @@ function M.register(lang, filetype) end for _, f in ipairs(filetypes) do - ft_to_lang[f] = lang + if f ~= '' then + ft_to_lang[f] = lang + end end end -- cgit From c5b9643bf1b0f6d5166b4abf6a7c3f29532aefeb Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 10:25:10 +0000 Subject: fix(treesitter): better lang handling of get_parser() --- runtime/lua/vim/treesitter.lua | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index ab9f8968c8..5723cce563 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -99,13 +99,28 @@ function M.get_parser(bufnr, lang, opts) if bufnr == nil or bufnr == 0 then bufnr = a.nvim_get_current_buf() end + if lang == nil then local ft = vim.bo[bufnr].filetype - lang = language.get_lang(ft) or ft - -- TODO(lewis6991): we should error here and not default to ft - -- if not lang then - -- error(string.format('filetype %s of buffer %d is not associated with any lang', ft, bufnr)) - -- end + if ft ~= '' then + lang = language.get_lang(ft) or ft + -- TODO(lewis6991): we should error here and not default to ft + -- if not lang then + -- error(string.format('filetype %s of buffer %d is not associated with any lang', ft, bufnr)) + -- end + else + if parsers[bufnr] then + return parsers[bufnr] + end + error( + string.format( + 'There is no parser available for buffer %d and one could not be' + .. ' created because lang could not be determined. Either pass lang' + .. ' or set the buffer filetype', + bufnr + ) + ) + end end if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then -- cgit From 46b73bf22cb951151de9bf0712d42e194000b677 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 9 Mar 2023 15:28:55 +0000 Subject: perf(treesitter): more efficient foldexpr --- runtime/lua/vim/treesitter/_fold.lua | 290 +++++++++++++++++++++++------------ 1 file changed, 188 insertions(+), 102 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a66cc6d543..435cb9fdb6 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,139 +1,157 @@ +local Range = require('vim.treesitter._range') + local api = vim.api -local M = {} +---@class FoldInfo +---@field levels table +---@field levels0 table +---@field private start_counts table +---@field private stop_counts table +local FoldInfo = {} +FoldInfo.__index = FoldInfo ---- Memoizes a function based on the buffer tick of the provided bufnr. ---- The cache entry is cleared when the buffer is detached to avoid memory leaks. ----@generic F: function ----@param fn F fn to memoize, taking the bufnr as first argument ----@return F -local function memoize_by_changedtick(fn) - ---@type table - local cache = {} - - ---@param bufnr integer - return function(bufnr, ...) - local tick = api.nvim_buf_get_changedtick(bufnr) - - if cache[bufnr] then - if cache[bufnr].last_tick == tick then - return cache[bufnr].result - end - else - local function detach_handler() - cache[bufnr] = nil - end +function FoldInfo.new() + return setmetatable({ + start_counts = {}, + stop_counts = {}, + levels0 = {}, + levels = {}, + }, FoldInfo) +end - -- Clean up logic only! - api.nvim_buf_attach(bufnr, false, { - on_detach = detach_handler, - on_reload = detach_handler, - }) - end +---@param srow integer +---@param erow integer +function FoldInfo:invalidate_range(srow, erow) + for i = srow, erow do + self.start_counts[i + 1] = nil + self.stop_counts[i + 1] = nil + self.levels0[i + 1] = nil + self.levels[i + 1] = nil + end +end - cache[bufnr] = { - result = fn(bufnr, ...), - last_tick = tick, - } +---@param srow integer +---@param erow integer +function FoldInfo:remove_range(srow, erow) + for i = erow - 1, srow, -1 do + table.remove(self.levels, i + 1) + table.remove(self.levels0, i + 1) + table.remove(self.start_counts, i + 1) + table.remove(self.stop_counts, i + 1) + end +end - return cache[bufnr].result +---@param srow integer +---@param erow integer +function FoldInfo:add_range(srow, erow) + for i = srow, erow - 1 do + table.insert(self.levels, i + 1, '-1') + table.insert(self.levels0, i + 1, -1) + table.insert(self.start_counts, i + 1, nil) + table.insert(self.stop_counts, i + 1, nil) end end ----@param bufnr integer ----@param capture string ----@param query_name string ----@param callback fun(id: integer, node:TSNode, metadata: TSMetadata) -local function iter_matches_with_capture(bufnr, capture, query_name, callback) - local parser = vim.treesitter.get_parser(bufnr) +---@param lnum integer +function FoldInfo:add_start(lnum) + self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1 +end - if not parser then - return - end +---@param lnum integer +function FoldInfo:add_stop(lnum) + self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 +end - parser:for_each_tree(function(tree, lang_tree) - local lang = lang_tree:lang() - local query = vim.treesitter.query.get_query(lang, query_name) - if query then - local root = tree:root() - local start, _, stop = root:range() - for _, match, metadata in query:iter_matches(root, bufnr, start, stop) do - for id, node in pairs(match) do - if query.captures[id] == capture then - callback(id, node, metadata) - end - end - end - end - end) +---@param lnum integer +---@return integer +function FoldInfo:get_start(lnum) + return self.start_counts[lnum] or 0 +end + +---@param lnum integer +---@return integer +function FoldInfo:get_stop(lnum) + return self.stop_counts[lnum] or 0 end ---@private --- TODO(lewis6991): copied from languagetree.lua. Consolidate ---@param node TSNode ----@param id integer ---@param metadata TSMetadata ----@return Range -local function get_range_from_metadata(node, id, metadata) - if metadata[id] and metadata[id].range then - return metadata[id].range --[[@as Range]] +---@return Range4 +local function get_range_from_metadata(node, metadata) + if metadata and metadata.range then + return metadata.range --[[@as Range4]] end return { node:range() } end --- This is cached on buf tick to avoid computing that multiple times --- Especially not for every line in the file when `zx` is hit ----@param bufnr integer ----@return table -local folds_levels = memoize_by_changedtick(function(bufnr) +local function trim_level(level) local max_fold_level = vim.wo.foldnestmax - local function trim_level(level) - if level > max_fold_level then - return max_fold_level - end - return level + if level > max_fold_level then + return max_fold_level end + return level +end - -- start..stop is an inclusive range - local start_counts = {} ---@type table - local stop_counts = {} ---@type table +---@param bufnr integer +---@param info FoldInfo +---@param srow integer? +---@param erow integer? +local function get_folds_levels(bufnr, info, srow, erow) + srow = srow or 0 + erow = erow or api.nvim_buf_line_count(bufnr) + + info:invalidate_range(srow, erow) local prev_start = -1 local prev_stop = -1 - local min_fold_lines = vim.wo.foldminlines + vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) + local query = vim.treesitter.query.get_query(ltree:lang(), 'folds') + if not query then + return + end + + -- erow in query is end-exclusive + local q_erow = erow and erow + 1 or -1 - iter_matches_with_capture(bufnr, 'fold', 'folds', function(id, node, metadata) - local range = get_range_from_metadata(node, id, metadata) - local start, stop, stop_col = range[1], range[3], range[4] + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do + if query.captures[id] == 'fold' then + local range = get_range_from_metadata(node, metadata[id]) + local start, _, stop, stop_col = Range.unpack4(range) - if stop_col == 0 then - stop = stop - 1 - end + if stop_col == 0 then + stop = stop - 1 + end - local fold_length = stop - start + 1 + 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 > min_fold_lines and not (start == prev_start and stop == prev_stop) then - start_counts[start] = (start_counts[start] or 0) + 1 - stop_counts[stop] = (stop_counts[stop] or 0) + 1 - prev_start = start - prev_stop = stop + -- 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 + info:add_start(start + 1) + info:add_stop(stop + 1) + prev_start = start + prev_stop = stop + end + end end end) - ---@type table - local levels = {} - local current_level = 0 + local current_level = info.levels0[srow] or 0 -- We now have the list of fold opening and closing, fill the gaps and mark where fold start - for lnum = 0, api.nvim_buf_line_count(bufnr) do + for lnum = srow + 1, erow + 1 do local last_trimmed_level = trim_level(current_level) - current_level = current_level + (start_counts[lnum] or 0) + current_level = current_level + info:get_start(lnum) + info.levels0[lnum] = current_level + local trimmed_level = trim_level(current_level) - current_level = current_level - (stop_counts[lnum] or 0) + current_level = current_level - info:get_stop(lnum) -- Determine if it's the start/end of a fold -- NB: vim's fold-expr interface does not have a mechanism to indicate that @@ -148,11 +166,61 @@ local folds_levels = memoize_by_changedtick(function(bufnr) prefix = '>' end - levels[lnum + 1] = prefix .. tostring(trimmed_level) + info.levels[lnum] = prefix .. tostring(trimmed_level) + end +end + +local M = {} + +---@type table +local foldinfos = {} + +local function recompute_folds() + if api.nvim_get_mode().mode == 'i' then + -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave + api.nvim_create_autocmd('InsertLeave', { + once = true, + callback = vim._foldupdate, + }) + return end - return levels -end) + vim._foldupdate() +end + +---@param bufnr integer +---@param foldinfo FoldInfo +---@param tree_changes Range4[] +local function on_changedtree(bufnr, foldinfo, tree_changes) + -- For some reason, queries seem to use the old buffer state in on_bytes. + -- Get around this by scheduling and manually updating folds. + vim.schedule(function() + for _, change in ipairs(tree_changes) do + local srow, _, erow = Range.unpack4(change) + get_folds_levels(bufnr, foldinfo, srow, erow) + end + recompute_folds() + end) +end + +---@param bufnr integer +---@param foldinfo FoldInfo +---@param start_row integer +---@param old_row integer +---@param new_row integer +local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) + local end_row_old = start_row + old_row + local end_row_new = start_row + new_row + if new_row < old_row then + foldinfo:remove_range(end_row_old, end_row_new) + elseif new_row > old_row then + foldinfo:add_range(start_row, end_row_new) + vim.schedule(function() + get_folds_levels(bufnr, foldinfo, start_row, end_row_new) + recompute_folds() + end) + end +end ---@param lnum integer|nil ---@return string @@ -165,9 +233,27 @@ function M.foldexpr(lnum) return '0' end - local levels = folds_levels(bufnr) or {} + if not foldinfos[bufnr] then + foldinfos[bufnr] = FoldInfo.new() + get_folds_levels(bufnr, foldinfos[bufnr]) + + local parser = vim.treesitter.get_parser(bufnr) + parser:register_cbs({ + on_changedtree = function(tree_changes) + on_changedtree(bufnr, foldinfos[bufnr], tree_changes) + end, + + on_bytes = function(_, _, start_row, _, _, old_row, _, _, new_row, _, _) + on_bytes(bufnr, foldinfos[bufnr], start_row, old_row, new_row) + end, + + on_detach = function() + foldinfos[bufnr] = nil + end, + }) + end - return levels[lnum] or '0' + return foldinfos[bufnr].levels[lnum] or '0' end return M -- cgit From 75537768ef0b8cc35ef9c6aa906237e449640b46 Mon Sep 17 00:00:00 2001 From: Null Chilly <56817415+nullchilly@users.noreply.github.com> Date: Fri, 10 Mar 2023 20:10:38 +0700 Subject: perf(lsp): better binary search mid calculation in semantic token (#22607) This commit replaces the usage of math.floor((lo + hi) / 2) with the faster and equivalent bit.rshift(lo + hi, 1) for calculating the midpoint in binary search. --- runtime/lua/vim/lsp/semantic_tokens.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 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 a5e007a011..03532c33b7 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -44,7 +44,7 @@ local STHighlighter = { active = {} } ---@private local function lower_bound(tokens, line, lo, hi) while lo < hi do - local mid = math.floor((lo + hi) / 2) + local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2). if tokens[mid].line < line then lo = mid + 1 else @@ -62,7 +62,7 @@ end ---@private local function upper_bound(tokens, line, lo, hi) while lo < hi do - local mid = math.floor((lo + hi) / 2) + local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2). if line < tokens[mid].line then hi = mid else -- cgit From 762a06c6bcfbcc1e40ba670bae10bacdbb973524 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 16:16:49 +0000 Subject: feat!(treesitter): do not return changes from LanguageTree:parse() Never return the changes an only notify them using the `on_changedtree` callback. It is not guaranteed for a plugin that it'll be the first one to call `tree:parse()` and thus get the changes. Closes #19915 --- runtime/lua/vim/treesitter/languagetree.lua | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index c89419085f..26321cd1f4 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -228,7 +228,6 @@ end --- determine if any child languages should be created. --- ---@return TSTree[] ----@return table|nil Change list function LanguageTree:parse() if self:is_valid() then self:_log('valid') @@ -302,18 +301,12 @@ function LanguageTree:parse() }) self:for_each_child(function(child) - local _, child_changes = child:parse() - - -- Propagate any child changes so they are included in the - -- the change list for the callback. - if child_changes then - vim.list_extend(changes, child_changes) - end + child:parse() end) self._valid = true - return self._trees, changes + return self._trees end --- Invokes the callback for each |LanguageTree| and its children recursively -- cgit From 9d70fe062ca01ac0673faa6ccbb88345916aeea7 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 10 Mar 2023 16:10:05 +0000 Subject: feat(treesitter)!: consolidate query util functions - And address more type errors. - Removed the `concat` option from `get_node_text` since it was applied inconsistently and made typing awkward. --- runtime/lua/vim/treesitter/_fold.lua | 15 +---- runtime/lua/vim/treesitter/_range.lua | 15 ++++- runtime/lua/vim/treesitter/languagetree.lua | 55 +++++----------- runtime/lua/vim/treesitter/query.lua | 97 ++++++++++++++--------------- 4 files changed, 79 insertions(+), 103 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 435cb9fdb6..fd2c707d17 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,4 +1,5 @@ local Range = require('vim.treesitter._range') +local Query = require('vim.treesitter.query') local api = vim.api @@ -74,18 +75,6 @@ function FoldInfo:get_stop(lnum) return self.stop_counts[lnum] or 0 end ----@private ---- TODO(lewis6991): copied from languagetree.lua. Consolidate ----@param node TSNode ----@param metadata TSMetadata ----@return Range4 -local function get_range_from_metadata(node, metadata) - if metadata and metadata.range then - return metadata.range --[[@as Range4]] - end - return { node:range() } -end - local function trim_level(level) local max_fold_level = vim.wo.foldnestmax if level > max_fold_level then @@ -118,7 +107,7 @@ local function get_folds_levels(bufnr, info, srow, erow) for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do if query.captures[id] == 'fold' then - local range = get_range_from_metadata(node, metadata[id]) + local range = Query.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 02918da23f..0017a567ec 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -2,8 +2,19 @@ local api = vim.api local M = {} ----@alias Range4 {[1]: integer, [2]: integer, [3]: integer, [4]: integer} ----@alias Range6 {[1]: integer, [2]: integer, [3]: integer, [4]: integer, [5]: integer, [6]: integer} +---@class Range4 +---@field [1] integer start row +---@field [2] integer start column +---@field [3] integer end row +---@field [4] integer end column + +---@class Range6 +---@field [1] integer start row +---@field [2] integer start column +---@field [3] integer start bytes +---@field [4] integer end row +---@field [5] integer end column +---@field [6] integer end bytes ---@private ---@param a_row integer diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index c89419085f..0bb0601241 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -455,7 +455,7 @@ end --- nodes, which is useful for templating languages like ERB and EJS. --- ---@private ----@param new_regions Range4[][] List of regions this tree should manage and parse. +---@param new_regions Range6[][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(new_regions) -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(new_regions) do @@ -483,18 +483,6 @@ function LanguageTree:included_regions() return self._regions end ----@private ----@param node TSNode ----@param source integer|string ----@param metadata TSMetadata ----@return Range6 -local function get_range_from_metadata(node, source, metadata) - if metadata and metadata.range then - return Range.add_bytes(source, metadata.range --[[@as Range4|Range6]]) - end - return { node:range(true) } -end - ---@private --- TODO(lewis6991): cleanup of the node_range interface ---@param node TSNode @@ -502,7 +490,7 @@ end ---@param metadata TSMetadata ---@return Range6[] local function get_node_ranges(node, source, metadata, include_children) - local range = get_range_from_metadata(node, source, metadata) + local range = query.get_range(node, source, metadata) if include_children then return { range } @@ -565,31 +553,18 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges) table.insert(t[tree_index][lang][pattern].regions, ranges) end ----@private ----Get node text ---- ----Note: `query.get_node_text` returns string|string[]|nil so use this simple alias function ----to annotate it returns string. ---- ----TODO(lewis6991): use [at]overload annotations on `query.get_node_text` ----@param node TSNode ----@param source integer|string ----@param metadata table ----@return string -local function get_node_text(node, source, metadata) - return query.get_node_text(node, source, { metadata = metadata }) --[[@as string]] -end - ---@private --- Extract injections according to: --- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection ---@param match table ----@param metadata table +---@param metadata TSMetadata ---@return string, boolean, Range4[] function LanguageTree:_get_injection(match, metadata) local ranges = {} ---@type Range4[] local combined = metadata['injection.combined'] ~= nil - local lang = metadata['injection.language'] ---@type string + local lang = metadata['injection.language'] + assert(type(lang) == 'string') + local include_children = metadata['injection.include-children'] ~= nil for id, node in pairs(match) do @@ -597,7 +572,7 @@ function LanguageTree:_get_injection(match, metadata) -- Lang should override any other language tag if name == 'injection.language' then - lang = get_node_text(node, self._source, metadata[id]) + lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'injection.content' then ranges = get_node_ranges(node, self._source, metadata[id], include_children) end @@ -608,11 +583,11 @@ end ---@private ---@param match table ----@param metadata table +---@param metadata TSMetadata ---@return string, boolean, Range4[] function LanguageTree:_get_injection_deprecated(match, metadata) local lang = nil ---@type string - local ranges = {} ---@type Range4[] + local ranges = {} ---@type Range6[] local combined = metadata.combined ~= nil -- Directives can configure how injections are captured as well as actual node captures. @@ -630,8 +605,10 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end end - if metadata.language then - lang = metadata.language ---@type string + local mlang = metadata.language + if mlang ~= nil then + assert(type(mlang) == 'string') + lang = mlang end -- You can specify the content and language together @@ -642,11 +619,11 @@ function LanguageTree:_get_injection_deprecated(match, metadata) -- Lang should override any other language tag if name == 'language' and not lang then - lang = get_node_text(node, self._source, metadata[id]) + lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - ranges[#ranges + 1] = get_range_from_metadata(node, self._source, metadata[id]) + ranges[#ranges + 1] = query.get_range(node, self._source, metadata[id]) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -655,7 +632,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end if #ranges == 0 then - ranges[#ranges + 1] = get_range_from_metadata(node, self._source, metadata[id]) + ranges[#ranges + 1] = query.get_range(node, self._source, metadata[id]) end end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index e7cf42283d..70af4f7bce 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,6 +1,8 @@ local a = vim.api local language = require('vim.treesitter.language') +local Range = require('vim.treesitter._range') + ---@class Query ---@field captures string[] List of captures used in query ---@field info TSQueryInfo Contains used queries, predicates, directives @@ -56,35 +58,13 @@ local function add_included_lang(base_langs, lang, ilang) end ---@private ----@param buf (integer) ----@param range (table) ----@param concat (boolean) ----@returns (string[]|string|nil) -local function buf_range_get_text(buf, range, concat) - local lines - local start_row, start_col, end_row, end_col = unpack(range) - local eof_row = a.nvim_buf_line_count(buf) - if start_row >= eof_row then - return nil - end - - if end_col == 0 then - lines = a.nvim_buf_get_lines(buf, start_row, end_row, true) - end_col = -1 - else - lines = a.nvim_buf_get_lines(buf, start_row, end_row + 1, true) - end - - if #lines > 0 then - if #lines == 1 then - lines[1] = string.sub(lines[1], start_col + 1, end_col) - else - lines[1] = string.sub(lines[1], start_col + 1) - lines[#lines] = string.sub(lines[#lines], 1, end_col) - end - end - - return concat and table.concat(lines, '\n') or lines +---@param buf integer +---@param range Range6 +---@returns string +local function buf_range_get_text(buf, range) + local start_row, start_col, end_row, end_col = Range.unpack4(range) + local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) + return table.concat(lines, '\n') end --- Gets the list of files used to make up a query @@ -256,14 +236,28 @@ function M.parse_query(lang, query) local cached = query_cache[lang][query] if cached then return cached - else - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) - self.info = self.query:inspect() - self.captures = self.info.captures - query_cache[lang][query] = self - return self end + + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + query_cache[lang][query] = self + return self +end + +---Get the range of a |TSNode|. Can also supply {source} and {metadata} +---to get the range with directives applied. +---@param node TSNode +---@param source integer|string|nil Buffer or string from which the {node} is extracted +---@param metadata TSMetadata|nil +---@return Range6 +function M.get_range(node, source, metadata) + if metadata and metadata.range then + assert(source) + return Range.add_bytes(source, metadata.range) + end + return { node:range(true) } end --- Gets the text corresponding to a given node @@ -271,24 +265,22 @@ end ---@param node TSNode ---@param source (integer|string) Buffer or string from which the {node} is extracted ---@param opts (table|nil) Optional parameters. ---- - concat: (boolean) Concatenate result in a string (default true) --- - metadata (table) Metadata of a specific capture. This would be --- set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|. ----@return (string[]|string|nil) +---@return string function M.get_node_text(node, source, opts) opts = opts or {} - -- TODO(lewis6991): concat only works when source is number. - local concat = vim.F.if_nil(opts.concat, true) local metadata = opts.metadata or {} if metadata.text then return metadata.text elseif type(source) == 'number' then - return metadata.range and buf_range_get_text(source, metadata.range, concat) - or buf_range_get_text(source, { node:range() }, concat) - elseif type(source) == 'string' then - return source:sub(select(3, node:start()) + 1, select(3, node:end_())) + local range = M.get_range(node, source, metadata) + return buf_range_get_text(source, range) end + + ---@cast source string + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) end ---@alias TSMatch table @@ -312,7 +304,7 @@ local predicate_handlers = { str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) --[[@as string]] + str = M.get_node_text(match[predicate[3]], source) end if node_text ~= str or str == nil then @@ -328,7 +320,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source) --[[@as string]], regex) ~= nil + return string.find(M.get_node_text(node, source), regex) ~= nil end, ['match?'] = (function() @@ -366,7 +358,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) --[[@as string]] + local node_text = M.get_node_text(node, source) for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -404,9 +396,9 @@ local predicate_handlers = { predicate_handlers['vim-match?'] = predicate_handlers['match?'] ---@class TSMetadata +---@field range Range4|Range6 ---@field [integer] TSMetadata ---@field [string] integer|string ----@field range Range4 ---@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata) @@ -465,13 +457,20 @@ local directive_handlers = { assert(#pred == 4) local id = pred[2] + assert(type(id) == 'number') + local node = match[id] local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' if not metadata[id] then metadata[id] = {} end - metadata[id].text = text:gsub(pred[3], pred[4]) + + local pattern, replacement = pred[3], pred[3] + assert(type(pattern) == 'string') + assert(type(replacement) == 'string') + + metadata[id].text = text:gsub(pattern, replacement) end, } -- cgit From 236c20795eb9f11e21e0719b735ea741711acc08 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 11 Mar 2023 07:35:23 +0100 Subject: revert: "fix(lsp): use buffer scheme for files not stored on disk" (#22604) Although using `buffer://` for unsaved file buffers fixes issues with language servers like eclipse.jdt.ls or ansible-language-server, it breaks completion and signature help for clangd. A regression is worse than a fix for something else, so this reverts commit 896d672736b32a8f4a4fa51844b44f266dcdcc6c. The spec change is also still in dicussion, see https://github.com/microsoft/language-server-protocol/pull/1679#discussion_r1130704886 --- runtime/lua/vim/lsp.lua | 54 ++++++++++---------------------------------- runtime/lua/vim/lsp/util.lua | 9 ++------ 2 files changed, 14 insertions(+), 49 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c85d38a50e..117b32dc57 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -369,7 +369,7 @@ do --- @field offset_encoding "utf-8"|"utf-16"|"utf-32" --- --- @class CTBufferState - --- @field uri string uri of the buffer + --- @field name string name of the buffer --- @field lines string[] snapshot of buffer lines from last didChange --- @field lines_tmp string[] --- @field pending_changes table[] List of debounced changes in incremental sync mode @@ -488,12 +488,8 @@ do if buf_state then buf_state.refs = buf_state.refs + 1 else - local uri = vim.uri_from_bufnr(bufnr) - if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then - uri = uri:gsub('^file://', 'buffer://') - end buf_state = { - uri = uri, + name = api.nvim_buf_get_name(bufnr), lines = {}, lines_tmp = {}, pending_changes = {}, @@ -508,26 +504,12 @@ do end ---@private - ---@param client table - ---@param bufnr integer - ---@return string uri - function changetracking._get_uri(client, bufnr) - local state = state_by_group[get_group(client)] or {} - local buf_state = (state.buffers or {})[bufnr] - return assert(buf_state.uri, 'Must have an URI set') - end - - ---@private - ---@param client table - ---@param bufnr integer - ---@param uri string - ---@return string uri - function changetracking._get_and_set_uri(client, bufnr, uri) + function changetracking._get_and_set_name(client, bufnr, name) local state = state_by_group[get_group(client)] or {} local buf_state = (state.buffers or {})[bufnr] - local old_uri = buf_state.uri - buf_state.uri = uri - return old_uri + local old_name = buf_state.name + buf_state.name = name + return old_name end ---@private @@ -614,7 +596,7 @@ do { text = buf_get_full_text(bufnr) }, } end - local uri = buf_state.uri + local uri = vim.uri_from_bufnr(bufnr) for _, client in pairs(state.clients) do if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then client.notify('textDocument/didChange', { @@ -727,14 +709,11 @@ local function text_document_did_open_handler(bufnr, client) return end local filetype = nvim_buf_get_option(bufnr, 'filetype') - local uri = vim.uri_from_bufnr(bufnr) - if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then - uri = uri:gsub('^file://', 'buffer://') - end + local params = { textDocument = { version = 0, - uri = uri, + uri = vim.uri_from_bufnr(bufnr), languageId = client.config.get_language_id(bufnr, filetype), text = buf_get_full_text(bufnr), }, @@ -1611,13 +1590,8 @@ local function text_document_did_save_handler(bufnr) local text = once(buf_get_full_text) for_each_buffer_client(bufnr, function(client) local name = api.nvim_buf_get_name(bufnr) - local old_uri = changetracking._get_and_set_uri(client, bufnr, uri) - if old_uri and name ~= old_uri then - client.notify('textDocument/didClose', { - textDocument = { - uri = old_uri, - }, - }) + local old_name = changetracking._get_and_set_name(client, bufnr, name) + if old_name and name ~= old_name then client.notify('textDocument/didOpen', { textDocument = { version = 0, @@ -1720,12 +1694,8 @@ function lsp.buf_attach_client(bufnr, client_id) end) end, on_detach = function() + local params = { textDocument = { uri = uri } } for_each_buffer_client(bufnr, function(client, _) - local params = { - textDocument = { - uri = changetracking._get_uri(client, bufnr), - }, - } changetracking.reset_buf(client, bufnr) if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index c9613dc7a7..342fad33c2 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2032,12 +2032,7 @@ end ---@returns `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) - bufnr = bufnr or 0 - local uri = vim.uri_from_bufnr(bufnr) - if not uv.fs_stat(api.nvim_buf_get_name(bufnr)) then - uri = uri:gsub('^file://', 'buffer://') - end - return { uri = uri } + return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params @@ -2070,7 +2065,7 @@ function M.make_formatting_params(options) insertSpaces = vim.bo.expandtab, }) return { - textDocument = M.make_text_document_params(0), + textDocument = { uri = vim.uri_from_bufnr(0) }, options = options, } end -- cgit From c2f7f8d61c6436e1de13d921f13760fbdfe1d624 Mon Sep 17 00:00:00 2001 From: nullchilly Date: Sat, 11 Mar 2023 17:23:50 +0700 Subject: refactor(highlight)!: remove deprecated functions vim.highlight.create/link --- runtime/lua/vim/highlight.lua | 24 ------------------------ 1 file changed, 24 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 89eae0def5..d71a806ea8 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -10,30 +10,6 @@ M.priorities = { user = 200, } ----@private -function M.create(higroup, hi_info, default) - vim.deprecate('vim.highlight.create', 'vim.api.nvim_set_hl', '0.9') - local options = {} - -- TODO: Add validation - for k, v in pairs(hi_info) do - table.insert(options, string.format('%s=%s', k, v)) - end - vim.cmd( - string.format( - [[highlight %s %s %s]], - default and 'default' or '', - higroup, - table.concat(options, ' ') - ) - ) -end - ----@private -function M.link(higroup, link_to, force) - vim.deprecate('vim.highlight.link', 'vim.api.nvim_set_hl', '0.9') - vim.cmd(string.format([[highlight%s link %s %s]], force and '!' or ' default', higroup, link_to)) -end - --- Highlight range between two positions --- ---@param bufnr integer Buffer number to apply highlighting to -- cgit From 71eebd28d1da487fbbb3c162c6b909940f76883d Mon Sep 17 00:00:00 2001 From: nullchilly Date: Sat, 11 Mar 2023 17:24:48 +0700 Subject: refactor(treesitter)!: remove deprecated show_tree func --- runtime/lua/vim/treesitter.lua | 6 ------ 1 file changed, 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 5723cce563..56000f99a8 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -648,12 +648,6 @@ function M.inspect_tree(opts) }) end ----@deprecated ----@private -function M.show_tree() - vim.deprecate('show_tree', 'inspect_tree', '0.9', nil, false) -end - --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': ---
lua
 --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
-- 
cgit 


From 865d8d4720ca72de1c385773ca4c597f3642874c Mon Sep 17 00:00:00 2001
From: Raphael 
Date: Sat, 11 Mar 2023 21:49:53 +0800
Subject: refactor(lsp): remove _resolve_capabilities_compat (#22628)

---
 runtime/lua/vim/lsp/protocol.lua | 174 ---------------------------------------
 1 file changed, 174 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 27dd68645a..1686e22c48 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -854,7 +854,6 @@ function protocol.make_client_capabilities()
   }
 end
 
-local if_nil = vim.F.if_nil
 --- Creates a normalized object describing LSP server capabilities.
 ---@param server_capabilities table Table of capabilities supported by the server
 ---@return table Normalized table of capabilities
@@ -892,178 +891,5 @@ function protocol.resolve_capabilities(server_capabilities)
   return server_capabilities
 end
 
----@private
---- Creates a normalized object describing LSP server capabilities.
--- @deprecated access resolved_capabilities instead
----@param server_capabilities table Table of capabilities supported by the server
----@return table Normalized table of capabilities
-function protocol._resolve_capabilities_compat(server_capabilities)
-  local general_properties = {}
-  local text_document_sync_properties
-  do
-    local TextDocumentSyncKind = protocol.TextDocumentSyncKind
-    local textDocumentSync = server_capabilities.textDocumentSync
-    if textDocumentSync == nil then
-      -- Defaults if omitted.
-      text_document_sync_properties = {
-        text_document_open_close = false,
-        text_document_did_change = TextDocumentSyncKind.None,
-        --        text_document_did_change = false;
-        text_document_will_save = false,
-        text_document_will_save_wait_until = false,
-        text_document_save = false,
-        text_document_save_include_text = false,
-      }
-    elseif type(textDocumentSync) == 'number' then
-      -- Backwards compatibility
-      if not TextDocumentSyncKind[textDocumentSync] then
-        return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
-      end
-      text_document_sync_properties = {
-        text_document_open_close = true,
-        text_document_did_change = textDocumentSync,
-        text_document_will_save = false,
-        text_document_will_save_wait_until = false,
-        text_document_save = true,
-        text_document_save_include_text = false,
-      }
-    elseif type(textDocumentSync) == 'table' then
-      text_document_sync_properties = {
-        text_document_open_close = if_nil(textDocumentSync.openClose, false),
-        text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None),
-        text_document_will_save = if_nil(textDocumentSync.willSave, true),
-        text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, true),
-        text_document_save = if_nil(textDocumentSync.save, false),
-        text_document_save_include_text = if_nil(
-          type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText,
-          false
-        ),
-      }
-    else
-      return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
-    end
-  end
-  general_properties.completion = server_capabilities.completionProvider ~= nil
-  general_properties.hover = server_capabilities.hoverProvider or false
-  general_properties.goto_definition = server_capabilities.definitionProvider or false
-  general_properties.find_references = server_capabilities.referencesProvider or false
-  general_properties.document_highlight = server_capabilities.documentHighlightProvider or false
-  general_properties.document_symbol = server_capabilities.documentSymbolProvider or false
-  general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
-  general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
-  general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider
-    or false
-  general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
-  general_properties.execute_command = server_capabilities.executeCommandProvider ~= nil
-
-  if server_capabilities.renameProvider == nil then
-    general_properties.rename = false
-  elseif type(server_capabilities.renameProvider) == 'boolean' then
-    general_properties.rename = server_capabilities.renameProvider
-  else
-    general_properties.rename = true
-  end
-
-  if server_capabilities.codeLensProvider == nil then
-    general_properties.code_lens = false
-    general_properties.code_lens_resolve = false
-  elseif type(server_capabilities.codeLensProvider) == 'table' then
-    general_properties.code_lens = true
-    general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider
-      or false
-  else
-    error('The server sent invalid codeLensProvider')
-  end
-
-  if server_capabilities.codeActionProvider == nil then
-    general_properties.code_action = false
-  elseif
-    type(server_capabilities.codeActionProvider) == 'boolean'
-    or type(server_capabilities.codeActionProvider) == 'table'
-  then
-    general_properties.code_action = server_capabilities.codeActionProvider
-  else
-    error('The server sent invalid codeActionProvider')
-  end
-
-  if server_capabilities.declarationProvider == nil then
-    general_properties.declaration = false
-  elseif type(server_capabilities.declarationProvider) == 'boolean' then
-    general_properties.declaration = server_capabilities.declarationProvider
-  elseif type(server_capabilities.declarationProvider) == 'table' then
-    general_properties.declaration = server_capabilities.declarationProvider
-  else
-    error('The server sent invalid declarationProvider')
-  end
-
-  if server_capabilities.typeDefinitionProvider == nil then
-    general_properties.type_definition = false
-  elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then
-    general_properties.type_definition = server_capabilities.typeDefinitionProvider
-  elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
-    general_properties.type_definition = server_capabilities.typeDefinitionProvider
-  else
-    error('The server sent invalid typeDefinitionProvider')
-  end
-
-  if server_capabilities.implementationProvider == nil then
-    general_properties.implementation = false
-  elseif type(server_capabilities.implementationProvider) == 'boolean' then
-    general_properties.implementation = server_capabilities.implementationProvider
-  elseif type(server_capabilities.implementationProvider) == 'table' then
-    general_properties.implementation = server_capabilities.implementationProvider
-  else
-    error('The server sent invalid implementationProvider')
-  end
-
-  local workspace = server_capabilities.workspace
-  local workspace_properties = {}
-  if workspace == nil or workspace.workspaceFolders == nil then
-    -- Defaults if omitted.
-    workspace_properties = {
-      workspace_folder_properties = {
-        supported = false,
-        changeNotifications = false,
-      },
-    }
-  elseif type(workspace.workspaceFolders) == 'table' then
-    workspace_properties = {
-      workspace_folder_properties = {
-        supported = if_nil(workspace.workspaceFolders.supported, false),
-        changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
-      },
-    }
-  else
-    error('The server sent invalid workspace')
-  end
-
-  local signature_help_properties
-  if server_capabilities.signatureHelpProvider == nil then
-    signature_help_properties = {
-      signature_help = false,
-      signature_help_trigger_characters = {},
-    }
-  elseif type(server_capabilities.signatureHelpProvider) == 'table' then
-    signature_help_properties = {
-      signature_help = true,
-      -- The characters that trigger signature help automatically.
-      signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters
-        or {},
-    }
-  else
-    error('The server sent invalid signatureHelpProvider')
-  end
-
-  local capabilities = vim.tbl_extend(
-    'error',
-    text_document_sync_properties,
-    signature_help_properties,
-    workspace_properties,
-    general_properties
-  )
-
-  return capabilities
-end
-
 return protocol
 -- vim:sw=2 ts=2 et
-- 
cgit 


From 23dc2a59b6e13b0dbab47c6c64ac5a55095b258b Mon Sep 17 00:00:00 2001
From: Mathias Fußenegger 
Date: Sat, 11 Mar 2023 14:50:14 +0100
Subject: fix(lsp): send didClose on buffer rename (#22623)

Subset of https://github.com/neovim/neovim/pull/22407 that was reverted
in https://github.com/neovim/neovim/pull/22604

If a buffer is renamed sending `didClose` for the old buffer helps
ensure the language server doesn't keep a stale document in memory.
---
 runtime/lua/vim/lsp.lua | 6 ++++++
 1 file changed, 6 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 117b32dc57..1896543da3 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,3 +1,4 @@
+---@diagnostic disable: invisible
 local default_handlers = require('vim.lsp.handlers')
 local log = require('vim.lsp.log')
 local lsp_rpc = require('vim.lsp.rpc')
@@ -1592,6 +1593,11 @@ local function text_document_did_save_handler(bufnr)
     local name = api.nvim_buf_get_name(bufnr)
     local old_name = changetracking._get_and_set_name(client, bufnr, name)
     if old_name and name ~= old_name then
+      client.notify('textDocument/didClose', {
+        textDocument = {
+          uri = vim.uri_from_fname(old_name),
+        },
+      })
       client.notify('textDocument/didOpen', {
         textDocument = {
           version = 0,
-- 
cgit 


From 0ce626b783dcca8eed08a2bb5a18e2c3ef931fbe Mon Sep 17 00:00:00 2001
From: Jaehwang Jung 
Date: Sat, 11 Mar 2023 22:50:53 +0900
Subject: docs(lsp): more precise type annotations (#22621)

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

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 1896543da3..39665a3d4f 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1038,7 +1038,7 @@ function lsp.start_client(config)
   --- Returns the default handler if the user hasn't set a custom one.
   ---
   ---@param method (string) LSP method name
-  ---@return function|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers|
+  ---@return lsp-handler|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers|
   local function resolve_handler(method)
     return handlers[method] or default_handlers[method]
   end
@@ -1938,7 +1938,7 @@ api.nvim_create_autocmd('VimLeavePre', {
 ---@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 function|nil See |lsp-handler|
+---@param handler lsp-handler|nil See |lsp-handler|
 ---       If nil, follows resolution strategy defined in |lsp-handler-configuration|
 ---
 ---@return table, fun() 2-tuple:
@@ -1998,9 +1998,10 @@ 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 callback (function) The callback to call when all requests are finished.
+---@param callback fun(request_results: table) (function)
+--- The callback to call when all requests are finished.
 --- Unlike `buf_request`, this will collect all the responses from each server instead of handling them.
---- A map of client_id:request_result will be provided to the callback
+--- A map of client_id:request_result will be provided to the callback.
 ---
 ---@return fun() cancel A function that will cancel all requests
 function lsp.buf_request_all(bufnr, method, params, callback)
@@ -2043,9 +2044,8 @@ end
 ---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for a
 ---                               result. Defaults to 1000
 ---
----@return table|nil result, string|nil err Map of client_id:request_result.
---- On timeout, cancel or error, returns `(nil, err)` where `err` is a string describing
---- the failure reason.
+---@return table|nil (table) result Map of client_id:request_result.
+---@return string|nil 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
 
-- 
cgit 


From 58bbc2ea0b3dfed13471e8cc0447d7598be24276 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Fri, 10 Mar 2023 16:40:27 +0000
Subject: refactor(treesitter): add Range type aliase for Range4|Range6

---
 runtime/lua/vim/treesitter/_range.lua       | 14 ++++++++------
 runtime/lua/vim/treesitter/languagetree.lua | 15 ++++++---------
 runtime/lua/vim/treesitter/query.lua        | 12 ++++++++++--
 3 files changed, 24 insertions(+), 17 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua
index 0017a567ec..f4db5016ac 100644
--- a/runtime/lua/vim/treesitter/_range.lua
+++ b/runtime/lua/vim/treesitter/_range.lua
@@ -16,6 +16,8 @@ local M = {}
 ---@field [5] integer end column
 ---@field [6] integer end bytes
 
+---@alias Range Range4|Range6
+
 ---@private
 ---@param a_row integer
 ---@param a_col integer
@@ -85,8 +87,8 @@ function M.validate(r)
 end
 
 ---@private
----@param r1 Range4|Range6
----@param r2 Range4|Range6
+---@param r1 Range
+---@param r2 Range
 ---@return boolean
 function M.intercepts(r1, r2)
   local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
@@ -106,7 +108,7 @@ function M.intercepts(r1, r2)
 end
 
 ---@private
----@param r Range4|Range6
+---@param r Range
 ---@return integer, integer, integer, integer
 function M.unpack4(r)
   local off_1 = #r == 6 and 1 or 0
@@ -121,8 +123,8 @@ function M.unpack6(r)
 end
 
 ---@private
----@param r1 Range4|Range6
----@param r2 Range4|Range6
+---@param r1 Range
+---@param r2 Range
 ---@return boolean whether r1 contains r2
 function M.contains(r1, r2)
   local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
@@ -143,7 +145,7 @@ end
 
 ---@private
 ---@param source integer|string
----@param range Range4|Range6
+---@param range Range
 ---@return Range6
 function M.add_bytes(source, range)
   if type(range) == 'table' and #range == 6 then
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 0bb0601241..6869fae92c 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -484,7 +484,6 @@ function LanguageTree:included_regions()
 end
 
 ---@private
---- TODO(lewis6991): cleanup of the node_range interface
 ---@param node TSNode
 ---@param source string|integer
 ---@param metadata TSMetadata
@@ -530,7 +529,7 @@ end
 ---@param pattern integer
 ---@param lang string
 ---@param combined boolean
----@param ranges Range4[]
+---@param ranges Range6[]
 local function add_injection(t, tree_index, pattern, lang, combined, ranges)
   assert(type(lang) == 'string')
 
@@ -558,13 +557,11 @@ end
 --- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
 ---@param match table
 ---@param metadata TSMetadata
----@return string, boolean, Range4[]
+---@return string?, boolean, Range6[]
 function LanguageTree:_get_injection(match, metadata)
-  local ranges = {} ---@type Range4[]
+  local ranges = {} ---@type Range6[]
   local combined = metadata['injection.combined'] ~= nil
-  local lang = metadata['injection.language']
-  assert(type(lang) == 'string')
-
+  local lang = metadata['injection.language'] --[[@as string?]]
   local include_children = metadata['injection.include-children'] ~= nil
 
   for id, node in pairs(match) do
@@ -584,7 +581,7 @@ end
 ---@private
 ---@param match table
 ---@param metadata TSMetadata
----@return string, boolean, Range4[]
+---@return string, boolean, Range6[]
 function LanguageTree:_get_injection_deprecated(match, metadata)
   local lang = nil ---@type string
   local ranges = {} ---@type Range6[]
@@ -910,7 +907,7 @@ end
 
 ---@private
 ---@param tree TSTree
----@param range Range4
+---@param range Range
 ---@return boolean
 local function tree_contains(tree, range)
   return Range.contains({ tree:root():range() }, range)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 70af4f7bce..f4e038b2d8 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -59,10 +59,18 @@ end
 
 ---@private
 ---@param buf integer
----@param range Range6
+---@param range Range
 ---@returns string
 local function buf_range_get_text(buf, range)
   local start_row, start_col, end_row, end_col = Range.unpack4(range)
+  if end_col == 0 then
+    if start_row == end_row then
+      start_col = -1
+      start_row = start_row - 1
+    end
+    end_col = -1
+    end_row = end_row - 1
+  end
   local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {})
   return table.concat(lines, '\n')
 end
@@ -396,7 +404,7 @@ local predicate_handlers = {
 predicate_handlers['vim-match?'] = predicate_handlers['match?']
 
 ---@class TSMetadata
----@field range Range4|Range6
+---@field range Range
 ---@field [integer] TSMetadata
 ---@field [string] integer|string
 
-- 
cgit 


From d15abd1be4ae85b10174e3ee139d3b7605e87577 Mon Sep 17 00:00:00 2001
From: Mathias Fußenegger 
Date: Sun, 12 Mar 2023 09:45:28 +0100
Subject: fix(lsp): use line start/end for visual line selection (#22632)

Fixes https://github.com/neovim/neovim/issues/22629
---
 runtime/lua/vim/lsp/buf.lua | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 6ac885c78f..0e16e8f820 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -118,8 +118,10 @@ function M.completion(context)
 end
 
 ---@private
+---@param bufnr integer
+---@param mode "v"|"V"
 ---@return table {start={row, col}, end={row, col}} using (1, 0) indexing
-local function range_from_selection()
+local function range_from_selection(bufnr, mode)
   -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896
 
   -- [bufnum, lnum, col, off]; both row and column 1-indexed
@@ -138,6 +140,11 @@ local function range_from_selection()
     start_row, end_row = end_row, start_row
     start_col, end_col = end_col, start_col
   end
+  if mode == 'V' then
+    start_col = 1
+    local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)
+    end_col = #lines[1]
+  end
   return {
     ['start'] = { start_row, start_col - 1 },
     ['end'] = { end_row, end_col - 1 },
@@ -200,7 +207,7 @@ function M.format(options)
   local mode = api.nvim_get_mode().mode
   local range = options.range
   if not range and mode == 'v' or mode == 'V' then
-    range = range_from_selection()
+    range = range_from_selection(bufnr, mode)
   end
   local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting'
 
@@ -772,7 +779,7 @@ function M.code_action(options)
     local end_ = assert(options.range['end'], 'range must have a `end` property')
     params = util.make_given_range_params(start, end_)
   elseif mode == 'v' or mode == 'V' then
-    local range = range_from_selection()
+    local range = range_from_selection(0, mode)
     params = util.make_given_range_params(range.start, range['end'])
   else
     params = util.make_range_params()
-- 
cgit 


From 673d2b52fa4335aa083c52e6686f0728e25b8ebd Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Tue, 7 Mar 2023 16:04:57 +0100
Subject: refactor!: rename vim.pretty_print => vim.print

Problem:
The function name `vim.pretty_print`:
1. is verbose, which partially defeats its purpose as sugar
2. does not draw from existing precedent or any sort of convention
   (except external projects like penlight or python?), which reduces
   discoverability, and degrades signaling about best practices.

Solution:
- Rename to `vim.print`.
- Change the behavior so that
  1. strings are printed without quotes
  2. each arg is printed on its own line
  3. tables are indented with 2 instead of 4 spaces
- Example:
  :lua ='a', 'b', 42, {a=3}
  a
  b
  42
  {
    a = 3
  }

Comparison of alternatives:
- `vim.print`:
  - pro: consistent with Lua's `print()`
  - pro: aligns with potential `nvim_print` API function which will
    replace nvim_echo, nvim_notify, etc.
  - con: behaves differently than Lua's `print()`, slightly misleading?
- `vim.echo`:
  - pro: `:echo` has similar "pretty print" behavior.
  - con: inconsistent with Lua idioms.
- `vim.p`:
  - pro: very short, fits with `vim.o`, etc.
  - con: not as discoverable as "echo"
  - con: less opportunity for `local p = vim.p` because of potential shadowing.
---
 runtime/lua/vim/_editor.lua | 39 +++++++++++++++++++++++++++------------
 1 file changed, 27 insertions(+), 12 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 9516233b45..5445c4e492 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -778,22 +778,37 @@ do
   end
 end
 
----Prints given arguments in human-readable format.
----Example:
----
lua
----  -- Print highlight group Normal and store it's contents in a variable.
----  local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
----
----@see |vim.inspect()| ----@return any # given arguments. +---@private function vim.pretty_print(...) - local objects = {} + vim.deprecate('vim.pretty_print', 'vim.print', '0.10') + return vim.print(...) +end + +--- "Pretty prints" the given arguments and returns them unmodified. +--- +--- Example: +---
lua
+---   local hl_normal = vim.print(vim.api.nvim_get_hl_by_name('Normal', true))
+--- 
+--- +--- @see |vim.inspect()| +--- @return any # given arguments. +function vim.print(...) + if vim.in_fast_event() then + print(...) + return ... + end + for i = 1, select('#', ...) do - local v = select(i, ...) - table.insert(objects, vim.inspect(v)) + local o = select(i, ...) + if type(o) == 'string' then + vim.api.nvim_out_write(o) + else + vim.api.nvim_out_write(vim.inspect(o, { newline = '\n', indent = ' ' })) + end + vim.api.nvim_out_write('\n') end - print(table.concat(objects, ' ')) return ... end -- cgit From 35799a6629f10cc49e79381e61038b3a4ca3bb23 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 13 Mar 2023 10:44:43 +0000 Subject: fix(treesitter): foldexpr (#22652) The ranges passed to foldinfo.remove_range were in the wrong order. --- runtime/lua/vim/treesitter/_fold.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index fd2c707d17..90f4394fcc 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -200,8 +200,9 @@ end local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) local end_row_old = start_row + old_row local end_row_new = start_row + new_row + if new_row < old_row then - foldinfo:remove_range(end_row_old, end_row_new) + foldinfo:remove_range(end_row_new, end_row_old) elseif new_row > old_row then foldinfo:add_range(start_row, end_row_new) vim.schedule(function() -- cgit From f01f18cdf4ffc3ce035db5fde2f45493eebb7fd9 Mon Sep 17 00:00:00 2001 From: Dan Strokirk Date: Mon, 13 Mar 2023 14:01:34 +0100 Subject: fix(lsp): remove_workspace_folders fails if client has no workspace_folders #22633 When a client has no workspace_folders, (e.g., copilot), `pairs` code would crash. --- runtime/lua/vim/lsp/buf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 0e16e8f820..8bf3764f5e 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -530,7 +530,7 @@ function M.remove_workspace_folder(workspace_folder) { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } ) for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do - for idx, folder in pairs(client.workspace_folders) do + for idx, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) client.workspace_folders[idx] = nil -- cgit From 8dde7c907ca9ad365895bded2c2f59e08f65d3ed Mon Sep 17 00:00:00 2001 From: hrsh7th <629908+hrsh7th@users.noreply.github.com> Date: Tue, 14 Mar 2023 20:59:43 +0900 Subject: fix(lsp): vim.lsp.util.apply_text_edits cursor validation #22636 Problem Using wrong variable when checking the cursor position is valid or not in vim.lsp.util.apply_text_edits. Solution Use the correct variable. --- runtime/lua/vim/lsp/util.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 342fad33c2..82c9e3bc87 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -436,7 +436,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Some LSP servers are depending on the VSCode behavior. -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. - local is_current_buf = api.nvim_get_current_buf() == bufnr + local is_current_buf = api.nvim_get_current_buf() == bufnr or bufnr == 0 local cursor = (function() if not is_current_buf then return { @@ -464,7 +464,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding), end_row = text_edit.range['end'].line, end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding), - text = split(text_edit.newText, '\n', true), + text = split(text_edit.newText, '\n', { plain = true }), } local max = api.nvim_buf_line_count(bufnr) @@ -522,7 +522,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) if is_cursor_fixed then local is_valid_cursor = true is_valid_cursor = is_valid_cursor and cursor.row < max - is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '') + is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, cursor.row) or '') if is_valid_cursor then api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) end -- cgit From 4f7879dff0f0dc22ddf4cb2a2095b88605a3bab0 Mon Sep 17 00:00:00 2001 From: Ivan Date: Tue, 14 Mar 2023 13:08:37 +0100 Subject: fix(lsp): kill buffers after renaming a directory #22618 Problem: When LSP client renames a directory, opened buffers in the edfitor are not renamed or closed. Then `:wall` shows errors. https://github.com/neovim/neovim/blob/master/runtime/lua/vim/lsp/util.lua#L776 works correctly if you try to rename a single file, but doesn't delete old buffers with `old_fname` is a dir. Solution: Update the logic in runtime/lua/vim/lsp/util.lua:rename() Fixes #22617 --- runtime/lua/vim/lsp/util.lua | 48 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 82c9e3bc87..48faddfce1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -743,6 +743,20 @@ local function bufwinid(bufnr) end end +--- Get list of buffers for a directory +---@private +local function get_dir_bufs(path) + path = path:gsub('([^%w])', '%%%1') + local buffers = {} + for _, v in ipairs(vim.api.nvim_list_bufs()) do + local bufname = vim.api.nvim_buf_get_name(v):gsub('buffer://', '') + if bufname:find(path) then + table.insert(buffers, v) + end + end + return buffers +end + --- Rename old_fname to new_fname --- ---@param opts (table) @@ -755,12 +769,22 @@ function M.rename(old_fname, new_fname, opts) vim.notify('Rename target already exists. Skipping rename.') return end - local oldbuf = vim.fn.bufadd(old_fname) - vim.fn.bufload(oldbuf) - -- The there may be pending changes in the buffer - if vim.fn.isdirectory(old_fname) == 0 then - api.nvim_buf_call(oldbuf, function() + local oldbufs = {} + local win = nil + + if vim.fn.isdirectory(old_fname) == 1 then + oldbufs = get_dir_bufs(old_fname) + else + local oldbuf = vim.fn.bufadd(old_fname) + table.insert(oldbufs, oldbuf) + win = bufwinid(oldbuf) + end + + for _, b in ipairs(oldbufs) do + vim.fn.bufload(b) + -- The there may be pending changes in the buffer + api.nvim_buf_call(b, function() vim.cmd('w!') end) end @@ -768,12 +792,16 @@ function M.rename(old_fname, new_fname, opts) local ok, err = os.rename(old_fname, new_fname) assert(ok, err) - local newbuf = vim.fn.bufadd(new_fname) - local win = bufwinid(oldbuf) - if win then - api.nvim_win_set_buf(win, newbuf) + if vim.fn.isdirectory(new_fname) == 0 then + local newbuf = vim.fn.bufadd(new_fname) + if win then + api.nvim_win_set_buf(win, newbuf) + end + end + + for _, b in ipairs(oldbufs) do + api.nvim_buf_delete(b, {}) end - api.nvim_buf_delete(oldbuf, { force = true }) end ---@private -- cgit From 210120dde81ec289ae01e1d247df08f0b147c08a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 15 Mar 2023 08:56:13 -0400 Subject: fix(lua): vim.deprecate() shows ":help deprecated" #22677 Problem: vim.deprecate() shows ":help deprecated" for third-party plugins. ":help deprecated" only describes deprecations in Nvim, and is unrelated to any 3rd party deprecations. Solution: If `plugin` is specified, don't show ":help deprecated". fix #22235 --- runtime/lua/vim/_editor.lua | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 5445c4e492..f2875e5646 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -518,11 +518,6 @@ do end end ----@private -function vim.register_keystroke_callback() - error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key') -end - local on_key_cbs = {} --- Adds Lua function {fn} with namespace id {ns_id} as a listener to every, @@ -882,27 +877,33 @@ function vim._cs_remote(rcid, server_addr, connect_error, args) } end ---- Display a deprecation notification to the user. +--- Shows a deprecation message to the user. --- ----@param name string Deprecated function. ----@param alternative string|nil Preferred alternative function. ----@param version string Version in which the deprecated function will ---- be removed. ----@param plugin string|nil Plugin name that the function will be removed ---- from. Defaults to "Nvim". +---@param name string Deprecated feature (function, API, etc.). +---@param alternative string|nil Suggested alternative feature. +---@param version string Version when the deprecated function will be removed. +---@param plugin string|nil Name of the plugin that owns the deprecated feature. +--- Defaults to "Nvim". ---@param backtrace boolean|nil Prints backtrace. Defaults to true. +--- +---@returns Deprecated message, or nil if no message was shown. function vim.deprecate(name, alternative, version, plugin, backtrace) - local message = name .. ' is deprecated' + local msg = ('%s is deprecated'):format(name) plugin = plugin or 'Nvim' - message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message - message = message - .. ' See :h deprecated\nThis function will be removed in ' - .. plugin - .. ' version ' - .. version - if vim.notify_once(message, vim.log.levels.WARN) and backtrace ~= false then + 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, + (plugin == 'Nvim' and ' :help deprecated' or ''), + 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 + if displayed then + return msg + end end --- Create builtin mappings (incl. menus). -- cgit From 43d33c5d1abb7ed58237cb5e59d6e1e7dd046b55 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Mar 2023 09:40:25 +0100 Subject: vim-patch:9.0.1406: ILE RPG files are not recognized Problem: ILE RPG files are not recognized. Solution: Add patterns for ILE RPG files. (Andreas Louv, issue vim/vim#12152) https://github.com/vim/vim/commit/e202ec8a0c89e8ef47a3d38e668b7326fa68510a Co-authored-by: Andreas Louv --- 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 1732623223..7be4b75138 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -883,6 +883,8 @@ local extension = { ron = 'ron', rsc = 'routeros', x = 'rpcgen', + rpgle = 'rpgle', + rpgleinc = 'rpgle', rpl = 'rpl', Srst = 'rrst', srst = 'rrst', -- cgit From d7746b6ef1dcb64b3fc4ba744eafc69780772e26 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Mar 2023 09:41:19 +0100 Subject: vim-patch:9.0.1407: TableGen files are not recognized Problem: TableGen files are not recognized. Solution: Add a pattern for TableGen files. (Amaan Qureshi, closes vim/vim#12156) https://github.com/vim/vim/commit/b8ef029ee416fc2b402c3f321a55c9049b0ad2a9 Co-authored-by: Amaan Qureshi --- 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 7be4b75138..9eb03c776a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1018,6 +1018,7 @@ local extension = { swift = 'swift', svh = 'systemverilog', sv = 'systemverilog', + td = 'tablegen', tak = 'tak', tal = 'tal', task = 'taskedit', -- cgit From f83d8ea2798c87e441801ce5f08291808bf6ddbc Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Mar 2023 09:42:01 +0100 Subject: vim-patch:9.0.1408: QMLdir files are not recognized Problem: QMLdir files are not recognized. Solution: Add a pattern for QMLdir files. (Amaan Qureshi, closes vim/vim#12161) https://github.com/vim/vim/commit/1505bef5c482a48e704644e6172be91c07ef1d12 Co-authored-by: Amaan Qureshi --- 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 9eb03c776a..f5353b853d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1593,6 +1593,7 @@ local filename = { ['.pythonstartup'] = 'python', ['.pythonrc'] = 'python', SConstruct = 'python', + qmldir = 'qmldir', ['.Rprofile'] = 'r', ['Rprofile'] = 'r', ['Rprofile.site'] = 'r', -- cgit From 571b50be16a0bb859082196ac1a80872526314cb Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 17 Mar 2023 09:42:33 +0100 Subject: vim-patch:9.0.1409: racket files are recognized as scheme Problem: Racket files are recognized as scheme. Solution: Recognize rackets files separately. (Gabriel Kakizaki, closes vim/vim#12164, closes vim/vim#12162) https://github.com/vim/vim/commit/d11ac403db07b6eac43882485e98caeb5e83e2e5 Co-authored-by: Gabriel Kakizaki --- runtime/lua/vim/filetype.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index f5353b853d..b777eaa179 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -838,6 +838,9 @@ local extension = { R = function(path, bufnr) return require('vim.filetype.detect').r(bufnr) end, + rkt = 'racket', + rktd = 'racket', + rktl = 'racket', rad = 'radiance', mat = 'radiance', ['pod6'] = 'raku', @@ -910,9 +913,6 @@ local extension = { ss = 'scheme', scm = 'scheme', sld = 'scheme', - rkt = 'scheme', - rktd = 'scheme', - rktl = 'scheme', sce = 'scilab', sci = 'scilab', scss = 'scss', -- cgit From 6162269fa3107b23fd18c6c6d0e3e8fb73cfb45e Mon Sep 17 00:00:00 2001 From: August Masquelier <31262046+levouh@users.noreply.github.com> Date: Fri, 17 Mar 2023 05:26:13 -0600 Subject: fix(lsp): avoid switching buffers on lsp attach (#22689) --- runtime/lua/vim/lsp.lua | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 39665a3d4f..7e8c73ddb6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1101,21 +1101,21 @@ function lsp.start_client(config) return true end - local old_bufnr = vim.fn.bufnr('') local last_set_from = vim.fn.gettext('\n\tLast set from ') local line = vim.fn.gettext(' line ') + local scriptname - vim.cmd.buffer(bufnr) - local scriptname = vim.fn - .execute('verbose set ' .. option .. '?') - :match(last_set_from .. '(.*)' .. line .. '%d+') - vim.cmd.buffer(old_bufnr) + vim.api.nvim_buf_call(bufnr, function() + scriptname = vim.fn + .execute('verbose set ' .. option .. '?') + :match(last_set_from .. '(.*)' .. line .. '%d+') + end) if not scriptname then return false end - local vimruntime = vim.fn.getenv('VIMRUNTIME') - return vim.startswith(vim.fn.expand(scriptname), vim.fn.expand(vimruntime)) + + return vim.startswith(vim.fn.expand(scriptname), vim.fn.expand('$VIMRUNTIME')) end ---@private -- cgit From 2720d2c793b22e6c0c8719e5592922619fffdf7a Mon Sep 17 00:00:00 2001 From: Yochem van Rosmalen Date: Fri, 17 Mar 2023 12:41:57 +0100 Subject: fix(treesitter): InspectTree does not respect 'splitright' #22692 Problem: vim.treesitter.inspect_tree() and :InspectTree does not respect 'splitright'. Solution: - Change the default `command` from `topleft 60vnew` to `60vnew`. - Change :InspectTree to respect command mods (`:vertical`, count, etc.). Closes #22656 --- runtime/lua/vim/treesitter.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 56000f99a8..43b8c11b80 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -439,7 +439,7 @@ end --- - winid (integer|nil): Window id to display the tree buffer in. If omitted, --- a new window is created with {command}. --- - command (string|nil): Vimscript command to create the window. Default ---- value is "topleft 60vnew". Only used when {winid} is nil. +--- value is "60vnew". Only used when {winid} is nil. --- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a --- function, it accepts the buffer number of the source buffer as its only --- argument and should return a string. @@ -465,7 +465,7 @@ function M.inspect_tree(opts) local w = opts.winid if not w then - vim.cmd(opts.command or 'topleft 60vnew') + vim.cmd(opts.command or '60vnew') w = a.nvim_get_current_win() end -- cgit From 66ba81a6f7b52c0a765fc52362ca8f730af487ab Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Sat, 18 Mar 2023 05:34:31 -0400 Subject: vim-patch:9.0.1412: Pony files are not recognized (#22721) Problem: Pony files are not recognized. Solution: Add a pattern for Pony files. (Amaan Qureshi, closes vim/vim#12155) https://github.com/vim/vim/commit/6e377eca8de4c0c0d25808beb7de7676194ebf1d --- 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 b777eaa179..cf813b19b1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -800,6 +800,7 @@ local extension = { pod = 'pod', filter = 'poefilter', pk = 'poke', + pony = 'pony', ps = 'postscr', epsi = 'postscr', afm = 'postscr', -- cgit From e5641df6d3fc3bb6c3c55593b6152082bfc561b6 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 11 Mar 2023 17:11:02 +0000 Subject: feat: add `vim.filetype.get_option()` --- runtime/lua/vim/filetype.lua | 20 +++++++++ runtime/lua/vim/filetype/options.lua | 82 ++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 runtime/lua/vim/filetype/options.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index cf813b19b1..69fcb01e8c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2631,4 +2631,24 @@ function M.match(args) end end +--- Get the default option value for a {filetype}. +--- +--- The returned value is what would be set in a new buffer after 'filetype' +--- is set, meaning it should respect all FileType autocmds and ftplugin files. +--- +--- Example: +---
lua
+---   vim.filetype.get_option('vim', 'commentstring')
+--- 
+--- +--- Note: this uses |nvim_get_option_value()| but caches the result. +--- This means |ftplugin| and |FileType| autocommands are only +--- triggered once and may not reflect later changes. +--- @param filetype string Filetype +--- @param option string Option name +--- @return string|boolean|integer: Option value +function M.get_option(filetype, option) + return require('vim.filetype.options').get_option(filetype, option) +end + return M diff --git a/runtime/lua/vim/filetype/options.lua b/runtime/lua/vim/filetype/options.lua new file mode 100644 index 0000000000..77a66991d5 --- /dev/null +++ b/runtime/lua/vim/filetype/options.lua @@ -0,0 +1,82 @@ +local api = vim.api + +local M = {} + +local function get_ftplugin_runtime(filetype) + return api.nvim__get_runtime({ + string.format('ftplugin/%s.vim', filetype), + string.format('ftplugin/%s_*.vim', filetype), + string.format('ftplugin/%s/*.vim', filetype), + string.format('ftplugin/%s.lua', filetype), + string.format('ftplugin/%s_*.lua', filetype), + string.format('ftplugin/%s/*.lua', filetype), + }, true, {}) --[[@as string[] ]] +end + +-- Keep track of ftplugin files +local ftplugin_cache = {} ---@type table> + +-- Keep track of total number of FileType autocmds +local ft_autocmd_num ---@type integer? + +-- Keep track of filetype options +local ft_option_cache = {} ---@type table> + +--- @param path string +--- @return integer +local function hash(path) + local mtime0 = vim.loop.fs_stat(path).mtime + return mtime0.sec * 1000000000 + mtime0.nsec +end + +--- Only update the cache on changes to the number of FileType autocmds +--- and changes to any ftplugin/ file. This isn't guaranteed to catch everything +--- but should be good enough. +--- @param filetype string +local function update_ft_option_cache(filetype) + local new_ftautos = #api.nvim_get_autocmds({ event = 'FileType' }) + if new_ftautos ~= ft_autocmd_num then + -- invalidate + ft_option_cache[filetype] = nil + ft_autocmd_num = new_ftautos + end + + local files = get_ftplugin_runtime(filetype) + + ftplugin_cache[filetype] = ftplugin_cache[filetype] or {} + + if #files ~= #vim.tbl_keys(ftplugin_cache[filetype]) then + -- invalidate + ft_option_cache[filetype] = nil + ftplugin_cache[filetype] = {} + end + + for _, f in ipairs(files) do + -- VIMRUNTIME should be static so shouldn't need to worry about it changing + if not vim.startswith(f, vim.env.VIMRUNTIME) then + local mtime = hash(f) + if ftplugin_cache[filetype][f] ~= mtime then + -- invalidate + ft_option_cache[filetype] = nil + ftplugin_cache[filetype][f] = mtime + end + end + end +end + +--- @private +--- @param filetype string Filetype +--- @param option string Option name +--- @return string|integer|boolean +function M.get_option(filetype, option) + update_ft_option_cache(filetype) + + if not ft_option_cache[filetype] or not ft_option_cache[filetype][option] then + ft_option_cache[filetype] = ft_option_cache[filetype] or {} + ft_option_cache[filetype][option] = api.nvim_get_option_value(option, { filetype = filetype }) + end + + return ft_option_cache[filetype][option] +end + +return M -- cgit From 990c481551af2b346f315d75aa0815e9b65051f3 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 16 Mar 2023 14:50:20 +0100 Subject: refactor(vim.version): use lazy.nvim semver module Use semver code from https://github.com/folke/lazy.nvim License: Apache License 2.0 Co-authored-by: Folke Lemaitre --- runtime/lua/vim/version.lua | 191 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 35629c461f..b409483755 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -1,5 +1,196 @@ local M = {} +local LazyM = {} +M.LazyM = LazyM + +---@class Semver +---@field [1] number +---@field [2] number +---@field [3] number +---@field major number +---@field minor number +---@field patch number +---@field prerelease? string +---@field build? string +local Semver = {} +Semver.__index = Semver + +function Semver:__index(key) + return type(key) == "number" and ({ self.major, self.minor, self.patch })[key] or Semver[key] +end + +function Semver:__newindex(key, value) + if key == 1 then + self.major = value + elseif key == 2 then + self.minor = value + elseif key == 3 then + self.patch = value + else + rawset(self, key, value) + end +end + +---@param other Semver +function Semver:__eq(other) + for i = 1, 3 do + if self[i] ~= other[i] then + return false + end + end + return self.prerelease == other.prerelease +end + +function Semver:__tostring() + local ret = table.concat({ self.major, self.minor, self.patch }, ".") + if self.prerelease then + ret = ret .. "-" .. self.prerelease + end + if self.build then + ret = ret .. "+" .. self.build + end + return ret +end + +---@param other Semver +function Semver:__lt(other) + for i = 1, 3 do + if self[i] > other[i] then + return false + elseif self[i] < other[i] then + return true + end + end + if self.prerelease and not other.prerelease then + return true + end + if other.prerelease and not self.prerelease then + return false + end + return (self.prerelease or "") < (other.prerelease or "") +end + +---@param other Semver +function Semver:__le(other) + return self < other or self == other +end + +---@param version string|number[] +---@return Semver? +function LazyM.parse(version) + if type(version) == "table" then + return setmetatable({ + major = version[1] or 0, + minor = version[2] or 0, + patch = version[3] or 0, + }, Semver) + end + local major, minor, patch, prerelease, build = version:match("^v?(%d+)%.?(%d*)%.?(%d*)%-?([^+]*)+?(.*)$") + if major then + return setmetatable({ + major = tonumber(major), + minor = minor == "" and 0 or tonumber(minor), + patch = patch == "" and 0 or tonumber(patch), + prerelease = prerelease ~= "" and prerelease or nil, + build = build ~= "" and build or nil, + }, Semver) + end +end + +---@generic T: Semver +---@param versions T[] +---@return T? +function LazyM.last(versions) + local last = versions[1] + for i = 2, #versions do + if versions[i] > last then + last = versions[i] + end + end + return last +end + +---@class SemverRange +---@field from Semver +---@field to? Semver +local Range = {} + +---@param version string|Semver +function Range:matches(version) + if type(version) == "string" then + ---@diagnostic disable-next-line: cast-local-type + version = LazyM.parse(version) + end + if version then + if version.prerelease ~= self.from.prerelease then + return false + end + return version >= self.from and (self.to == nil or version < self.to) + end +end + +---@param spec string +function LazyM.range(spec) + if spec == "*" or spec == "" then + return setmetatable({ from = LazyM.parse("0.0.0") }, { __index = Range }) + end + + ---@type number? + local hyphen = spec:find(" - ", 1, true) + if hyphen then + local a = spec:sub(1, hyphen - 1) + local b = spec:sub(hyphen + 3) + local parts = vim.split(b, ".", { plain = true }) + local ra = LazyM.range(a) + local rb = LazyM.range(b) + return setmetatable({ + from = ra and ra.from, + to = rb and (#parts == 3 and rb.from or rb.to), + }, { __index = Range }) + end + ---@type string, string + local mods, version = spec:lower():match("^([%^=>~]*)(.*)$") + version = version:gsub("%.[%*x]", "") + local parts = vim.split(version:gsub("%-.*", ""), ".", { plain = true }) + if #parts < 3 and mods == "" then + mods = "~" + end + + local semver = LazyM.parse(version) + if semver then + local from = semver + local to = vim.deepcopy(semver) + if mods == "" or mods == "=" then + to.patch = to.patch + 1 + elseif mods == ">" then + from.patch = from.patch + 1 + to = nil + elseif mods == ">=" then + to = nil + elseif mods == "~" then + if #parts >= 2 then + to[2] = to[2] + 1 + to[3] = 0 + else + to[1] = to[1] + 1 + to[2] = 0 + to[3] = 0 + end + elseif mods == "^" then + for i = 1, 3 do + if to[i] ~= 0 then + to[i] = to[i] + 1 + for j = i + 1, 3 do + to[j] = 0 + end + break + end + end + end + return setmetatable({ from = from, to = to }, { __index = Range }) + end +end + ---@private ---@param version string ---@return string -- cgit From a715e6f87eede36775d0921b3537c7c57a82890a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 16 Mar 2023 22:49:12 +0100 Subject: refactor(vim.version): use lazy.nvim semver module Now the Nvim version string "v0.9.0-dev-1233+g210120dde81e" parses correctly. --- runtime/lua/vim/version.lua | 295 +++++++++++--------------------------------- 1 file changed, 70 insertions(+), 225 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index b409483755..e79acf079b 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -16,7 +16,7 @@ local Semver = {} Semver.__index = Semver function Semver:__index(key) - return type(key) == "number" and ({ self.major, self.minor, self.patch })[key] or Semver[key] + return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Semver[key] end function Semver:__newindex(key, value) @@ -42,12 +42,12 @@ function Semver:__eq(other) end function Semver:__tostring() - local ret = table.concat({ self.major, self.minor, self.patch }, ".") + local ret = table.concat({ self.major, self.minor, self.patch }, '.') if self.prerelease then - ret = ret .. "-" .. self.prerelease + ret = ret .. '-' .. self.prerelease end if self.build then - ret = ret .. "+" .. self.build + ret = ret .. '+' .. self.build end return ret end @@ -67,7 +67,7 @@ function Semver:__lt(other) if other.prerelease and not self.prerelease then return false end - return (self.prerelease or "") < (other.prerelease or "") + return (self.prerelease or '') < (other.prerelease or '') end ---@param other Semver @@ -76,23 +76,40 @@ function Semver:__le(other) end ---@param version string|number[] +---@param strict? boolean Reject "1.0", "0-x" or other non-conforming version strings ---@return Semver? -function LazyM.parse(version) - if type(version) == "table" then +function LazyM.version(version, strict) + if type(version) == 'table' then return setmetatable({ major = version[1] or 0, minor = version[2] or 0, patch = version[3] or 0, }, Semver) end - local major, minor, patch, prerelease, build = version:match("^v?(%d+)%.?(%d*)%.?(%d*)%-?([^+]*)+?(.*)$") - if major then + + local prerel = version:match('%-([^+]*)') + local prerel_strict = version:match('%-([0-9A-Za-z-]*)') + if + strict + and prerel + and (prerel_strict == nil or prerel_strict == '' or not vim.startswith(prerel, prerel_strict)) + then + return nil -- Invalid prerelease. + end + local build = prerel and version:match('%-[^+]*%+(.*)$') or version:match('%+(.*)$') + local major, minor, patch = + version:match('^v?(%d+)%.?(%d*)%.?(%d*)' .. (strict and (prerel and '%-' or '$') or '')) + + if + (not strict and major) + or (major and minor and patch and major ~= '' and minor ~= '' and patch ~= '') + then return setmetatable({ major = tonumber(major), - minor = minor == "" and 0 or tonumber(minor), - patch = patch == "" and 0 or tonumber(patch), - prerelease = prerelease ~= "" and prerelease or nil, - build = build ~= "" and build or nil, + minor = minor == '' and 0 or tonumber(minor), + patch = patch == '' and 0 or tonumber(patch), + prerelease = prerel ~= '' and prerel or nil, + build = build ~= '' and build or nil, }, Semver) end end @@ -100,7 +117,7 @@ end ---@generic T: Semver ---@param versions T[] ---@return T? -function LazyM.last(versions) +function M.last(versions) local last = versions[1] for i = 2, #versions do if versions[i] > last then @@ -117,9 +134,9 @@ local Range = {} ---@param version string|Semver function Range:matches(version) - if type(version) == "string" then + if type(version) == 'string' then ---@diagnostic disable-next-line: cast-local-type - version = LazyM.parse(version) + version = M.parse(version) end if version then if version.prerelease ~= self.from.prerelease then @@ -131,16 +148,16 @@ end ---@param spec string function LazyM.range(spec) - if spec == "*" or spec == "" then - return setmetatable({ from = LazyM.parse("0.0.0") }, { __index = Range }) + if spec == '*' or spec == '' then + return setmetatable({ from = M.parse('0.0.0') }, { __index = Range }) end ---@type number? - local hyphen = spec:find(" - ", 1, true) + local hyphen = spec:find(' - ', 1, true) if hyphen then local a = spec:sub(1, hyphen - 1) local b = spec:sub(hyphen + 3) - local parts = vim.split(b, ".", { plain = true }) + local parts = vim.split(b, '.', { plain = true }) local ra = LazyM.range(a) local rb = LazyM.range(b) return setmetatable({ @@ -149,25 +166,25 @@ function LazyM.range(spec) }, { __index = Range }) end ---@type string, string - local mods, version = spec:lower():match("^([%^=>~]*)(.*)$") - version = version:gsub("%.[%*x]", "") - local parts = vim.split(version:gsub("%-.*", ""), ".", { plain = true }) - if #parts < 3 and mods == "" then - mods = "~" + local mods, version = spec:lower():match('^([%^=>~]*)(.*)$') + version = version:gsub('%.[%*x]', '') + local parts = vim.split(version:gsub('%-.*', ''), '.', { plain = true }) + if #parts < 3 and mods == '' then + mods = '~' end - local semver = LazyM.parse(version) + local semver = M.parse(version) if semver then local from = semver local to = vim.deepcopy(semver) - if mods == "" or mods == "=" then + if mods == '' or mods == '=' then to.patch = to.patch + 1 - elseif mods == ">" then + elseif mods == '>' then from.patch = from.patch + 1 - to = nil - elseif mods == ">=" then - to = nil - elseif mods == "~" then + to = nil ---@diagnostic disable-line: cast-local-type + elseif mods == '>=' then + to = nil ---@diagnostic disable-line: cast-local-type + elseif mods == '~' then if #parts >= 2 then to[2] = to[2] + 1 to[3] = 0 @@ -176,7 +193,7 @@ function LazyM.range(spec) to[2] = 0 to[3] = 0 end - elseif mods == "^" then + elseif mods == '^' then for i = 1, 3 do if to[i] ~= 0 then to[i] = to[i] + 1 @@ -192,7 +209,7 @@ function LazyM.range(spec) end ---@private ----@param version string +---@param v string ---@return string local function create_err_msg(v) if type(v) == 'string' then @@ -203,188 +220,36 @@ end ---@private --- Throws an error if `version` cannot be parsed. ----@param version string -local function assert_version(version, opt) - local rv = M.parse(version, opt) +---@param v string +local function assert_version(v, opt) + local rv = M.parse(v, opt) if rv == nil then - error(create_err_msg(version)) + error(create_err_msg(v)) end return rv end ----@private ---- Compares the prerelease component of the two versions. -local function cmp_prerelease(v1, v2) - if v1.prerelease and not v2.prerelease then - return -1 - end - if not v1.prerelease and v2.prerelease then - return 1 - end - if not v1.prerelease and not v2.prerelease then - return 0 - end - - local v1_identifiers = vim.split(v1.prerelease, '.', { plain = true }) - local v2_identifiers = vim.split(v2.prerelease, '.', { plain = true }) - local i = 1 - local max = math.max(vim.tbl_count(v1_identifiers), vim.tbl_count(v2_identifiers)) - while i <= max do - local v1_identifier = v1_identifiers[i] - local v2_identifier = v2_identifiers[i] - if v1_identifier ~= v2_identifier then - local v1_num = tonumber(v1_identifier) - local v2_num = tonumber(v2_identifier) - local is_number = v1_num and v2_num - if is_number then - -- Number comparisons - if not v1_num and v2_num then - return -1 - end - if v1_num and not v2_num then - return 1 - end - if v1_num == v2_num then - return 0 - end - if v1_num > v2_num then - return 1 - end - if v1_num < v2_num then - return -1 - end - else - -- String comparisons - if v1_identifier and not v2_identifier then - return 1 - end - if not v1_identifier and v2_identifier then - return -1 - end - if v1_identifier < v2_identifier then - return -1 - end - if v1_identifier > v2_identifier then - return 1 - end - if v1_identifier == v2_identifier then - return 0 - end - end - end - i = i + 1 - end - - return 0 -end - ----@private -local function cmp_version_core(v1, v2) - if v1.major == v2.major and v1.minor == v2.minor and v1.patch == v2.patch then - return 0 - end - if - v1.major > v2.major - or (v1.major == v2.major and v1.minor > v2.minor) - or (v1.major == v2.major and v1.minor == v2.minor and v1.patch > v2.patch) - then - return 1 - end - return -1 -end - ---- Compares two strings (`v1` and `v2`) in semver format. +--- Parses and compares two version strings. +--- +--- semver notes: +--- - Build metadata MUST be ignored when comparing versions. +--- ---@param v1 string Version. ---@param v2 string Version to compare with v1. ---@param opts table|nil Optional keyword arguments: ---- - strict (boolean): see `semver.parse` for details. Defaults to false. +--- - strict (boolean): see `version.parse` for details. Defaults to false. ---@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. function M.cmp(v1, v2, opts) opts = opts or { strict = false } local v1_parsed = assert_version(v1, opts) local v2_parsed = assert_version(v2, opts) - - local result = cmp_version_core(v1_parsed, v2_parsed) - if result == 0 then - result = cmp_prerelease(v1_parsed, v2_parsed) - end - return result -end - ----@private ----@param labels string Prerelease and build component of semantic version string e.g. "-rc1+build.0". ----@return string|nil -local function parse_prerelease(labels) - -- This pattern matches "-(alpha)+build.15". - -- '^%-[%w%.]+$' - local result = labels:match('^%-([%w%.]+)+.+$') - if result then - return result - end - -- This pattern matches "-(alpha)". - result = labels:match('^%-([%w%.]+)') - if result then - return result - end - - return nil -end - ----@private ----@param labels string Prerelease and build component of semantic version string e.g. "-rc1+build.0". ----@return string|nil -local function parse_build(labels) - -- Pattern matches "-alpha+(build.15)". - local result = labels:match('^%-[%w%.]+%+([%w%.]+)$') - if result then - return result - end - - -- Pattern matches "+(build.15)". - result = labels:match('^%+([%w%.]+)$') - if result then - return result + if v1_parsed == v2_parsed then + return 0 end - - return nil -end - ----@private ---- Extracts the major, minor, patch and preprelease and build components from ---- `version`. ----@param version string Version string -local function extract_components_strict(version) - local major, minor, patch, prerelease_and_build = version:match('^v?(%d+)%.(%d+)%.(%d+)(.*)$') - return tonumber(major), tonumber(minor), tonumber(patch), prerelease_and_build -end - ----@private ---- Extracts the major, minor, patch and preprelease and build components from ---- `version`. When `minor` and `patch` components are not found (nil), coerce ---- them to 0. ----@param version string Version string -local function extract_components_loose(version) - local major, minor, patch, prerelease_and_build = version:match('^v?(%d+)%.?(%d*)%.?(%d*)(.*)$') - major = tonumber(major) - minor = tonumber(minor) or 0 - patch = tonumber(patch) or 0 - return major, minor, patch, prerelease_and_build -end - ----@private ---- Validates the prerelease and build string e.g. "-rc1+build.0". If the ---- prerelease, build or both are valid forms then it will return true, if it ---- is not of any valid form, it will return false. ----@param prerelease_and_build string ----@return boolean -local function is_prerelease_and_build_valid(prerelease_and_build) - if prerelease_and_build == '' then - return true + if v1_parsed > v2_parsed then + return 1 end - local has_build = parse_build(prerelease_and_build) ~= nil - local has_prerelease = parse_prerelease(prerelease_and_build) ~= nil - local has_prerelease_and_build = has_prerelease and has_build - return has_build or has_prerelease or has_prerelease_and_build + return -1 end --- Parses a semantic version string. @@ -405,34 +270,14 @@ function M.parse(version, opts) if type(version) ~= 'string' then error(create_err_msg(version)) end - opts = opts or { strict = false } - version = vim.trim(version) - - local extract_components = opts.strict and extract_components_strict or extract_components_loose - local major, minor, patch, prerelease_and_build = extract_components(version) - - -- If major is nil then that means that the version does not begin with a - -- digit with or without a "v" prefix. - if major == nil or not is_prerelease_and_build_valid(prerelease_and_build) then - return nil - end - - local prerelease = nil - local build = nil - if prerelease_and_build ~= nil then - prerelease = parse_prerelease(prerelease_and_build) - build = parse_build(prerelease_and_build) + if opts.strict then + return LazyM.version(version, true) end - return { - major = major, - minor = minor, - patch = patch, - prerelease = prerelease, - build = build, - } + version = vim.trim(version) -- TODO: add more "scrubbing". + return LazyM.version(version, false) end ---Returns `true` if `v1` are `v2` are equal versions. -- cgit From a40eb7cc991eb4f8b89f467e8e42563868efa76b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 17 Mar 2023 01:12:33 +0100 Subject: feat(vim.version): more coercion with strict=false Problem: "tmux 3.2a" (output from "tmux -V") is not parsed easily. Solution: With `strict=false`, discard everything before the first digit. - rename Semver => Version - rename vim.version.version() => vim.version._version() - rename matches() => has() - remove `opts` from cmp() --- runtime/lua/vim/version.lua | 267 +++++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 93 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index e79acf079b..8d8b0d6da7 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -1,9 +1,59 @@ -local M = {} +--- @defgroup lua-version +--- +--- @brief The \`vim.version\` module provides functions for comparing versions and ranges +--- conforming to the https://semver.org spec. Plugins, and plugin managers, can use this to check +--- available tools and dependencies on the current system. +--- +--- Example: +---
lua
+---   local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false})
+---   if vim.version.gt(v, {3, 2, 0}) then
+---     -- ...
+---   end
+---   
+--- +--- \*vim.version()\* returns the version of the current Nvim process. +--- +--- VERSION RANGE SPEC \*version-range\* +--- +--- A version "range spec" defines a semantic version range which can be tested against a version, +--- using |vim.version.range()|. +--- +--- Supported range specs are shown in the following table. +--- Note: suffixed versions (1.2.3-rc1) are not matched. +---
+---   1.2.3             is 1.2.3
+---   =1.2.3            is 1.2.3
+---   >1.2.3            greater than 1.2.3
+---   <1.2.3            before 1.2.3
+---   >=1.2.3           at least 1.2.3
+---   ~1.2.3            is >=1.2.3 <1.3.0       "reasonably close to 1.2.3"
+---   ^1.2.3            is >=1.2.3 <2.0.0       "compatible with 1.2.3"
+---   ^0.2.3            is >=0.2.3 <0.3.0       (0.x.x is special)
+---   ^0.0.1            is =0.0.1               (0.0.x is special)
+---   ^1.2              is >=1.2.0 <2.0.0       (like ^1.2.0)
+---   ~1.2              is >=1.2.0 <1.3.0       (like ~1.2.0)
+---   ^1                is >=1.0.0 <2.0.0       "compatible with 1"
+---   ~1                same                    "reasonably close to 1"
+---   1.x               same
+---   1.*               same
+---   1                 same
+---   *                 any version
+---   x                 same
+---
+---   1.2.3 - 2.3.4     is >=1.2.3 <=2.3.4
+---
+---   Partial right: missing pieces treated as x (2.3 => 2.3.x).
+---   1.2.3 - 2.3       is >=1.2.3 <2.4.0
+---   1.2.3 - 2         is >=1.2.3 <3.0.0
+---
+---   Partial left: missing pieces treated as 0 (1.2 => 1.2.0).
+---   1.2 - 2.3.0       is 1.2.0 - 2.3.0
+---   
-local LazyM = {} -M.LazyM = LazyM +local M = {} ----@class Semver +---@class Version ---@field [1] number ---@field [2] number ---@field [3] number @@ -12,14 +62,14 @@ M.LazyM = LazyM ---@field patch number ---@field prerelease? string ---@field build? string -local Semver = {} -Semver.__index = Semver +local Version = {} +Version.__index = Version -function Semver:__index(key) - return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Semver[key] +function Version:__index(key) + return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Version[key] end -function Semver:__newindex(key, value) +function Version:__newindex(key, value) if key == 1 then self.major = value elseif key == 2 then @@ -31,8 +81,8 @@ function Semver:__newindex(key, value) end end ----@param other Semver -function Semver:__eq(other) +---@param other Version +function Version:__eq(other) for i = 1, 3 do if self[i] ~= other[i] then return false @@ -41,7 +91,7 @@ function Semver:__eq(other) return self.prerelease == other.prerelease end -function Semver:__tostring() +function Version:__tostring() local ret = table.concat({ self.major, self.minor, self.patch }, '.') if self.prerelease then ret = ret .. '-' .. self.prerelease @@ -52,8 +102,8 @@ function Semver:__tostring() return ret end ----@param other Semver -function Semver:__lt(other) +---@param other Version +function Version:__lt(other) for i = 1, 3 do if self[i] > other[i] then return false @@ -70,21 +120,32 @@ function Semver:__lt(other) return (self.prerelease or '') < (other.prerelease or '') end ----@param other Semver -function Semver:__le(other) +---@param other Version +function Version:__le(other) return self < other or self == other end ----@param version string|number[] ----@param strict? boolean Reject "1.0", "0-x" or other non-conforming version strings ----@return Semver? -function LazyM.version(version, strict) +--- @private +--- +--- Creates a new Version object. Not public currently. +--- +--- @param version string|number[]|Version +--- @param strict? boolean Reject "1.0", "0-x", "3.2a" or other non-conforming version strings +--- @return Version? +function M._version(version, strict) -- Adapted from https://github.com/folke/lazy.nvim if type(version) == 'table' then + if version.major then + return setmetatable(vim.deepcopy(version), Version) + end return setmetatable({ major = version[1] or 0, minor = version[2] or 0, patch = version[3] or 0, - }, Semver) + }, Version) + end + + if not strict then -- TODO: add more "scrubbing". + version = version:match('%d[^ ]*') end local prerel = version:match('%-([^+]*)') @@ -110,11 +171,13 @@ function LazyM.version(version, strict) patch = patch == '' and 0 or tonumber(patch), prerelease = prerel ~= '' and prerel or nil, build = build ~= '' and build or nil, - }, Semver) + }, Version) end end ----@generic T: Semver +---TODO: generalize this, move to func.lua +--- +---@generic T: Version ---@param versions T[] ---@return T? function M.last(versions) @@ -127,13 +190,15 @@ function M.last(versions) return last end ----@class SemverRange ----@field from Semver ----@field to? Semver +---@class Range +---@field from Version +---@field to? Version local Range = {} ----@param version string|Semver -function Range:matches(version) +--- @private +--- +---@param version string|Version +function Range:has(version) if type(version) == 'string' then ---@diagnostic disable-next-line: cast-local-type version = M.parse(version) @@ -146,8 +211,32 @@ function Range:matches(version) end end ----@param spec string -function LazyM.range(spec) +--- Parses a semver |version-range| "spec" and returns a range object: +---
+---   {
+---     from: Version
+---     to: Version
+---     has(v: string|Version)
+---   }
+---   
+--- +--- `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`). Example: +---
lua
+---   local r = vim.version.range('1.0.0 - 2.0.0')
+---   print(r:has('1.9.9'))  -- true
+---   print(r:has('2.0.0'))  -- false
+---   
+--- +--- Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly: +---
lua
+---   local r = vim.version.range('1.0.0 - 2.0.0')
+---   print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to))
+---   
+--- +--- @see # https://github.com/npm/node-semver#ranges +--- +--- @param spec string Version range "spec" +function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim if spec == '*' or spec == '' then return setmetatable({ from = M.parse('0.0.0') }, { __index = Range }) end @@ -158,8 +247,8 @@ function LazyM.range(spec) local a = spec:sub(1, hyphen - 1) local b = spec:sub(hyphen + 3) local parts = vim.split(b, '.', { plain = true }) - local ra = LazyM.range(a) - local rb = LazyM.range(b) + local ra = M.range(a) + local rb = M.range(b) return setmetatable({ from = ra and ra.from, to = rb and (#parts == 3 and rb.from or rb.to), @@ -209,40 +298,40 @@ function LazyM.range(spec) end ---@private ----@param v string +---@param v string|Version ---@return string local function create_err_msg(v) if type(v) == 'string' then return string.format('invalid version: "%s"', tostring(v)) + elseif type(v) == 'table' and v.major then + return string.format('invalid version: %s', vim.inspect(v)) end return string.format('invalid version: %s (%s)', tostring(v), type(v)) end ----@private ---- Throws an error if `version` cannot be parsed. ----@param v string -local function assert_version(v, opt) - local rv = M.parse(v, opt) - if rv == nil then - error(create_err_msg(v)) - end - return rv -end - ---- Parses and compares two version strings. +--- Parses and compares two version version objects (the result of |vim.version.parse()|, or +--- specified literally as a `{major, minor, patch}` tuple, e.g. `{1, 0, 3}`). --- ---- semver notes: ---- - Build metadata MUST be ignored when comparing versions. +--- Example: +---
lua
+---   if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then
+---     -- ...
+---   end
+---   local v1 = vim.version.parse('1.0.3-pre')
+---   local v2 = vim.version.parse('0.2.1')
+---   if vim.version.cmp(v1, v2) == 0 then
+---     -- ...
+---   end
+--- 
--- ----@param v1 string Version. ----@param v2 string Version to compare with v1. ----@param opts table|nil Optional keyword arguments: ---- - strict (boolean): see `version.parse` for details. Defaults to false. ----@return integer `-1` if `v1 < v2`, `0` if `v1 == v2`, `1` if `v1 > v2`. -function M.cmp(v1, v2, opts) - opts = opts or { strict = false } - local v1_parsed = assert_version(v1, opts) - local v2_parsed = assert_version(v2, opts) +--- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions. +--- +---@param v1 Version|number[] Version object. +---@param v2 Version|number[] Version to compare with `v1`. +---@return integer -1 if `v1 < v2`, 0 if `v1 == v2`, 1 if `v1 > v2`. +function M.cmp(v1, v2) + local v1_parsed = assert(M._version(v1), create_err_msg(v1)) + local v2_parsed = assert(M._version(v2), create_err_msg(v1)) if v1_parsed == v2_parsed then return 0 end @@ -252,58 +341,50 @@ function M.cmp(v1, v2, opts) return -1 end ---- Parses a semantic version string. ---- ---- Ignores leading "v" and surrounding whitespace, e.g. " v1.0.1-rc1+build.2", ---- "1.0.1-rc1+build.2", "v1.0.1-rc1+build.2" and "v1.0.1-rc1+build.2 " are all parsed as: ----
----   { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" }
---- 
---- ----@param version string Version string to be parsed. ----@param opts table|nil Optional keyword arguments: ---- - strict (boolean): Default false. If `true`, no coercion is attempted on ---- input not strictly conforming to semver v2.0.0 ---- (https://semver.org/spec/v2.0.0.html). E.g. `parse("v1.2")` returns nil. ----@return table|nil parsed_version Parsed version table or `nil` if `version` is invalid. -function M.parse(version, opts) - if type(version) ~= 'string' then - error(create_err_msg(version)) - end - opts = opts or { strict = false } - - if opts.strict then - return LazyM.version(version, true) - end - - version = vim.trim(version) -- TODO: add more "scrubbing". - return LazyM.version(version, false) -end - ----Returns `true` if `v1` are `v2` are equal versions. ----@param v1 string ----@param v2 string +---Returns `true` if the given versions are equal. +---@param v1 Version|number[] +---@param v2 Version|number[] ---@return boolean function M.eq(v1, v2) return M.cmp(v1, v2) == 0 end ----Returns `true` if `v1` is less than `v2`. ----@param v1 string ----@param v2 string +---Returns `true` if `v1 < v2`. +---@param v1 Version|number[] +---@param v2 Version|number[] ---@return boolean function M.lt(v1, v2) return M.cmp(v1, v2) == -1 end ----Returns `true` if `v1` is greater than `v2`. ----@param v1 string ----@param v2 string +---Returns `true` if `v1 > v2`. +---@param v1 Version|number[] +---@param v2 Version|number[] ---@return boolean function M.gt(v1, v2) return M.cmp(v1, v2) == 1 end +--- Parses a semantic version string and returns a version object which can be used with other +--- `vim.version` functions. For example "1.0.1-rc1+build.2" returns: +---
+---   { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" }
+--- 
+--- +--- @see # https://semver.org/spec/v2.0.0.html +--- +---@param version string Version string to parse. +---@param opts table|nil Optional keyword arguments: +--- - strict (boolean): Default false. If `true`, no coercion is attempted on +--- input not conforming to semver v2.0.0. If `false`, `parse()` attempts to +--- coerce input such as "1.0", "0-x", "tmux 3.2a" into valid versions. +---@return table|nil parsed_version Version object or `nil` if input is invalid. +function M.parse(version, opts) + assert(type(version) == 'string', create_err_msg(version)) + opts = opts or { strict = false } + return M._version(version, opts.strict) +end + setmetatable(M, { __call = function() return vim.fn.api_info().version -- cgit From d11986171b4a5ddd950053e2b9a26d2ad8e89a5c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 20 Mar 2023 21:07:09 +0100 Subject: vim-patch:9.0.1415: Crystal files are not recognized Problem: Crystal files are not recognized. Solution: Add a pattern for Crystal files. (Amaan Qureshi, closes vim/vim#12175) https://github.com/vim/vim/commit/7c4516fe93d865f4ef1877181f8156b8b9331856 Co-authored-by: Amaan Qureshi --- 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 69fcb01e8c..399760b254 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -265,6 +265,7 @@ local extension = { end, cql = 'cqlang', crm = 'crm', + cr = 'crystal', csx = 'cs', cs = 'cs', csc = 'csc', -- cgit From 0c9ed19af3be45d679d77460dd5f6833752f9f56 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 20 Mar 2023 21:07:55 +0100 Subject: vim-patch:9.0.1417: ESDL files are not recognized Problem: ESDL files are not recognized. Solution: Add a pattern for ESDL files. (Amaan Qureshi, closes vim/vim#12174) https://github.com/vim/vim/commit/a1fa8b3ac2f7b54c7901ad3eb0ba8b8290912509 Co-authored-by: Amaan Qureshi --- 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 399760b254..5127474765 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -350,6 +350,7 @@ local extension = { yaws = 'erlang', erb = 'eruby', rhtml = 'eruby', + esdl = 'esdl', ec = 'esqlc', EC = 'esqlc', strl = 'esterel', -- cgit From 48ce2ef912cbbe43b85b45a8a97f8d240318718a Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Wed, 22 Mar 2023 04:19:56 -0400 Subject: vim-patch:9.0.{1419,1421,1422,1423}: some files are not recognized (#22749) vim-patch:9.0.1419: Lean files are not recognized Problem: Lean files are not recognized. Solution: Add a pattern for Lean files. (Amaan Qureshi, closes vim/vim#12177) https://github.com/vim/vim/commit/4a5c39fc52a73d46ac77cf574f765a465befc3c0 vim-patch:9.0.1421: Nu files are not recognized Problem: Nu files are not recognized. Solution: Add a pattern for Nu files. (Amaan Qureshi, closes vim/vim#12172) https://github.com/vim/vim/commit/8aa2a37f8983a7863afa32d9ffbe64b2f2bc70be vim-patch:9.0.1422: Sage files are not recognized Problem: Sage files are not recognized. Solution: Add a pattern for Sage files. (Amaan Qureshi, closes vim/vim#12176) https://github.com/vim/vim/commit/d0639d717ba7cf8b04b588aadd0b379ed43a5f1f vim-patch:9.0.1423: WebAssembly Interface Type files are not recognized Problem: WebAssembly Interface Type files are not recognized. Solution: Add a pattern for WIT files. (Amaan Qureshi, closes vim/vim#12173) https://github.com/vim/vim/commit/890c77203637626b1005db818667084d11e653e7 --- 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 5127474765..fa4208e2a4 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -593,6 +593,7 @@ local extension = { lte = 'latte', ld = 'ld', ldif = 'ldif', + lean = 'lean', journal = 'ledger', ldg = 'ledger', ledger = 'ledger', @@ -908,6 +909,7 @@ local extension = { builder = 'ruby', rake = 'ruby', rs = 'rust', + sage = 'sage', sas = 'sas', sass = 'sass', sa = 'sather', @@ -1106,6 +1108,7 @@ local extension = { wdl = 'wdl', wm = 'webmacro', wbt = 'winbatch', + wit = 'wit', wml = 'wml', wsml = 'wsml', ad = 'xdefaults', @@ -1543,6 +1546,8 @@ local filename = { NEWS = function(path, bufnr) return require('vim.filetype.detect').news(bufnr) end, + ['env.nu'] = 'nu', + ['config.nu'] = 'nu', ['.ocamlinit'] = 'ocaml', ['.octaverc'] = 'octave', octaverc = 'octave', -- cgit From 9c49c1047079427ff0a2356cb37302934845108e Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 20 Mar 2023 08:12:33 +0100 Subject: feat(vim.gsplit): gain features of vim.split Problem: - vim.split has more features than vim.gsplit. - Cannot inspect the "separator" segments of vim.split or vim.gsplit. Solution: - Move common implementation from vim.split into vim.gsplit. - TODO: deprecate vim.split in favor of vim.totable(vim.gsplit())? - Introduce `keepsep` parameter. Related: 84f66909e4008a57da947f1640bfc24da5e41a72 --- runtime/lua/vim/shared.lua | 122 ++++++++++++++++++++++++++++----------------- 1 file changed, 77 insertions(+), 45 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 1c8defc93a..95ddb3c70d 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -59,6 +59,13 @@ end)() --- Splits a string at each instance of a separator. --- +--- Example: +---
lua
+---   for s in vim.gsplit(':aa::b:', ':', {plain=true}) do
+---     print(s)
+---   end
+---   
+--- ---@see |vim.split()| ---@see |luaref-patterns| ---@see https://www.lua.org/pil/20.2.html @@ -66,17 +73,40 @@ end)() --- ---@param s string String to split ---@param sep string Separator or pattern ----@param plain (boolean|nil) If `true` use `sep` literally (passed to string.find) ----@return fun():string (function) Iterator over the split components -function vim.gsplit(s, sep, plain) - vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } }) +---@param opts (table|nil) Keyword arguments |kwargs|: +--- - keepsep: (boolean) Include segments matching `sep` instead of discarding them. +--- - plain: (boolean) Use `sep` literally (as in string.find). +--- - trimempty: (boolean) Discard empty segments at start and end of the sequence. +---@return fun():string|nil (function) Iterator over the split components +function vim.gsplit(s, sep, opts) + local plain + local trimempty = false + local keepsep = false + if type(opts) == 'boolean' then + plain = opts -- For backwards compatibility. + else + vim.validate({ s = { s, 's' }, sep = { sep, 's' }, opts = { opts, 't', true } }) + opts = opts or {} + plain, trimempty, keepsep = opts.plain, opts.trimempty, opts.keepsep + assert(not trimempty or not keepsep, 'keepsep+trimempty not supported') + end local start = 1 local done = false + local sepseg = nil -- Last matched `sep` segment. + local sepesc = plain and vim.pesc(sep) or sep + + -- For `trimempty`: + local empty_start = true -- Only empty segments seen so far. + local empty_segs = 0 -- Empty segments found between non-empty segments. + local nonemptyseg = nil local function _pass(i, j, ...) if i then assert(j + 1 > start, 'Infinite loop detected') + if keepsep then + sepseg = s:match(sepesc, start) + end local seg = s:sub(start, i - 1) start = j + 1 return seg, ... @@ -87,16 +117,48 @@ function vim.gsplit(s, sep, plain) end return function() - if done or (s == '' and sep == '') then - return - end - if sep == '' then + if trimempty and empty_segs > 0 then + -- trimempty: Pop the collected empty segments. + empty_segs = empty_segs - 1 + return '' + elseif trimempty and nonemptyseg then + local seg = nonemptyseg + nonemptyseg = nil + return seg + elseif keepsep and sepseg then + local seg = sepseg + sepseg = nil + return seg + elseif done or (s == '' and sep == '') then + return nil + elseif sep == '' then if start == #s then done = true end return _pass(start + 1, start) end - return _pass(s:find(sep, start, plain)) + + local seg = _pass(s:find(sep, start, plain)) + + -- Trim empty segments from start/end. + if trimempty and seg == '' then + while not done and seg == '' do + empty_segs = empty_segs + 1 + seg = _pass(s:find(sep, start, plain)) + end + if done and seg == '' then + return nil + elseif empty_start then + empty_start = false + empty_segs = 0 + return seg + end + nonemptyseg = seg ~= '' and seg or nil + seg = '' + empty_segs = empty_segs - 1 + end + + return seg end end @@ -108,51 +170,21 @@ end --- split("axaby", "ab?") --> {'','x','y'} --- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} --- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} +--- split("|x|y|z|", "|", {keepsep=true}) --> {'|', 'x', '|', 'y', '|', 'z', '|'} ---
--- ---@see |vim.gsplit()| --- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs (table|nil) Keyword arguments: ---- - plain: (boolean) If `true` use `sep` literally (passed to string.find) ---- - trimempty: (boolean) If `true` remove empty items from the front ---- and back of the list +---@param opts (table|nil) Keyword arguments |kwargs| accepted by |vim.gsplit()| ---@return string[] List of split components -function vim.split(s, sep, kwargs) - local plain - local trimempty = false - if type(kwargs) == 'boolean' then - -- Support old signature for backward compatibility - plain = kwargs - else - vim.validate({ kwargs = { kwargs, 't', true } }) - kwargs = kwargs or {} - plain = kwargs.plain - trimempty = kwargs.trimempty - end - +function vim.split(s, sep, opts) + -- TODO(justinmk): deprecate vim.split in favor of vim.totable(vim.gsplit()) local t = {} - local skip = trimempty - for c in vim.gsplit(s, sep, plain) do - if c ~= '' then - skip = false - end - - if not skip then - table.insert(t, c) - end + for c in vim.gsplit(s, sep, opts) do + table.insert(t, c) end - - if trimempty then - for i = #t, 1, -1 do - if t[i] ~= '' then - break - end - table.remove(t, i) - end - end - return t end -- cgit From 8a70adbde03ee9931dc4e1b6f31bd8635eb3633b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 20 Mar 2023 13:36:06 +0100 Subject: fix(vim.version): prerelease compare Problem: semver specifies that digit sequences in a prerelease string should be compared as numbers, not lexically: https://semver.org/#spec-item-11 > Precedence for two pre-release versions with the same major, minor, > and patch version MUST be determined by comparing each dot separated > identifier from left to right until a difference is found as follows: > 1. Identifiers consisting of only digits are compared numerically. > 2. Identifiers with letters or hyphens are compared lexically in ASCII sort order. > 3. Numeric identifiers always have lower precedence than non-numeric identifiers. > 4. A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal. Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0. Solution: cmp_prerel() treats all digit sequences in a prerelease string as numbers. This doesn't _exactly_ match the spec, which specifies that only dot-delimited digit sequences should be treated as numbers... --- runtime/lua/vim/shared.lua | 17 +++++++++-------- runtime/lua/vim/version.lua | 46 +++++++++++++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 20 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 95ddb3c70d..9eb49cdfac 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -66,14 +66,15 @@ end)() --- end ---
--- ----@see |vim.split()| ----@see |luaref-patterns| ----@see https://www.lua.org/pil/20.2.html ----@see http://lua-users.org/wiki/StringLibraryTutorial ---- ----@param s string String to split ----@param sep string Separator or pattern ----@param opts (table|nil) Keyword arguments |kwargs|: +--- @see |string.gmatch()| +--- @see |vim.split()| +--- @see |luaref-patterns| +--- @see https://www.lua.org/pil/20.2.html +--- @see http://lua-users.org/wiki/StringLibraryTutorial +--- +--- @param s string String to split +--- @param sep string Separator or pattern +--- @param opts (table|nil) Keyword arguments |kwargs|: --- - keepsep: (boolean) Include segments matching `sep` instead of discarding them. --- - plain: (boolean) Use `sep` literally (as in string.find). --- - trimempty: (boolean) Discard empty segments at start and end of the sequence. diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 8d8b0d6da7..43001c195c 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -65,6 +65,33 @@ local M = {} local Version = {} Version.__index = Version +--- Compares prerelease strings: per semver, number parts must be must be treated as numbers: +--- "pre1.10" is greater than "pre1.2". https://semver.org/#spec-item-11 +local function cmp_prerel(prerel1, prerel2) + if not prerel1 or not prerel2 then + return prerel1 and -1 or (prerel2 and 1 or 0) + end + -- TODO(justinmk): not fully spec-compliant; this treats non-dot-delimited digit sequences as + -- numbers. Maybe better: "(.-)(%.%d*)". + local iter1 = prerel1:gmatch('([^0-9]*)(%d*)') + local iter2 = prerel2:gmatch('([^0-9]*)(%d*)') + while true do + local word1, n1 = iter1() + local word2, n2 = iter2() + if word1 == nil and word2 == nil then -- Done iterating. + return 0 + end + word1, n1, word2, n2 = + word1 or '', n1 and tonumber(n1) or 0, word2 or '', n2 and tonumber(n2) or 0 + if word1 ~= word2 then + return word1 < word2 and -1 or 1 + end + if n1 ~= n2 then + return n1 < n2 and -1 or 1 + end + end +end + function Version:__index(key) return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Version[key] end @@ -88,7 +115,7 @@ function Version:__eq(other) return false end end - return self.prerelease == other.prerelease + return 0 == cmp_prerel(self.prerelease, other.prerelease) end function Version:__tostring() @@ -111,13 +138,7 @@ function Version:__lt(other) return true end end - if self.prerelease and not other.prerelease then - return true - end - if other.prerelease and not self.prerelease then - return false - end - return (self.prerelease or '') < (other.prerelease or '') + return -1 == cmp_prerel(self.prerelease, other.prerelease) end ---@param other Version @@ -127,7 +148,7 @@ end --- @private --- ---- Creates a new Version object. Not public currently. +--- Creates a new Version object, or returns `nil` if `version` is invalid. --- --- @param version string|number[]|Version --- @param strict? boolean Reject "1.0", "0-x", "3.2a" or other non-conforming version strings @@ -173,6 +194,7 @@ function M._version(version, strict) -- Adapted from https://github.com/folke/la build = build ~= '' and build or nil, }, Version) end + return nil -- Invalid version string. end ---TODO: generalize this, move to func.lua @@ -341,7 +363,7 @@ function M.cmp(v1, v2) return -1 end ----Returns `true` if the given versions are equal. +---Returns `true` if the given versions are equal. See |vim.version.cmp()| for usage. ---@param v1 Version|number[] ---@param v2 Version|number[] ---@return boolean @@ -349,7 +371,7 @@ function M.eq(v1, v2) return M.cmp(v1, v2) == 0 end ----Returns `true` if `v1 < v2`. +---Returns `true` if `v1 < v2`. See |vim.version.cmp()| for usage. ---@param v1 Version|number[] ---@param v2 Version|number[] ---@return boolean @@ -357,7 +379,7 @@ function M.lt(v1, v2) return M.cmp(v1, v2) == -1 end ----Returns `true` if `v1 > v2`. +---Returns `true` if `v1 > v2`. See |vim.version.cmp()| for usage. ---@param v1 Version|number[] ---@param v2 Version|number[] ---@return boolean -- cgit From e51139f5c1d70bef1424f29e63eb527514e42865 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 22 Mar 2023 15:14:51 +0100 Subject: refactor(vim.gsplit): remove "keepsep" string.gmatch() is superior, use that instead. --- runtime/lua/vim/shared.lua | 25 ++++++++++--------------- runtime/lua/vim/version.lua | 2 ++ 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 9eb49cdfac..884929e33a 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -66,6 +66,14 @@ end)() --- end --- --- +--- If you want to also inspect the separator itself (instead of discarding it), use +--- |string.gmatch()|. Example: +---
lua
+---   for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do
+---     print(('word: %s num: %s'):format(word, num))
+---   end
+---   
+--- --- @see |string.gmatch()| --- @see |vim.split()| --- @see |luaref-patterns| @@ -75,27 +83,22 @@ end)() --- @param s string String to split --- @param sep string Separator or pattern --- @param opts (table|nil) Keyword arguments |kwargs|: ---- - keepsep: (boolean) Include segments matching `sep` instead of discarding them. --- - plain: (boolean) Use `sep` literally (as in string.find). --- - trimempty: (boolean) Discard empty segments at start and end of the sequence. ---@return fun():string|nil (function) Iterator over the split components function vim.gsplit(s, sep, opts) local plain local trimempty = false - local keepsep = false if type(opts) == 'boolean' then plain = opts -- For backwards compatibility. else vim.validate({ s = { s, 's' }, sep = { sep, 's' }, opts = { opts, 't', true } }) opts = opts or {} - plain, trimempty, keepsep = opts.plain, opts.trimempty, opts.keepsep - assert(not trimempty or not keepsep, 'keepsep+trimempty not supported') + plain, trimempty = opts.plain, opts.trimempty end local start = 1 local done = false - local sepseg = nil -- Last matched `sep` segment. - local sepesc = plain and vim.pesc(sep) or sep -- For `trimempty`: local empty_start = true -- Only empty segments seen so far. @@ -105,9 +108,6 @@ function vim.gsplit(s, sep, opts) local function _pass(i, j, ...) if i then assert(j + 1 > start, 'Infinite loop detected') - if keepsep then - sepseg = s:match(sepesc, start) - end local seg = s:sub(start, i - 1) start = j + 1 return seg, ... @@ -126,10 +126,6 @@ function vim.gsplit(s, sep, opts) local seg = nonemptyseg nonemptyseg = nil return seg - elseif keepsep and sepseg then - local seg = sepseg - sepseg = nil - return seg elseif done or (s == '' and sep == '') then return nil elseif sep == '' then @@ -171,17 +167,16 @@ end --- split("axaby", "ab?") --> {'','x','y'} --- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'} --- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'} ---- split("|x|y|z|", "|", {keepsep=true}) --> {'|', 'x', '|', 'y', '|', 'z', '|'} --- --- ---@see |vim.gsplit()| +---@see |string.gmatch()| --- ---@param s string String to split ---@param sep string Separator or pattern ---@param opts (table|nil) Keyword arguments |kwargs| accepted by |vim.gsplit()| ---@return string[] List of split components function vim.split(s, sep, opts) - -- TODO(justinmk): deprecate vim.split in favor of vim.totable(vim.gsplit()) local t = {} for c in vim.gsplit(s, sep, opts) do table.insert(t, c) diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 43001c195c..3aacf3d4e0 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -65,6 +65,8 @@ local M = {} local Version = {} Version.__index = Version +--- @private +--- --- Compares prerelease strings: per semver, number parts must be must be treated as numbers: --- "pre1.10" is greater than "pre1.2". https://semver.org/#spec-item-11 local function cmp_prerel(prerel1, prerel2) -- cgit From 28cfcf51269b7390b447c8a5b61d5083c830e834 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 22 Mar 2023 16:49:04 +0000 Subject: fix(api): vim.filetype.get_option() (#22753) - Fix a bug in the cache - Set some buffer options on the dummy buffer --- runtime/lua/vim/filetype/options.lua | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype/options.lua b/runtime/lua/vim/filetype/options.lua index 77a66991d5..a093c249f7 100644 --- a/runtime/lua/vim/filetype/options.lua +++ b/runtime/lua/vim/filetype/options.lua @@ -3,7 +3,7 @@ local api = vim.api local M = {} local function get_ftplugin_runtime(filetype) - return api.nvim__get_runtime({ + local files = api.nvim__get_runtime({ string.format('ftplugin/%s.vim', filetype), string.format('ftplugin/%s_*.vim', filetype), string.format('ftplugin/%s/*.vim', filetype), @@ -11,6 +11,15 @@ local function get_ftplugin_runtime(filetype) string.format('ftplugin/%s_*.lua', filetype), string.format('ftplugin/%s/*.lua', filetype), }, true, {}) --[[@as string[] ]] + + local r = {} ---@type string[] + for _, f in ipairs(files) do + -- VIMRUNTIME should be static so shouldn't need to worry about it changing + if not vim.startswith(f, vim.env.VIMRUNTIME) then + r[#r + 1] = f + end + end + return r end -- Keep track of ftplugin files @@ -52,14 +61,11 @@ local function update_ft_option_cache(filetype) end for _, f in ipairs(files) do - -- VIMRUNTIME should be static so shouldn't need to worry about it changing - if not vim.startswith(f, vim.env.VIMRUNTIME) then - local mtime = hash(f) - if ftplugin_cache[filetype][f] ~= mtime then - -- invalidate - ft_option_cache[filetype] = nil - ftplugin_cache[filetype][f] = mtime - end + local mtime = hash(f) + if ftplugin_cache[filetype][f] ~= mtime then + -- invalidate + ft_option_cache[filetype] = nil + ftplugin_cache[filetype][f] = mtime end end end -- cgit From ea0b66d208dbcd5d5c0a17810596d769c7a0b6dd Mon Sep 17 00:00:00 2001 From: meredith Date: Thu, 23 Mar 2023 02:27:53 -0500 Subject: fix(lsp): Jump to tag locations reliably when :ltag is used (#22750) --- runtime/lua/vim/lsp/tagfunc.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/tagfunc.lua b/runtime/lua/vim/lsp/tagfunc.lua index 49029f8599..eb25b67db9 100644 --- a/runtime/lua/vim/lsp/tagfunc.lua +++ b/runtime/lua/vim/lsp/tagfunc.lua @@ -9,7 +9,7 @@ local function mk_tag_item(name, range, uri, offset_encoding) return { name = name, filename = vim.uri_to_fname(uri), - cmd = string.format('call cursor(%d, %d)|', range.start.line + 1, byte), + cmd = string.format([[/\%%%dl\%%%dc/]], range.start.line + 1, byte), } end -- cgit From 4e4203f71b0b9bb2ca4ad9abd2fbf4ea1deaf9a6 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 23 Mar 2023 11:23:51 +0000 Subject: fix(treesitter): annotations - Begin using `@package` in place of `@private` for functions that are accessed internally but outside their defined class. - Rename Node -> TSP.Node --- runtime/lua/vim/treesitter.lua | 6 ++---- runtime/lua/vim/treesitter/_fold.lua | 20 ++++++++++++++------ runtime/lua/vim/treesitter/_meta.lua | 3 ++- runtime/lua/vim/treesitter/highlighter.lua | 23 ++++++++++------------- runtime/lua/vim/treesitter/languagetree.lua | 11 +++++------ runtime/lua/vim/treesitter/playground.lua | 26 +++++++++++++------------- 6 files changed, 46 insertions(+), 43 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 43b8c11b80..685b1d8830 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -30,8 +30,6 @@ setmetatable(M, { end, }) ----@diagnostic disable:invisible - --- Creates a new parser --- --- It is not recommended to use this; use |get_parser()| instead. @@ -132,7 +130,7 @@ function M.get_parser(bufnr, lang, opts) return parsers[bufnr] end ----@private +---@package ---@param bufnr (integer|nil) Buffer number ---@return boolean function M._has_parser(bufnr) @@ -276,7 +274,7 @@ function M.get_captures_at_pos(bufnr, row, col) end end end - end, true) + end) return matches end diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 90f4394fcc..3e67e400c2 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -3,7 +3,7 @@ local Query = require('vim.treesitter.query') local api = vim.api ----@class FoldInfo +---@class TS.FoldInfo ---@field levels table ---@field levels0 table ---@field private start_counts table @@ -11,6 +11,7 @@ local api = vim.api local FoldInfo = {} FoldInfo.__index = FoldInfo +---@private function FoldInfo.new() return setmetatable({ start_counts = {}, @@ -20,6 +21,7 @@ function FoldInfo.new() }, FoldInfo) end +---@package ---@param srow integer ---@param erow integer function FoldInfo:invalidate_range(srow, erow) @@ -31,6 +33,7 @@ function FoldInfo:invalidate_range(srow, erow) end end +---@package ---@param srow integer ---@param erow integer function FoldInfo:remove_range(srow, erow) @@ -42,6 +45,7 @@ function FoldInfo:remove_range(srow, erow) end end +---@package ---@param srow integer ---@param erow integer function FoldInfo:add_range(srow, erow) @@ -53,22 +57,26 @@ function FoldInfo:add_range(srow, erow) end end +---@package ---@param lnum integer function FoldInfo:add_start(lnum) self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1 end +---@package ---@param lnum integer function FoldInfo:add_stop(lnum) self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 end +---@packag ---@param lnum integer ---@return integer function FoldInfo:get_start(lnum) return self.start_counts[lnum] or 0 end +---@package ---@param lnum integer ---@return integer function FoldInfo:get_stop(lnum) @@ -84,7 +92,7 @@ local function trim_level(level) end ---@param bufnr integer ----@param info FoldInfo +---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) @@ -161,7 +169,7 @@ end local M = {} ----@type table +---@type table local foldinfos = {} local function recompute_folds() @@ -178,7 +186,7 @@ local function recompute_folds() end ---@param bufnr integer ----@param foldinfo FoldInfo +---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) -- For some reason, queries seem to use the old buffer state in on_bytes. @@ -193,7 +201,7 @@ local function on_changedtree(bufnr, foldinfo, tree_changes) end ---@param bufnr integer ----@param foldinfo FoldInfo +---@param foldinfo TS.FoldInfo ---@param start_row integer ---@param old_row integer ---@param new_row integer @@ -212,13 +220,13 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) end end +---@package ---@param lnum integer|nil ---@return string function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - ---@diagnostic disable-next-line:invisible if not vim.treesitter._has_parser(bufnr) or not lnum then return '0' end diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 72823ccf26..4d0f43d030 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -3,7 +3,8 @@ ---@class TSNode ---@field id fun(self: TSNode): integer ---@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode, include_bytes: boolean?): integer, integer, integer, integer, integer, integer +---@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer +---@field range fun(self: TSNode, include_bytes: true): integer, integer, integer, integer, integer, integer ---@field start fun(self: TSNode): integer, integer, integer ---@field end_ fun(self: TSNode): integer, integer, integer ---@field type fun(self: TSNode): string diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index e3deaf6ba6..e24b3ba5df 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -53,12 +53,12 @@ function TSHighlighterQuery.new(lang, query_string) return self end ----@private +---@package function TSHighlighterQuery:query() return self._query end ----@private +---@package --- --- Creates a highlighter for `tree`. --- @@ -76,16 +76,14 @@ function TSHighlighter.new(tree, opts) opts = opts or {} ---@type { queries: table } self.tree = tree tree:register_cbs({ - ---@diagnostic disable:invisible on_changedtree = function(...) self:on_changedtree(...) end, on_bytes = function(...) self:on_bytes(...) end, - on_detach = function(...) - ---@diagnostic disable-next-line:redundant-parameter - self:on_detach(...) + on_detach = function() + self:on_detach() end, }) @@ -147,7 +145,7 @@ function TSHighlighter:destroy() end end ----@private +---@package ---@param tstree TSTree ---@return TSHighlightState function TSHighlighter:get_highlight_state(tstree) @@ -166,19 +164,19 @@ function TSHighlighter:reset_highlight_state() self._highlight_states = {} end ----@private +---@package ---@param start_row integer ---@param new_end integer function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end ----@private +---@package function TSHighlighter:on_detach() self:destroy() end ----@private +---@package ---@param changes integer[][]? function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do @@ -188,7 +186,7 @@ end --- Gets the query used for @param lang -- ----@private +---@package ---@param lang string Language used by the highlighter. ---@return TSHighlighterQuery function TSHighlighter:get_query(lang) @@ -205,7 +203,6 @@ end ---@param line integer ---@param is_spell_nav boolean local function on_line_impl(self, buf, line, is_spell_nav) - ---@diagnostic disable:invisible self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -268,7 +265,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) state.next_row = start_row end end - end, true) + end) end ---@private diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index bdfe281a5b..ae41062ab2 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -70,7 +70,7 @@ local LanguageTree = {} LanguageTree.__index = LanguageTree ---- @private +--- @package --- --- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any --- "injected" language parsers, which themselves may inject other languages, recursively. @@ -700,7 +700,7 @@ function LanguageTree:_do_callback(cb_name, ...) end end ----@private +---@package function LanguageTree:_edit( start_byte, end_byte_old, @@ -776,7 +776,7 @@ function LanguageTree:_edit( end) end ----@private +---@package ---@param bufnr integer ---@param changed_tick integer ---@param start_row integer @@ -822,7 +822,6 @@ function LanguageTree:_on_bytes( -- Edit trees together BEFORE emitting a bytes callback. ---@private self:for_each_child(function(child) - ---@diagnostic disable-next-line:invisible child:_edit( start_byte, start_byte + old_byte, @@ -852,12 +851,12 @@ function LanguageTree:_on_bytes( ) end ----@private +---@package function LanguageTree:_on_reload() self:invalidate(true) end ----@private +---@package function LanguageTree:_on_detach(...) self:invalidate(true) self:_do_callback('detach', ...) diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index 7f181c23fd..35f06f5caf 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -5,11 +5,11 @@ local api = vim.api ---@field opts table Options table with the following keys: --- - anon (boolean): If true, display anonymous nodes --- - lang (boolean): If true, display the language alongside each node ----@field nodes Node[] ----@field named Node[] +---@field nodes TSP.Node[] +---@field named TSP.Node[] local TSPlayground = {} ---- ----@class Node + +---@class TSP.Node ---@field id integer Node id ---@field text string Node text ---@field named boolean True if this is a named (non-anonymous) node @@ -36,9 +36,9 @@ local TSPlayground = {} ---@param node TSNode Starting node to begin traversal |tsnode| ---@param depth integer Current recursion depth ---@param lang string Language of the tree currently being traversed ----@param injections table Mapping of node ids to root nodes of injected language trees (see +---@param injections table Mapping of node ids to root nodes of injected language trees (see --- explanation above) ----@param tree Node[] Output table containing a list of tables each representing a node in the tree +---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree ---@private local function traverse(node, depth, lang, injections, tree) local injection = injections[node:id()] @@ -87,7 +87,7 @@ end ---@return TSPlayground|nil ---@return string|nil Error message, if any --- ----@private +---@package function TSPlayground:new(bufnr, lang) local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) if not ok then @@ -114,7 +114,7 @@ function TSPlayground:new(bufnr, lang) local nodes = traverse(root, 0, parser:lang(), injections, {}) - local named = {} ---@type Node[] + local named = {} ---@type TSP.Node[] for _, v in ipairs(nodes) do if v.named then named[#named + 1] = v @@ -154,7 +154,7 @@ end --- Write the contents of this Playground into {bufnr}. --- ---@param bufnr integer Buffer number to write into. ----@private +---@package function TSPlayground:draw(bufnr) vim.bo[bufnr].modifiable = true local lines = {} ---@type string[] @@ -195,8 +195,8 @@ end --- The node number is dependent on whether or not anonymous nodes are displayed. --- ---@param i integer Node number to get ----@return Node ----@private +---@return TSP.Node +---@package function TSPlayground:get(i) local t = self.opts.anon and self.nodes or self.named return t[i] @@ -204,10 +204,10 @@ end --- Iterate over all of the nodes in this Playground object. --- ----@return (fun(): integer, Node) Iterator over all nodes in this Playground +---@return (fun(): integer, TSP.Node) Iterator over all nodes in this Playground ---@return table ---@return integer ----@private +---@package function TSPlayground:iter() return ipairs(self.opts.anon and self.nodes or self.named) end -- cgit From ee5008f8789f61a3b153220abb870dd49427e52c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 24 Mar 2023 14:06:04 +0100 Subject: vim-patch:9.0.1425: "wat" and "wast" files are one filetype (#22774) Problem: "wat" and "wast" files are one filetype. Solution: Add a separate filetype for "wat" files. (Amaan Qureshi, closes vim/vim#12165) https://github.com/vim/vim/commit/3ea62381c527395ae701715335776f427d22eb7b Co-authored-by: Amaan Qureshi --- 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 fa4208e2a4..740304df15 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1103,8 +1103,8 @@ local extension = { wrl = 'vrml', vroom = 'vroom', vue = 'vue', - wat = 'wast', wast = 'wast', + wat = 'wat', wdl = 'wdl', wm = 'webmacro', wbt = 'winbatch', -- cgit From cbbf8bd666c8419fdab80a0887948c8a36279c19 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Mar 2023 14:43:14 +0000 Subject: feat(treesitter)!: deprecate top level indexes to modules (#22761) The following top level Treesitter functions have been moved: - vim.treesitter.inspect_language() -> vim.treesitter.language.inspect() - vim.treesitter.get_query_files() -> vim.treesitter.query.get_files() - vim.treesitter.set_query() -> vim.treesitter.query.set() - vim.treesitter.query.set_query() -> vim.treesitter.query.set() - vim.treesitter.get_query() -> vim.treesitter.query.get() - vim.treesitter.query.get_query() -> vim.treesitter.query.get() - vim.treesitter.parse_query() -> vim.treesitter.query.parse() - vim.treesitter.query.parse_query() -> vim.treesitter.query.parse() - vim.treesitter.add_predicate() -> vim.treesitter.query.add_predicate() - vim.treesitter.add_directive() -> vim.treesitter.query.add_directive() - vim.treesitter.list_predicates() -> vim.treesitter.query.list_predicates() - vim.treesitter.list_directives() -> vim.treesitter.query.list_directives() - vim.treesitter.query.get_range() -> vim.treesitter.get_range() - vim.treesitter.query.get_node_text() -> vim.treesitter.get_node_text() --- runtime/lua/vim/treesitter.lua | 89 +++++++++++++++++--- runtime/lua/vim/treesitter/highlighter.lua | 6 +- runtime/lua/vim/treesitter/language.lua | 13 ++- runtime/lua/vim/treesitter/languagetree.lua | 14 ++-- runtime/lua/vim/treesitter/query.lua | 123 +++++++++++++--------------- 5 files changed, 155 insertions(+), 90 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 685b1d8830..4c3b17daa4 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -1,6 +1,4 @@ local a = vim.api -local query = require('vim.treesitter.query') -local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') local Range = require('vim.treesitter._range') @@ -9,12 +7,9 @@ local parsers = setmetatable({}, { __mode = 'v' }) ---@class TreesitterModule ---@field highlighter TSHighlighter -local M = vim.tbl_extend('error', query, language) - -M.language_version = vim._ts_get_language_version() -M.minimum_language_version = vim._ts_get_minimum_language_version() - -setmetatable(M, { +---@field query TSQueryModule +---@field language TSLanguageModule +local M = setmetatable({}, { __index = function(t, k) ---@diagnostic disable:no-unknown if k == 'highlighter' then @@ -27,9 +22,26 @@ setmetatable(M, { t[k] = require('vim.treesitter.query') return t[k] end + + local query = require('vim.treesitter.query') + if query[k] then + vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.query.' .. k .. '()', '0.10') + t[k] = query[k] + return t[k] + end + + local language = require('vim.treesitter.language') + if language[k] then + vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.language.' .. k .. '()', '0.10') + t[k] = language[k] + return t[k] + end end, }) +M.language_version = vim._ts_get_language_version() +M.minimum_language_version = vim._ts_get_minimum_language_version() + --- Creates a new parser --- --- It is not recommended to use this; use |get_parser()| instead. @@ -47,7 +59,7 @@ function M._create_parser(bufnr, lang, opts) vim.fn.bufload(bufnr) local ft = vim.bo[bufnr].filetype - language.add(lang, { filetype = ft ~= '' and ft or nil }) + M.language.add(lang, { filetype = ft ~= '' and ft or nil }) local self = LanguageTree.new(bufnr, lang, opts) @@ -101,7 +113,7 @@ function M.get_parser(bufnr, lang, opts) if lang == nil then local ft = vim.bo[bufnr].filetype if ft ~= '' then - lang = language.get_lang(ft) or ft + lang = M.language.get_lang(ft) or ft -- TODO(lewis6991): we should error here and not default to ft -- if not lang then -- error(string.format('filetype %s of buffer %d is not associated with any lang', ft, bufnr)) @@ -152,7 +164,7 @@ function M.get_string_parser(str, lang, opts) str = { str, 'string' }, lang = { lang, 'string' }, }) - language.add(lang) + M.language.add(lang) return LanguageTree.new(str, lang, opts) end @@ -196,6 +208,61 @@ function M.get_node_range(node_or_range) end end +---Get the range of a |TSNode|. Can also supply {source} and {metadata} +---to get the range with directives applied. +---@param node TSNode +---@param source integer|string|nil Buffer or string from which the {node} is extracted +---@param metadata TSMetadata|nil +---@return Range6 +function M.get_range(node, source, metadata) + if metadata and metadata.range then + assert(source) + return Range.add_bytes(source, metadata.range) + end + return { node:range(true) } +end + +---@private +---@param buf integer +---@param range Range +---@returns string +local function buf_range_get_text(buf, range) + local start_row, start_col, end_row, end_col = Range.unpack4(range) + if end_col == 0 then + if start_row == end_row then + start_col = -1 + start_row = start_row - 1 + end + end_col = -1 + end_row = end_row - 1 + end + local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) + return table.concat(lines, '\n') +end + +--- Gets the text corresponding to a given node +--- +---@param node TSNode +---@param source (integer|string) Buffer or string from which the {node} is extracted +---@param opts (table|nil) Optional parameters. +--- - metadata (table) Metadata of a specific capture. This would be +--- set to `metadata[capture_id]` when using |vim.treesitter.query.add_directive()|. +---@return string +function M.get_node_text(node, source, opts) + opts = opts or {} + local metadata = opts.metadata or {} + + if metadata.text then + return metadata.text + elseif type(source) == 'number' then + local range = vim.treesitter.get_range(node, source, metadata) + return buf_range_get_text(source, range) + end + + ---@cast source string + return source:sub(select(3, node:start()) + 1, select(3, node:end_())) +end + --- Determines whether (line, col) position is in node range --- ---@param node TSNode defining the range diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index e24b3ba5df..729cd34090 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,5 +1,5 @@ local a = vim.api -local query = require('vim.treesitter.query') +local query = vim.treesitter.query ---@alias TSHlIter fun(): integer, TSNode, TSMetadata @@ -45,9 +45,9 @@ function TSHighlighterQuery.new(lang, query_string) }) if query_string then - self._query = query.parse_query(lang, query_string) + self._query = query.parse(lang, query_string) else - self._query = query.get_query(lang, 'highlights') + self._query = query.get(lang, 'highlights') end return self diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 47375fd5e6..974d66ec05 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -1,5 +1,6 @@ local a = vim.api +---@class TSLanguageModule local M = {} ---@type table @@ -111,9 +112,19 @@ end --- ---@param lang string Language ---@return table -function M.inspect_language(lang) +function M.inspect(lang) M.add(lang) return vim._ts_inspect_language(lang) end +---@deprecated +function M.inspect_language(...) + vim.deprecate( + 'vim.treesitter.language.inspect_language()', + 'vim.treesitter.language.inspect()', + '0.10' + ) + return M.inspect(...) +end + return M diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index ae41062ab2..82e507551d 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -99,8 +99,8 @@ function LanguageTree.new(source, lang, opts) _regions = {}, _trees = {}, _opts = opts, - _injection_query = injections[lang] and query.parse_query(lang, injections[lang]) - or query.get_query(lang, 'injections'), + _injection_query = injections[lang] and query.parse(lang, injections[lang]) + or query.get(lang, 'injections'), _valid = false, _parser = vim._create_ts_parser(lang), _callbacks = { @@ -482,7 +482,7 @@ end ---@param metadata TSMetadata ---@return Range6[] local function get_node_ranges(node, source, metadata, include_children) - local range = query.get_range(node, source, metadata) + local range = vim.treesitter.get_range(node, source, metadata) if include_children then return { range } @@ -562,7 +562,7 @@ function LanguageTree:_get_injection(match, metadata) -- Lang should override any other language tag if name == 'injection.language' then - lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) + lang = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'injection.content' then ranges = get_node_ranges(node, self._source, metadata[id], include_children) end @@ -609,11 +609,11 @@ function LanguageTree:_get_injection_deprecated(match, metadata) -- Lang should override any other language tag if name == 'language' and not lang then - lang = query.get_node_text(node, self._source, { metadata = metadata[id] }) + lang = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] }) elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - ranges[#ranges + 1] = query.get_range(node, self._source, metadata[id]) + ranges[#ranges + 1] = vim.treesitter.get_range(node, self._source, metadata[id]) -- Ignore any tags that start with "_" -- Allows for other tags to be used in matches elseif string.sub(name, 1, 1) ~= '_' then @@ -622,7 +622,7 @@ function LanguageTree:_get_injection_deprecated(match, metadata) end if #ranges == 0 then - ranges[#ranges + 1] = query.get_range(node, self._source, metadata[id]) + ranges[#ranges + 1] = vim.treesitter.get_range(node, self._source, metadata[id]) end end end diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index f4e038b2d8..8ccd6da8a7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,8 +1,6 @@ local a = vim.api local language = require('vim.treesitter.language') -local Range = require('vim.treesitter._range') - ---@class Query ---@field captures string[] List of captures used in query ---@field info TSQueryInfo Contains used queries, predicates, directives @@ -14,6 +12,7 @@ Query.__index = Query ---@field captures table ---@field patterns table +---@class TSQueryModule local M = {} ---@private @@ -57,22 +56,14 @@ local function add_included_lang(base_langs, lang, ilang) return false end ----@private ----@param buf integer ----@param range Range ----@returns string -local function buf_range_get_text(buf, range) - local start_row, start_col, end_row, end_col = Range.unpack4(range) - if end_col == 0 then - if start_row == end_row then - start_col = -1 - start_row = start_row - 1 - end - end_col = -1 - end_row = end_row - 1 - end - local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) - return table.concat(lines, '\n') +---@deprecated +function M.get_query_files(...) + vim.deprecate( + 'vim.treesitter.query.get_query_files()', + 'vim.treesitter.query.get_files()', + '0.10' + ) + return M.get_files(...) end --- Gets the list of files used to make up a query @@ -81,7 +72,7 @@ end ---@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` ---@return string[] query_files List of files to load for given query and language -function M.get_query_files(lang, query_name, is_included) +function M.get_files(lang, query_name, is_included) local query_path = string.format('queries/%s/%s.scm', lang, query_name) local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) @@ -153,7 +144,7 @@ function M.get_query_files(lang, query_name, is_included) local query_files = {} for _, base_lang in ipairs(base_langs) do - local base_files = M.get_query_files(base_lang, query_name, true) + local base_files = M.get_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end vim.list_extend(query_files, { base_query }) @@ -175,7 +166,7 @@ local function read_query_files(filenames) return table.concat(contents, '') end --- The explicitly set queries from |vim.treesitter.query.set_query()| +-- The explicitly set queries from |vim.treesitter.query.set()| ---@type table> local explicit_queries = setmetatable({}, { __index = function(t, k) @@ -186,6 +177,12 @@ local explicit_queries = setmetatable({}, { end, }) +---@deprecated +function M.set_query(...) + vim.deprecate('vim.treesitter.query.set_query()', 'vim.treesitter.query.set()', '0.10') + M.set(...) +end + --- Sets the runtime query named {query_name} for {lang} --- --- This allows users to override any runtime files and/or configuration @@ -194,8 +191,17 @@ local explicit_queries = setmetatable({}, { ---@param lang string Language to use for the query ---@param query_name string Name of the query (e.g., "highlights") ---@param text string Query text (unparsed). -function M.set_query(lang, query_name, text) - explicit_queries[lang][query_name] = M.parse_query(lang, text) +function M.set(lang, query_name, text) + explicit_queries[lang][query_name] = M.parse(lang, text) +end + +---@deprecated +---@param lang string Language to use for the query +---@param query_name string Name of the query (e.g. "highlights") +--- +---@return Query|nil Parsed query +function M.get_query(lang, query_name) + return M.get(lang, query_name) end --- Returns the runtime query {query_name} for {lang}. @@ -204,16 +210,16 @@ end ---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query|nil Parsed query -function M.get_query(lang, query_name) +function M.get(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] end - local query_files = M.get_query_files(lang, query_name) + local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) if #query_string > 0 then - return M.parse_query(lang, query_string) + return M.parse(lang, query_string) end end @@ -222,6 +228,12 @@ local query_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) +---@deprecated +function M.parse_query(...) + vim.deprecate('vim.treesitter.query.parse_query()', 'vim.treesitter.query.parse()', '0.10') + return M.parse(...) +end + --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). --- @@ -239,7 +251,7 @@ end) ---@param query string Query in s-expr syntax --- ---@return Query Parsed query -function M.parse_query(lang, query) +function M.parse(lang, query) language.add(lang) local cached = query_cache[lang][query] if cached then @@ -254,41 +266,16 @@ function M.parse_query(lang, query) return self end ----Get the range of a |TSNode|. Can also supply {source} and {metadata} ----to get the range with directives applied. ----@param node TSNode ----@param source integer|string|nil Buffer or string from which the {node} is extracted ----@param metadata TSMetadata|nil ----@return Range6 -function M.get_range(node, source, metadata) - if metadata and metadata.range then - assert(source) - return Range.add_bytes(source, metadata.range) - end - return { node:range(true) } +---@deprecated +function M.get_range(...) + vim.deprecate('vim.treesitter.query.get_range()', 'vim.treesitter.get_range()', '0.10') + return vim.treesitter.get_range(...) end ---- Gets the text corresponding to a given node ---- ----@param node TSNode ----@param source (integer|string) Buffer or string from which the {node} is extracted ----@param opts (table|nil) Optional parameters. ---- - metadata (table) Metadata of a specific capture. This would be ---- set to `metadata[capture_id]` when using |vim.treesitter.add_directive()|. ----@return string -function M.get_node_text(node, source, opts) - opts = opts or {} - local metadata = opts.metadata or {} - - if metadata.text then - return metadata.text - elseif type(source) == 'number' then - local range = M.get_range(node, source, metadata) - return buf_range_get_text(source, range) - end - - ---@cast source string - return source:sub(select(3, node:start()) + 1, select(3, node:end_())) +---@deprecated +function M.get_node_text(...) + vim.deprecate('vim.treesitter.query.get_node_text()', 'vim.treesitter.get_node_text()', '0.10') + return vim.treesitter.get_node_text(...) end ---@alias TSMatch table @@ -304,7 +291,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) local str ---@type string if type(predicate[3]) == 'string' then @@ -312,7 +299,7 @@ local predicate_handlers = { str = predicate[3] else -- (#eq? @aa @bb) - str = M.get_node_text(match[predicate[3]], source) + str = vim.treesitter.get_node_text(match[predicate[3]], source) end if node_text ~= str or str == nil then @@ -328,7 +315,7 @@ local predicate_handlers = { return true end local regex = predicate[3] - return string.find(M.get_node_text(node, source), regex) ~= nil + return string.find(vim.treesitter.get_node_text(node, source), regex) ~= nil end, ['match?'] = (function() @@ -357,7 +344,7 @@ local predicate_handlers = { end ---@diagnostic disable-next-line no-unknown local regex = compiled_vim_regexes[pred[3]] - return regex:match_str(M.get_node_text(node, source)) + return regex:match_str(vim.treesitter.get_node_text(node, source)) end end)(), @@ -366,7 +353,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) for i = 3, #predicate do if string.find(node_text, predicate[i], 1, true) then @@ -382,7 +369,7 @@ local predicate_handlers = { if not node then return true end - local node_text = M.get_node_text(node, source) + local node_text = vim.treesitter.get_node_text(node, source) -- Since 'predicate' will not be used by callers of this function, use it -- to store a string set built from the list of words to check against. @@ -468,7 +455,7 @@ local directive_handlers = { assert(type(id) == 'number') local node = match[id] - local text = M.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' + local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or '' if not metadata[id] then metadata[id] = {} @@ -486,7 +473,7 @@ local directive_handlers = { --- ---@param name string Name of the predicate, without leading # ---@param handler function(match:table, pattern:string, bufnr:integer, predicate:string[]) ---- - see |vim.treesitter.add_directive()| for argument meanings +--- - see |vim.treesitter.query.add_directive()| for argument meanings ---@param force boolean|nil function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then -- cgit From ac7397f4a06e451fedde86fb4eba0038d0d75e68 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 24 Mar 2023 16:31:30 +0000 Subject: fix(treesitter): add missing deprecate --- runtime/lua/vim/treesitter/_fold.lua | 5 ++--- runtime/lua/vim/treesitter/query.lua | 9 +++------ 2 files changed, 5 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 3e67e400c2..6547ab936e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,5 +1,4 @@ local Range = require('vim.treesitter._range') -local Query = require('vim.treesitter.query') local api = vim.api @@ -105,7 +104,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local prev_stop = -1 vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) - local query = vim.treesitter.query.get_query(ltree:lang(), 'folds') + local query = vim.treesitter.query.get(ltree:lang(), 'folds') if not query then return end @@ -115,7 +114,7 @@ local function get_folds_levels(bufnr, info, srow, erow) for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do if query.captures[id] == 'fold' then - local range = Query.get_range(node, bufnr, metadata[id]) + local range = vim.treesitter.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8ccd6da8a7..25623c1498 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -196,12 +196,9 @@ function M.set(lang, query_name, text) end ---@deprecated ----@param lang string Language to use for the query ----@param query_name string Name of the query (e.g. "highlights") ---- ----@return Query|nil Parsed query -function M.get_query(lang, query_name) - return M.get(lang, query_name) +function M.get_query(...) + vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') + return M.get(...) end --- Returns the runtime query {query_name} for {lang}. -- cgit From 42876ddc7ad7d3dac3e1caf2494f402e7eb25a88 Mon Sep 17 00:00:00 2001 From: Jakub Łuczyński Date: Sat, 25 Mar 2023 09:28:59 +0100 Subject: docs: more details about vim.region (#21116) --- runtime/lua/vim/_editor.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index f2875e5646..fd6a5865ce 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -397,14 +397,17 @@ do vim.t = make_dict_accessor('t') end ---- Get a table of lines with start, end columns for a region marked by two points +--- Get a table of lines with start, end columns for a region marked by two points. +--- Input and output positions are (0,0)-indexed and indicate byte positions. --- ---@param bufnr integer number of buffer ---@param pos1 integer[] (line, column) tuple marking beginning of region ---@param pos2 integer[] (line, column) tuple marking end of region ---@param regtype string type of selection, see |setreg()| ----@param inclusive boolean indicating whether the selection is end-inclusive ----@return table region Table of the form `{linenr = {startcol,endcol}}` +---@param inclusive boolean indicating whether column of pos2 is inclusive +---@return table region Table of the form `{linenr = {startcol,endcol}}`. +--- `endcol` is exclusive, and whole lines are marked with +--- `{startcol,endcol} = {0,-1}`. function vim.region(bufnr, pos1, pos2, regtype, inclusive) if not vim.api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) -- cgit From 36ee10057ab9a93144975449cc5e27f9b96e0af3 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Sat, 25 Mar 2023 09:01:39 -0400 Subject: fix(lsp): add missing silent check in lsp hover handler (#22763) Fixup to #21531. --- runtime/lua/vim/lsp/handlers.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index ee5b63d260..d01f8e6159 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -364,7 +364,9 @@ function M.hover(_, result, ctx, config) local markdown_lines = util.convert_input_to_markdown_lines(result.contents) markdown_lines = util.trim_empty_lines(markdown_lines) if vim.tbl_isempty(markdown_lines) then - vim.notify('No information available') + if config.silent ~= true then + vim.notify('No information available') + end return end return util.open_floating_preview(markdown_lines, 'markdown', config) -- cgit From 257d894d75bc583bb16f4dbe441907eb273d20ad Mon Sep 17 00:00:00 2001 From: Roberto Pommella Alegro Date: Sat, 25 Mar 2023 13:46:07 -0300 Subject: feat(lsp): render markdown in docs hover #22766 Problem: LSP docs hover (textDocument/hover) doesn't handle HTML escape seqs in markdown. Solution: Convert common HTML escape seqs to a nicer form, to display in the float. closees #22757 Signed-off-by: Kasama --- runtime/lua/vim/lsp/util.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 48faddfce1..ebde7af16c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1378,6 +1378,20 @@ function M.stylize_markdown(bufnr, contents, opts) end end + -- Handle some common html escape sequences + stripped = vim.tbl_map(function(line) + local escapes = { + ['>'] = '>', + ['<'] = '<', + ['"'] = '"', + ['''] = "'", + [' '] = ' ', + [' '] = ' ', + ['&'] = '&', + } + return (string.gsub(line, '&[^ ;]+;', escapes)) + end, stripped) + -- Compute size of float needed to show (wrapped) lines opts.wrap_at = opts.wrap_at or (vim.wo['wrap'] and api.nvim_win_get_width(0)) local width = M._make_floating_popup_size(stripped, opts) -- cgit From fe9cbcb3a5c82932ecfb8f49d07e98a1fc2b31e5 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Sat, 25 Mar 2023 18:58:48 +0200 Subject: feat(api): nvim_exec2(), deprecate nvim_exec() #19032 Problem: The signature of nvim_exec() is not extensible per ":help api-contract". Solution: Introduce nvim_exec2() and deprecate nvim_exec(). --- runtime/lua/vim/_editor.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index fd6a5865ce..db7a6c1d17 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -322,8 +322,8 @@ end --- ---@param command string|table Command(s) to execute. --- If a string, executes multiple lines of Vim script at once. In this ---- case, it is an alias to |nvim_exec()|, where `output` is set to ---- false. Thus it works identical to |:source|. +--- case, it is an alias to |nvim_exec2()|, where `opts.output` is set +--- to false. Thus it works identical to |:source|. --- If a table, executes a single command. In this case, it is an alias --- to |nvim_cmd()| where `opts` is empty. ---@see |ex-cmd-index| @@ -338,7 +338,7 @@ vim.cmd = setmetatable({}, { if type(command) == 'table' then return vim.api.nvim_cmd(command, {}) else - return vim.api.nvim_exec(command, false) + return vim.api.nvim_exec2(command, { output = false }).output end end, __index = function(t, command) -- cgit From 4863ca6b8902c5b0aab95f2af640118cd417d379 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 26 Mar 2023 10:49:32 +0800 Subject: test: use exec_capture() in more places (#22787) Problem: Using `meths.exec2("code", { output = true })` is too verbose. Solution: Use exec_capture() in more places. --- runtime/lua/vim/_editor.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index db7a6c1d17..2ff4d440f0 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -338,7 +338,8 @@ vim.cmd = setmetatable({}, { if type(command) == 'table' then return vim.api.nvim_cmd(command, {}) else - return vim.api.nvim_exec2(command, { output = false }).output + vim.api.nvim_exec2(command, {}) + return '' end end, __index = function(t, command) -- cgit From 2257ade3dc2daab5ee12d27807c0b3bcf103cd29 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 26 Mar 2023 12:42:15 +0200 Subject: feat(lua): add `vim.loader` feat: new faster lua loader using byte-compilation --- runtime/lua/vim/_editor.lua | 1 + runtime/lua/vim/loader.lua | 536 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 537 insertions(+) create mode 100644 runtime/lua/vim/loader.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 2ff4d440f0..0c4b634f6f 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -28,6 +28,7 @@ for k, v in pairs({ treesitter = true, filetype = true, + loader = true, F = true, lsp = true, highlight = true, diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua new file mode 100644 index 0000000000..41d5579664 --- /dev/null +++ b/runtime/lua/vim/loader.lua @@ -0,0 +1,536 @@ +local uv = vim.loop + +local M = {} + +---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} +---@alias CacheEntry {hash:CacheHash, chunk:string} + +---@class ModuleFindOpts +---@field all? boolean Search for all matches (defaults to `false`) +---@field rtp? boolean Search for modname in the runtime path (defaults to `true`) +---@field patterns? string[] Paterns to use (defaults to `{"/init.lua", ".lua"}`) +---@field paths? string[] Extra paths to search for modname + +---@class ModuleInfo +---@field modpath string Path of the module +---@field modname string Name of the module +---@field stat? uv_fs_t File stat of the module path + +---@alias LoaderStats table + +M.path = vim.fn.stdpath('cache') .. '/luac' +M.enabled = false + +---@class Loader +---@field _rtp string[] +---@field _rtp_pure string[] +---@field _rtp_key string +local Loader = { + VERSION = 3, + ---@type table> + _indexed = {}, + ---@type table + _topmods = {}, + _loadfile = loadfile, + ---@type LoaderStats + _stats = { + find = { total = 0, time = 0, not_found = 0 }, + }, +} + +--- Tracks the time spent in a function +---@private +function Loader.track(stat, start) + Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } + Loader._stats[stat].total = Loader._stats[stat].total + 1 + Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start +end + +--- slightly faster/different version than vim.fs.normalize +--- we also need to have it here, since the loader will load vim.fs +---@private +function Loader.normalize(path) + if path:sub(1, 1) == '~' then + local home = vim.loop.os_homedir() or '~' + if home:sub(-1) == '\\' or home:sub(-1) == '/' then + home = home:sub(1, -2) + end + path = home .. path:sub(2) + end + path = path:gsub('\\', '/'):gsub('/+', '/') + return path:sub(-1) == '/' and path:sub(1, -2) or path +end + +--- Gets the rtp excluding after directories. +--- The result is cached, and will be updated if the runtime path changes. +--- When called from a fast event, the cached value will be returned. +--- @return string[] rtp, boolean updated +---@private +function Loader.get_rtp() + local start = uv.hrtime() + if vim.in_fast_event() then + Loader.track('get_rtp', start) + return (Loader._rtp or {}), false + end + local updated = false + local key = vim.go.rtp + if key ~= Loader._rtp_key then + Loader._rtp = {} + for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do + path = Loader.normalize(path) + -- skip after directories + if + path:sub(-6, -1) ~= '/after' + and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path])) + then + Loader._rtp[#Loader._rtp + 1] = path + end + end + updated = true + Loader._rtp_key = key + end + Loader.track('get_rtp', start) + return Loader._rtp, updated +end + +--- Returns the cache file name +---@param name string can be a module name, or a file name +---@return string file_name +---@private +function Loader.cache_file(name) + local ret = M.path .. '/' .. name:gsub('[/\\:]', '%%') + return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac') +end + +--- Saves the cache entry for a given module or file +---@param name string module name or filename +---@param entry CacheEntry +---@private +function Loader.write(name, entry) + local cname = Loader.cache_file(name) + local f = assert(uv.fs_open(cname, 'w', 438)) + local header = { + Loader.VERSION, + entry.hash.size, + entry.hash.mtime.sec, + entry.hash.mtime.nsec, + } + uv.fs_write(f, table.concat(header, ',') .. '\0') + uv.fs_write(f, entry.chunk) + uv.fs_close(f) +end + +--- Loads the cache entry for a given module or file +---@param name string module name or filename +---@return CacheEntry? +---@private +function Loader.read(name) + local start = uv.hrtime() + local cname = Loader.cache_file(name) + local f = uv.fs_open(cname, 'r', 438) + if f then + local hash = uv.fs_fstat(f) --[[@as CacheHash]] + local data = uv.fs_read(f, hash.size, 0) --[[@as string]] + uv.fs_close(f) + + local zero = data:find('\0', 1, true) + if not zero then + return + end + + ---@type integer[]|{[0]:integer} + local header = vim.split(data:sub(1, zero - 1), ',') + if tonumber(header[1]) ~= Loader.VERSION then + return + end + Loader.track('read', start) + return { + hash = { + size = tonumber(header[2]), + mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) }, + }, + chunk = data:sub(zero + 1), + } + end + Loader.track('read', start) +end + +--- The `package.loaders` loader for lua files using the cache. +---@param modname string module name +---@return string|function +---@private +function Loader.loader(modname) + local start = uv.hrtime() + local ret = M.find(modname)[1] + if ret then + local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) + Loader.track('loader', start) + return chunk or error(err) + end + Loader.track('loader', start) + return '\ncache_loader: module ' .. modname .. ' not found' +end + +--- The `package.loaders` loader for libs +---@param modname string module name +---@return string|function +---@private +function Loader.loader_lib(modname) + local start = uv.hrtime() + local sysname = uv.os_uname().sysname:lower() or '' + local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true) + local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1] + ---@type function?, string? + if ret then + -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is + -- a) strip prefix up to and including the first dash, if any + -- b) replace all dots by underscores + -- c) prepend "luaopen_" + -- So "foo-bar.baz" should result in "luaopen_bar_baz" + local dash = modname:find('-', 1, true) + local funcname = dash and modname:sub(dash + 1) or modname + local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_')) + Loader.track('loader_lib', start) + return chunk or error(err) + end + Loader.track('loader_lib', start) + return '\ncache_loader_lib: module ' .. modname .. ' not found' +end + +--- `loadfile` using the cache +---@param filename? string +---@param mode? "b"|"t"|"bt" +---@param env? table +---@param hash? CacheHash +---@return function?, string? error_message +---@private +-- luacheck: ignore 312 +function Loader.loadfile(filename, mode, env, hash) + local start = uv.hrtime() + filename = Loader.normalize(filename) + mode = nil -- ignore mode, since we byte-compile the lua source files + local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) + Loader.track('loadfile', start) + return chunk, err +end + +--- Checks whether two cache hashes are the same based on: +--- * file size +--- * mtime in seconds +--- * mtime in nanoseconds +---@param h1 CacheHash +---@param h2 CacheHash +---@private +function Loader.eq(h1, h2) + return h1 + and h2 + and h1.size == h2.size + and h1.mtime.sec == h2.mtime.sec + and h1.mtime.nsec == h2.mtime.nsec +end + +--- Loads the given module path using the cache +---@param modpath string +---@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: +--- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.loop.fs_stat({modpath})`) +--- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`) +--- - env: (table) the environment to load the module in. (defaults to `nil`) +---@see |luaL_loadfile()| +---@return function?, string? error_message +---@private +function Loader.load(modpath, opts) + local start = uv.hrtime() + + opts = opts or {} + local hash = opts.hash or uv.fs_stat(modpath) + ---@type function?, string? + local chunk, err + + if not hash then + -- trigger correct error + chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) + Loader.track('load', start) + return chunk, err + end + + local entry = Loader.read(modpath) + if entry and Loader.eq(entry.hash, hash) then + -- found in cache and up to date + chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env) + if not (err and err:find('cannot load incompatible bytecode', 1, true)) then + Loader.track('load', start) + return chunk, err + end + end + entry = { hash = hash, modpath = modpath } + + chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) + if chunk then + entry.chunk = string.dump(chunk) + Loader.write(modpath, entry) + end + Loader.track('load', start) + return chunk, err +end + +--- Finds lua modules for the given module name. +---@param modname string Module name, or `"*"` to find the top-level modules instead +---@param opts? ModuleFindOpts (table|nil) Options for finding a module: +--- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`) +--- - paths: (string[]) Extra paths to search for modname (defaults to `{}`) +--- - patterns: (string[]) List of patterns to use when searching for modules. +--- A pattern is a string added to the basename of the Lua module being searched. +--- (defaults to `{"/init.lua", ".lua"}`) +--- - all: (boolean) Return all matches instead of just the first one (defaults to `false`) +---@return ModuleInfo[] (list) A list of results with the following properties: +--- - modpath: (string) the path to the module +--- - modname: (string) the name of the module +--- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"` +function M.find(modname, opts) + local start = uv.hrtime() + opts = opts or {} + + modname = modname:gsub('/', '.') + local basename = modname:gsub('%.', '/') + local idx = modname:find('.', 1, true) + + -- HACK: fix incorrect require statements. Really not a fan of keeping this, + -- but apparently the regular lua loader also allows this + if idx == 1 then + modname = modname:gsub('^%.+', '') + basename = modname:gsub('%.', '/') + idx = modname:find('.', 1, true) + end + + -- get the top-level module name + local topmod = idx and modname:sub(1, idx - 1) or modname + + -- OPTIM: search for a directory first when topmod == modname + local patterns = opts.patterns + or (topmod == modname and { '/init.lua', '.lua' } or { '.lua', '/init.lua' }) + for p, pattern in ipairs(patterns) do + patterns[p] = '/lua/' .. basename .. pattern + end + + ---@type ModuleInfo[] + local results = {} + + -- Only continue if we haven't found anything yet or we want to find all + ---@private + local function continue() + return #results == 0 or opts.all + end + + -- Checks if the given paths contain the top-level module. + -- If so, it tries to find the module path for the given module name. + ---@param paths string[] + ---@private + local function _find(paths) + for _, path in ipairs(paths) do + if topmod == '*' then + for _, r in pairs(Loader.lsmod(path)) do + results[#results + 1] = r + if not continue() then + return + end + end + elseif Loader.lsmod(path)[topmod] then + for _, pattern in ipairs(patterns) do + local modpath = path .. pattern + Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1 + local hash = uv.fs_stat(modpath) + if hash then + results[#results + 1] = { modpath = modpath, stat = hash, modname = modname } + if not continue() then + return + end + end + end + end + end + end + + -- always check the rtp first + if opts.rtp ~= false then + _find(Loader._rtp or {}) + if continue() then + local rtp, updated = Loader.get_rtp() + if updated then + _find(rtp) + end + end + end + + -- check any additional paths + if continue() and opts.paths then + _find(opts.paths) + end + + Loader.track('find', start) + if #results == 0 then + -- module not found + Loader._stats.find.not_found = Loader._stats.find.not_found + 1 + end + + return results +end + +--- Resets the topmods cache for the path, or all the paths +--- if path is nil. +---@param path string? path to reset +function M.reset(path) + if path then + Loader._indexed[Loader.normalize(path)] = nil + else + Loader._indexed = {} + end +end + +--- Enables the experimental Lua module loader: +--- * overrides loadfile +--- * adds the lua loader using the byte-compilation cache +--- * adds the libs loader +--- * removes the default Neovim loader +function M.enable() + if M.enabled then + return + end + M.enabled = true + vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') + _G.loadfile = Loader.loadfile + -- add lua loader + table.insert(package.loaders, 2, Loader.loader) + -- add libs loader + table.insert(package.loaders, 3, Loader.loader_lib) + -- remove Neovim loader + for l, loader in ipairs(package.loaders) do + if loader == vim._load_package then + table.remove(package.loaders, l) + break + end + end + + -- this will reset the top-mods in case someone adds a new + -- top-level lua module to a path already on the rtp + vim.api.nvim_create_autocmd('BufWritePost', { + group = vim.api.nvim_create_augroup('cache_topmods_reset', { clear = true }), + callback = function(event) + local bufname = event.match ---@type string + local idx = bufname:find('/lua/', 1, true) + if idx then + M.reset(bufname:sub(1, idx - 1)) + end + end, + }) +end + +--- Disables the experimental Lua module loader: +--- * removes the loaders +--- * adds the default Neovim loader +function M.disable() + if not M.enabled then + return + end + M.enabled = false + _G.loadfile = Loader._loadfile + ---@diagnostic disable-next-line: no-unknown + for l, loader in ipairs(package.loaders) do + if loader == Loader.loader or loader == Loader.loader_lib then + table.remove(package.loaders, l) + end + end + table.insert(package.loaders, 2, vim._load_package) + vim.api.nvim_del_augroup_by_name('cache_topmods_reset') +end + +--- Return the top-level `/lua/*` modules for this path +---@param path string path to check for top-level lua modules +---@private +function Loader.lsmod(path) + if not Loader._indexed[path] then + local start = uv.hrtime() + Loader._indexed[path] = {} + local handle = vim.loop.fs_scandir(path .. '/lua') + while handle do + local name, t = vim.loop.fs_scandir_next(handle) + if not name then + break + end + local modpath = path .. '/lua/' .. name + -- HACK: type is not always returned due to a bug in luv + t = t or uv.fs_stat(modpath).type + ---@type string + local topname + local ext = name:sub(-4) + if ext == '.lua' or ext == '.dll' then + topname = name:sub(1, -5) + elseif name:sub(-3) == '.so' then + topname = name:sub(1, -4) + elseif t == 'link' or t == 'directory' then + topname = name + end + if topname then + Loader._indexed[path][topname] = { modpath = modpath, modname = topname } + Loader._topmods[topname] = Loader._topmods[topname] or {} + if not vim.tbl_contains(Loader._topmods[topname], path) then + table.insert(Loader._topmods[topname], path) + end + end + end + Loader.track('lsmod', start) + end + return Loader._indexed[path] +end + +--- Debug function that wrapps all loaders and tracks stats +---@private +function M._profile_loaders() + for l, loader in pairs(package.loaders) do + local loc = debug.getinfo(loader, 'Sn').source:sub(2) + package.loaders[l] = function(modname) + local start = vim.loop.hrtime() + local ret = loader(modname) + Loader.track('loader ' .. l .. ': ' .. loc, start) + Loader.track('loader_all', start) + return ret + end + end +end + +--- Prints all cache stats +---@param opts? {print?:boolean} +---@return LoaderStats +---@private +function M._inspect(opts) + if opts and opts.print then + ---@private + local function ms(nsec) + return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms' + end + local chunks = {} ---@type string[][] + ---@type string[] + local stats = vim.tbl_keys(Loader._stats) + table.sort(stats) + for _, stat in ipairs(stats) do + vim.list_extend(chunks, { + { '\n' .. stat .. '\n', 'Title' }, + { '* total: ' }, + { tostring(Loader._stats[stat].total) .. '\n', 'Number' }, + { '* time: ' }, + { ms(Loader._stats[stat].time) .. '\n', 'Bold' }, + { '* avg time: ' }, + { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' }, + }) + for k, v in pairs(Loader._stats[stat]) do + if not vim.tbl_contains({ 'time', 'total' }, k) then + chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) } + chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' } + end + end + end + vim.api.nvim_echo(chunks, true, {}) + end + return Loader._stats +end + +return M -- cgit From 3c82ce0d625184a90e6b2dda96e38fcb44f901d2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:46:24 +0100 Subject: refactor(loader): use vim.fs --- runtime/lua/vim/fs.lua | 11 ++++++++--- runtime/lua/vim/loader.lua | 7 +------ 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 2c3fc64d57..c6371e9163 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -77,6 +77,8 @@ local function join_paths(...) return (table.concat({ ... }, '/'):gsub('//+', '/')) end +---@alias Iterator fun(): string?, string? + --- Return an iterator over the files and directories located in {path} --- ---@param path (string) An absolute or relative path to the directory to iterate @@ -100,10 +102,13 @@ function M.dir(path, opts) }) if not opts.depth or opts.depth == 1 then - return function(fs) + local fs = vim.loop.fs_scandir(M.normalize(path)) + return function() + if not fs then + return + end return vim.loop.fs_scandir_next(fs) - end, - vim.loop.fs_scandir(M.normalize(path)) + end end --- @async diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 41d5579664..bafe766a5d 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -450,12 +450,7 @@ function Loader.lsmod(path) if not Loader._indexed[path] then local start = uv.hrtime() Loader._indexed[path] = {} - local handle = vim.loop.fs_scandir(path .. '/lua') - while handle do - local name, t = vim.loop.fs_scandir_next(handle) - if not name then - break - end + for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name -- HACK: type is not always returned due to a bug in luv t = t or uv.fs_stat(modpath).type -- cgit From fab8dab6b6273767e2b31e4282cee5a11349ac8f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:47:06 +0100 Subject: refactor(loader): remove BufWritePost autocmd --- runtime/lua/vim/loader.lua | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index bafe766a5d..3dc1bd9574 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -409,19 +409,6 @@ function M.enable() break end end - - -- this will reset the top-mods in case someone adds a new - -- top-level lua module to a path already on the rtp - vim.api.nvim_create_autocmd('BufWritePost', { - group = vim.api.nvim_create_augroup('cache_topmods_reset', { clear = true }), - callback = function(event) - local bufname = event.match ---@type string - local idx = bufname:find('/lua/', 1, true) - if idx then - M.reset(bufname:sub(1, idx - 1)) - end - end, - }) end --- Disables the experimental Lua module loader: @@ -440,7 +427,6 @@ function M.disable() end end table.insert(package.loaders, 2, vim._load_package) - vim.api.nvim_del_augroup_by_name('cache_topmods_reset') end --- Return the top-level `/lua/*` modules for this path -- cgit From ffd8cd1a96a96b09351a5ffde1068c3595443128 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 12:49:38 +0100 Subject: refactor(loader): add typing for package.loaders --- runtime/lua/vim/loader.lua | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 3dc1bd9574..19df7e4383 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -1,5 +1,8 @@ local uv = vim.loop +--- @type (fun(modename: string): fun()|string)[] +local loaders = package.loaders + local M = {} ---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} @@ -399,13 +402,13 @@ function M.enable() vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') _G.loadfile = Loader.loadfile -- add lua loader - table.insert(package.loaders, 2, Loader.loader) + table.insert(loaders, 2, Loader.loader) -- add libs loader - table.insert(package.loaders, 3, Loader.loader_lib) + table.insert(loaders, 3, Loader.loader_lib) -- remove Neovim loader - for l, loader in ipairs(package.loaders) do + for l, loader in ipairs(loaders) do if loader == vim._load_package then - table.remove(package.loaders, l) + table.remove(loaders, l) break end end @@ -420,13 +423,12 @@ function M.disable() end M.enabled = false _G.loadfile = Loader._loadfile - ---@diagnostic disable-next-line: no-unknown - for l, loader in ipairs(package.loaders) do + for l, loader in ipairs(loaders) do if loader == Loader.loader or loader == Loader.loader_lib then - table.remove(package.loaders, l) + table.remove(loaders, l) end end - table.insert(package.loaders, 2, vim._load_package) + table.insert(loaders, 2, vim._load_package) end --- Return the top-level `/lua/*` modules for this path @@ -466,9 +468,9 @@ end --- Debug function that wrapps all loaders and tracks stats ---@private function M._profile_loaders() - for l, loader in pairs(package.loaders) do + for l, loader in pairs(loaders) do local loc = debug.getinfo(loader, 'Sn').source:sub(2) - package.loaders[l] = function(modname) + loaders[l] = function(modname) local start = vim.loop.hrtime() local ret = loader(modname) Loader.track('loader ' .. l .. ': ' .. loc, start) -- cgit From 25fa051fa157ca4bce712e61602447a4222581ca Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 13:01:48 +0100 Subject: feat(vim.fs): improve normalize - Add options argument with an option to expand env vars - Resolve '//' -> '/' - Use in vim.loader --- runtime/lua/vim/fs.lua | 34 +++++++++++++++++++++++++--------- runtime/lua/vim/loader.lua | 20 +++++--------------- 2 files changed, 30 insertions(+), 24 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index c6371e9163..407b334f20 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -321,16 +321,32 @@ end --- --- ---@param path (string) Path to normalize +---@param opts table|nil Options: +--- - expand_env: boolean Expand environment variables (default: true) ---@return (string) Normalized path -function M.normalize(path) - vim.validate({ path = { path, 's' } }) - return ( - path - :gsub('^~$', vim.loop.os_homedir()) - :gsub('^~/', vim.loop.os_homedir() .. '/') - :gsub('%$([%w_]+)', vim.loop.os_getenv) - :gsub('\\', '/') - ) +function M.normalize(path, opts) + opts = opts or {} + + vim.validate({ + path = { path, { 'string' } }, + expand_env = { opts.expand_env, { 'boolean' }, true }, + }) + + if path:sub(1, 1) == '~' then + local home = vim.loop.os_homedir() or '~' + if home:sub(-1) == '\\' or home:sub(-1) == '/' then + home = home:sub(1, -2) + end + path = home .. path:sub(2) + end + + if opts.expand_env == nil or opts.expand_env then + path = path:gsub('%$([%w_]+)', vim.loop.os_getenv) + end + + path = path:gsub('\\', '/'):gsub('/+', '/') + + return path:sub(-1) == '/' and path:sub(1, -2) or path end return M diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 19df7e4383..eccf95e2f2 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -49,19 +49,9 @@ function Loader.track(stat, start) Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start end ---- slightly faster/different version than vim.fs.normalize ---- we also need to have it here, since the loader will load vim.fs ---@private -function Loader.normalize(path) - if path:sub(1, 1) == '~' then - local home = vim.loop.os_homedir() or '~' - if home:sub(-1) == '\\' or home:sub(-1) == '/' then - home = home:sub(1, -2) - end - path = home .. path:sub(2) - end - path = path:gsub('\\', '/'):gsub('/+', '/') - return path:sub(-1) == '/' and path:sub(1, -2) or path +local function normalize(path) + return vim.fs.normalize(path, { expand_env = false }) end --- Gets the rtp excluding after directories. @@ -80,7 +70,7 @@ function Loader.get_rtp() if key ~= Loader._rtp_key then Loader._rtp = {} for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do - path = Loader.normalize(path) + path = normalize(path) -- skip after directories if path:sub(-6, -1) ~= '/after' @@ -210,7 +200,7 @@ end -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env, hash) local start = uv.hrtime() - filename = Loader.normalize(filename) + filename = normalize(filename) mode = nil -- ignore mode, since we byte-compile the lua source files local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) Loader.track('loadfile', start) @@ -383,7 +373,7 @@ end ---@param path string? path to reset function M.reset(path) if path then - Loader._indexed[Loader.normalize(path)] = nil + Loader._indexed[normalize(path)] = nil else Loader._indexed = {} end -- cgit From fd70a9dca199aae1eb7714608d63e8b2e63fb293 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 26 Mar 2023 13:23:34 +0100 Subject: refactor(loader): simplify tracking logic --- runtime/lua/vim/loader.lua | 74 +++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 44 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index eccf95e2f2..7b6b9cb563 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -41,14 +41,6 @@ local Loader = { }, } ---- Tracks the time spent in a function ----@private -function Loader.track(stat, start) - Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } - Loader._stats[stat].total = Loader._stats[stat].total + 1 - Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start -end - ---@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) @@ -60,9 +52,7 @@ end --- @return string[] rtp, boolean updated ---@private function Loader.get_rtp() - local start = uv.hrtime() if vim.in_fast_event() then - Loader.track('get_rtp', start) return (Loader._rtp or {}), false end local updated = false @@ -82,7 +72,6 @@ function Loader.get_rtp() updated = true Loader._rtp_key = key end - Loader.track('get_rtp', start) return Loader._rtp, updated end @@ -118,7 +107,6 @@ end ---@return CacheEntry? ---@private function Loader.read(name) - local start = uv.hrtime() local cname = Loader.cache_file(name) local f = uv.fs_open(cname, 'r', 438) if f then @@ -136,7 +124,6 @@ function Loader.read(name) if tonumber(header[1]) ~= Loader.VERSION then return end - Loader.track('read', start) return { hash = { size = tonumber(header[2]), @@ -145,7 +132,6 @@ function Loader.read(name) chunk = data:sub(zero + 1), } end - Loader.track('read', start) end --- The `package.loaders` loader for lua files using the cache. @@ -153,14 +139,11 @@ end ---@return string|function ---@private function Loader.loader(modname) - local start = uv.hrtime() local ret = M.find(modname)[1] if ret then local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) - Loader.track('loader', start) return chunk or error(err) end - Loader.track('loader', start) return '\ncache_loader: module ' .. modname .. ' not found' end @@ -169,7 +152,6 @@ end ---@return string|function ---@private function Loader.loader_lib(modname) - local start = uv.hrtime() local sysname = uv.os_uname().sysname:lower() or '' local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true) local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1] @@ -183,10 +165,8 @@ function Loader.loader_lib(modname) local dash = modname:find('-', 1, true) local funcname = dash and modname:sub(dash + 1) or modname local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_')) - Loader.track('loader_lib', start) return chunk or error(err) end - Loader.track('loader_lib', start) return '\ncache_loader_lib: module ' .. modname .. ' not found' end @@ -199,12 +179,9 @@ end ---@private -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env, hash) - local start = uv.hrtime() - filename = normalize(filename) - mode = nil -- ignore mode, since we byte-compile the lua source files - local chunk, err = Loader.load(filename, { mode = mode, env = env, hash = hash }) - Loader.track('loadfile', start) - return chunk, err + -- ignore mode, since we byte-compile the lua source files + mode = nil + return Loader.load(normalize(filename), { mode = mode, env = env, hash = hash }) end --- Checks whether two cache hashes are the same based on: @@ -232,8 +209,6 @@ end ---@return function?, string? error_message ---@private function Loader.load(modpath, opts) - local start = uv.hrtime() - opts = opts or {} local hash = opts.hash or uv.fs_stat(modpath) ---@type function?, string? @@ -241,9 +216,7 @@ function Loader.load(modpath, opts) if not hash then -- trigger correct error - chunk, err = Loader._loadfile(modpath, opts.mode, opts.env) - Loader.track('load', start) - return chunk, err + return Loader._loadfile(modpath, opts.mode, opts.env) end local entry = Loader.read(modpath) @@ -251,7 +224,6 @@ function Loader.load(modpath, opts) -- found in cache and up to date chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env) if not (err and err:find('cannot load incompatible bytecode', 1, true)) then - Loader.track('load', start) return chunk, err end end @@ -262,7 +234,6 @@ function Loader.load(modpath, opts) entry.chunk = string.dump(chunk) Loader.write(modpath, entry) end - Loader.track('load', start) return chunk, err end @@ -280,7 +251,6 @@ end --- - modname: (string) the name of the module --- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"` function M.find(modname, opts) - local start = uv.hrtime() opts = opts or {} modname = modname:gsub('/', '.') @@ -359,7 +329,6 @@ function M.find(modname, opts) _find(opts.paths) end - Loader.track('find', start) if #results == 0 then -- module not found Loader._stats.find.not_found = Loader._stats.find.not_found + 1 @@ -426,7 +395,6 @@ end ---@private function Loader.lsmod(path) if not Loader._indexed[path] then - local start = uv.hrtime() Loader._indexed[path] = {} for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name @@ -450,23 +418,41 @@ function Loader.lsmod(path) end end end - Loader.track('lsmod', start) end return Loader._indexed[path] end +--- Tracks the time spent in a function +--- @generic F: function +--- @param f F +--- @return F +--- @private +function Loader.track(stat, f) + return function(...) + local start = vim.loop.hrtime() + local r = { f(...) } + Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 } + Loader._stats[stat].total = Loader._stats[stat].total + 1 + Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start + return unpack(r, 1, table.maxn(r)) + end +end + +Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) +Loader.read = Loader.track('read', Loader.read) +Loader.loader = Loader.track('loader', Loader.loader) +Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) +Loader.loadfile = Loader.track('loadfile', Loader.loadfile) +Loader.load = Loader.track('load', Loader.load) +M.find = Loader.track('find', M.find) +Loader.lsmod = Loader.track('lsmod', Loader.lsmod) + --- Debug function that wrapps all loaders and tracks stats ---@private function M._profile_loaders() for l, loader in pairs(loaders) do local loc = debug.getinfo(loader, 'Sn').source:sub(2) - loaders[l] = function(modname) - local start = vim.loop.hrtime() - local ret = loader(modname) - Loader.track('loader ' .. l .. ': ' .. loc, start) - Loader.track('loader_all', start) - return ret - end + loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) end end -- cgit From 196f69909db9e44477731cd805dac119a54725c2 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 29 Mar 2023 10:23:52 +0200 Subject: vim-patch:9.0.1430: Livebook files are not recognized (#22808) Problem: Livebook files are not recognized. Solution: Add a pattern for Livebook files. (Mathias Jean Johansen, closes vim/vim#12203) https://github.com/vim/vim/commit/64002035178ac3e0d9ab7269d1bf06c6ede5a854 Co-authored-by: Mathias Jean Johansen --- 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 740304df15..87439f9f0c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -615,6 +615,7 @@ local extension = { asd = 'lisp', lt = 'lite', lite = 'lite', + livemd = 'livebook', lgt = 'logtalk', lotos = 'lotos', lot = 'lotos', -- cgit From 61e54f26361b2e7d08eabde9a4cbf42aaa41683b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 30 Mar 2023 10:26:28 +0100 Subject: feat: add `vim.treesitter.language.get_filetypes()` (#22643) --- runtime/lua/vim/treesitter.lua | 33 +++++++---------- runtime/lua/vim/treesitter/language.lua | 57 +++++++++++++++++++---------- runtime/lua/vim/treesitter/languagetree.lua | 7 +--- 3 files changed, 51 insertions(+), 46 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4c3b17daa4..2594c1672d 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -58,9 +58,6 @@ function M._create_parser(bufnr, lang, opts) vim.fn.bufload(bufnr) - local ft = vim.bo[bufnr].filetype - M.language.add(lang, { filetype = ft ~= '' and ft or nil }) - local self = LanguageTree.new(bufnr, lang, opts) ---@private @@ -94,7 +91,12 @@ function M._create_parser(bufnr, lang, opts) return self end ---- Returns the parser for a specific buffer and filetype and attaches it to the buffer +--- @private +local function valid_lang(lang) + return lang and lang ~= '' +end + +--- Returns the parser for a specific buffer and attaches it to the buffer --- --- If needed, this will create the parser. --- @@ -110,18 +112,12 @@ function M.get_parser(bufnr, lang, opts) bufnr = a.nvim_get_current_buf() end - if lang == nil then - local ft = vim.bo[bufnr].filetype - if ft ~= '' then - lang = M.language.get_lang(ft) or ft - -- TODO(lewis6991): we should error here and not default to ft - -- if not lang then - -- error(string.format('filetype %s of buffer %d is not associated with any lang', ft, bufnr)) - -- end - else - if parsers[bufnr] then - return parsers[bufnr] - end + if not valid_lang(lang) then + lang = M.language.get_lang(vim.bo[bufnr].filetype) or vim.bo[bufnr].filetype + end + + if not valid_lang(lang) then + if not parsers[bufnr] then error( string.format( 'There is no parser available for buffer %d and one could not be' @@ -131,9 +127,7 @@ function M.get_parser(bufnr, lang, opts) ) ) end - end - - if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then + elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then parsers[bufnr] = M._create_parser(bufnr, lang, opts) end @@ -164,7 +158,6 @@ function M.get_string_parser(str, lang, opts) str = { str, 'string' }, lang = { lang, 'string' }, }) - M.language.add(lang) return LanguageTree.new(str, lang, opts) end diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 974d66ec05..b1c788e6ba 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -6,9 +6,25 @@ local M = {} ---@type table local ft_to_lang = {} ----@param filetype string ----@return string|nil +--- Get the filetypes associated with the parser named {lang}. +--- @param lang string Name of parser +--- @return string[] filetypes +function M.get_filetypes(lang) + local r = {} ---@type string[] + for ft, p in pairs(ft_to_lang) do + if p == lang then + r[#r + 1] = ft + end + end + return r +end + +--- @param filetype string +--- @return string|nil function M.get_lang(filetype) + if filetype == '' then + return + end return ft_to_lang[filetype] end @@ -35,16 +51,14 @@ end ---@field filetype? string|string[] ---@field symbol_name? string ---- Asserts that a parser for the language {lang} is installed. +--- Load parser with name {lang} --- --- Parsers are searched in the `parser` runtime directory, or the provided {path} --- ----@param lang string Language the parser should parse (alphanumerical and `_` only) +---@param lang string Name of the parser (alphanumerical and `_` only) ---@param opts (table|nil) Options: ---- - filetype (string|string[]) Filetype(s) that lang can be parsed with. ---- Note this is not strictly the same as lang since a single lang can ---- parse multiple filetypes. ---- Defaults to lang. +--- - filetype (string|string[]) Default filetype the parser should be associated with. +--- Defaults to {lang}. --- - path (string|nil) Optional path the parser is located at --- - symbol_name (string|nil) Internal symbol name for the language to load function M.add(lang, opts) @@ -61,7 +75,7 @@ function M.add(lang, opts) filetype = { filetype, { 'string', 'table' }, true }, }) - M.register(lang, filetype or lang) + M.register(lang, filetype) if vim._ts_has_language(lang) then return @@ -83,23 +97,26 @@ function M.add(lang, opts) vim._ts_add_language(path, lang, symbol_name) end ---- Register a lang to be used for a filetype (or list of filetypes). ----@param lang string Language to register ----@param filetype string|string[] Filetype(s) to associate with lang +--- @private +--- @param x string|string[] +--- @return string[] +local function ensure_list(x) + if type(x) == 'table' then + return x + end + return { x } +end + +--- Register a parser named {lang} to be used for {filetype}(s). +--- @param lang string Name of parser +--- @param filetype string|string[] Filetype(s) to associate with lang function M.register(lang, filetype) vim.validate({ lang = { lang, 'string' }, filetype = { filetype, { 'string', 'table' } }, }) - local filetypes ---@type string[] - if type(filetype) == 'string' then - filetypes = { filetype } - else - filetypes = filetype - end - - for _, f in ipairs(filetypes) do + for _, f in ipairs(ensure_list(filetype)) do if f ~= '' then ft_to_lang[f] = lang end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 82e507551d..922e4881ca 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -76,7 +76,7 @@ LanguageTree.__index = LanguageTree --- "injected" language parsers, which themselves may inject other languages, recursively. --- ---@param source (integer|string) Buffer or text string to parse ----@param lang string Root language of this tree +---@param lang string|nil Root language of this tree ---@param opts (table|nil) Optional arguments: --- - injections table Map of language to injection query strings. Overrides the --- built-in runtime file searching for language injections. @@ -86,11 +86,6 @@ function LanguageTree.new(source, lang, opts) ---@type LanguageTreeOpts opts = opts or {} - if opts.queries then - a.nvim_err_writeln("'queries' is no longer supported. Use 'injections' now") - opts.injections = opts.queries - end - local injections = opts.injections or {} local self = setmetatable({ _source = source, -- cgit From 226a6c3eaef2a7220841d3d5e69e1baf543b3d6f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 30 Mar 2023 14:49:58 +0100 Subject: feat(diagnostic): add support for tags The LSP spec supports two tags that can be added to diagnostics: unnecessary and deprecated. Extend vim.diagnostic to be able to handle these. --- runtime/lua/vim/diagnostic.lua | 12 +++++++++ runtime/lua/vim/lsp/diagnostic.lua | 54 +++++++++++++++++++++++++++++--------- runtime/lua/vim/lsp/protocol.lua | 2 ++ runtime/lua/vim/lsp/types.lua | 17 ++++++++++++ 4 files changed, 73 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 56532d0184..714038f8e4 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -483,6 +483,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) local diagnostics = get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true) local line_diagnostics = diagnostic_lines(diagnostics) + for i = 0, line_count do local offset = i * (search_forward and 1 or -1) local lnum = position[1] + offset @@ -752,6 +753,7 @@ end ---@field message string ---@field source nil|string ---@field code nil|string +---@field _tags { deprecated: boolean, unnecessary: boolean} ---@field user_data nil|any arbitrary data plugins can add --- Get current diagnostics. @@ -948,6 +950,16 @@ M.handlers.underline = { higroup = underline_highlight_map.Error end + if diagnostic._tags then + -- TODO(lewis6991): we should be able to stack these. + if diagnostic._tags.unnecessary then + higroup = 'DiagnosticUnnecessary' + end + if diagnostic._tags.deprecated then + higroup = 'DiagnosticDeprecated' + end + end + vim.highlight.range( bufnr, underline_ns, diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index b27bf6e425..dcc8f6549c 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -1,13 +1,6 @@ ---@brief lsp-diagnostic ---- ----@class Diagnostic ----@field range Range ----@field message string ----@field severity DiagnosticSeverity|nil ----@field code integer | string ----@field source string ----@field tags DiagnosticTag[] ----@field relatedInformation DiagnosticRelatedInformation[] + +local protocol = require('vim.lsp.protocol') local M = {} @@ -22,14 +15,16 @@ local function get_client_id(client_id) end ---@private +---@param severity lsp.DiagnosticSeverity local function severity_lsp_to_vim(severity) if type(severity) == 'string' then - severity = vim.lsp.protocol.DiagnosticSeverity[severity] + severity = protocol.DiagnosticSeverity[severity] end return severity end ---@private +---@return lsp.DiagnosticSeverity local function severity_vim_to_lsp(severity) if type(severity) == 'string' then severity = vim.diagnostic.severity[severity] @@ -38,6 +33,7 @@ local function severity_vim_to_lsp(severity) end ---@private +---@return integer local function line_byte_from_position(lines, lnum, col, offset_encoding) if not lines or offset_encoding == 'utf-8' then return col @@ -77,12 +73,41 @@ local function get_buf_lines(bufnr) return lines end +--- @private +--- @param diagnostic lsp.Diagnostic +--- @param client_id integer +--- @return table? +local function tags_lsp_to_vim(diagnostic, client_id) + local tags ---@type table? + for _, tag in ipairs(diagnostic.tags or {}) do + if tag == protocol.DiagnosticTag.Unnecessary then + tags = tags or {} + tags.unnecessary = true + elseif tag == protocol.DiagnosticTag.Deprecated then + tags = tags or {} + tags.deprecated = true + else + vim.notify_once( + string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id), + vim.log.levels.WARN + ) + end + end + return tags +end + ---@private +---@param diagnostics lsp.Diagnostic[] +---@param bufnr integer +---@param client_id integer +---@return Diagnostic[] local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) local buf_lines = get_buf_lines(bufnr) local client = vim.lsp.get_client_by_id(client_id) local offset_encoding = client and client.offset_encoding or 'utf-16' + ---@diagnostic disable-next-line:no-unknown return vim.tbl_map(function(diagnostic) + ---@cast diagnostic lsp.Diagnostic local start = diagnostic.range.start local _end = diagnostic.range['end'] return { @@ -94,12 +119,12 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) message = diagnostic.message, source = diagnostic.source, code = diagnostic.code, + tags = tags_lsp_to_vim(diagnostic, client_id), user_data = { lsp = { -- usage of user_data.lsp.code is deprecated in favor of the top-level code field code = diagnostic.code, codeDescription = diagnostic.codeDescription, - tags = diagnostic.tags, relatedInformation = diagnostic.relatedInformation, data = diagnostic.data, }, @@ -108,9 +133,13 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) end, diagnostics) end ----@private +--- @private +--- @param diagnostics Diagnostic[] +--- @return lsp.Diagnostic[] local function diagnostic_vim_to_lsp(diagnostics) + ---@diagnostic disable-next-line:no-unknown return vim.tbl_map(function(diagnostic) + ---@cast diagnostic Diagnostic return vim.tbl_extend('keep', { -- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp range = { @@ -131,6 +160,7 @@ local function diagnostic_vim_to_lsp(diagnostics) end, diagnostics) end +---@type table local _client_namespaces = {} --- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|. diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 1686e22c48..f4489ad17d 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -21,6 +21,7 @@ end --]=] local constants = { + --- @enum lsp.DiagnosticSeverity DiagnosticSeverity = { -- Reports an error. Error = 1, @@ -32,6 +33,7 @@ local constants = { Hint = 4, }, + --- @enum lsp.DiagnosticTag DiagnosticTag = { -- Unused or unnecessary code Unnecessary = 1, diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua index 1aea6841ee..779f313aa7 100644 --- a/runtime/lua/vim/lsp/types.lua +++ b/runtime/lua/vim/lsp/types.lua @@ -18,3 +18,20 @@ ---@class lsp.FileEvent ---@field uri string ---@field type lsp.FileChangeType + +---@class lsp.Position +---@field line integer +---@field character integer + +---@class lsp.Range +---@field start lsp.Position +---@field end lsp.Position + +---@class lsp.Diagnostic +---@field range lsp.Range +---@field message string +---@field severity? lsp.DiagnosticSeverity +---@field code integer | string +---@field source string +---@field tags? lsp.DiagnosticTag[] +---@field relatedInformation DiagnosticRelatedInformation[] -- cgit From 4cff3aceeac2027d82080649ff1c8be9ffa87b90 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 31 Mar 2023 09:43:13 +0100 Subject: fix(loader): disable profiling by default --- runtime/lua/vim/loader.lua | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7b6b9cb563..7ac7f44338 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -438,21 +438,27 @@ function Loader.track(stat, f) end end -Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) -Loader.read = Loader.track('read', Loader.read) -Loader.loader = Loader.track('loader', Loader.loader) -Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) -Loader.loadfile = Loader.track('loadfile', Loader.loadfile) -Loader.load = Loader.track('load', Loader.load) -M.find = Loader.track('find', M.find) -Loader.lsmod = Loader.track('lsmod', Loader.lsmod) +---@class ProfileOpts +---@field loaders? boolean Add profiling to the loaders --- Debug function that wrapps all loaders and tracks stats ---@private -function M._profile_loaders() - for l, loader in pairs(loaders) do - local loc = debug.getinfo(loader, 'Sn').source:sub(2) - loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) +---@param opts ProfileOpts? +function M._profile(opts) + Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp) + Loader.read = Loader.track('read', Loader.read) + Loader.loader = Loader.track('loader', Loader.loader) + Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib) + Loader.loadfile = Loader.track('loadfile', Loader.loadfile) + Loader.load = Loader.track('load', Loader.load) + M.find = Loader.track('find', M.find) + Loader.lsmod = Loader.track('lsmod', Loader.lsmod) + + if opts and opts.loaders then + for l, loader in pairs(loaders) do + local loc = debug.getinfo(loader, 'Sn').source:sub(2) + loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader) + end end end -- cgit From ed10e4ef60c63d924b9969abdf77adaad506b676 Mon Sep 17 00:00:00 2001 From: Akin <22454918+akinsho@users.noreply.github.com> Date: Fri, 31 Mar 2023 11:23:19 +0100 Subject: fix(diagnostic): use correct field name for tags (#22835) LSP tags are added to the diagnostic as "tags" but referred to as "_tags" in the diagnostic underline handler --- runtime/lua/vim/lsp/diagnostic.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index dcc8f6549c..3efa5c51ff 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -119,7 +119,7 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) message = diagnostic.message, source = diagnostic.source, code = diagnostic.code, - tags = tags_lsp_to_vim(diagnostic, client_id), + _tags = tags_lsp_to_vim(diagnostic, client_id), user_data = { lsp = { -- usage of user_data.lsp.code is deprecated in favor of the top-level code field -- cgit From 83bfd94d1df5eecb8e4069a227c7d24598636d63 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 31 Mar 2023 13:05:22 +0100 Subject: refactor(loader): cache hash information Whenever we run fs_stat() on a path, save this information in the loader so it can be re-used. - Loader.loadfile: Remove arguments `hash` as it is no longer needed. - Loader.loader: Use _G.loadstring instead of Loader.load This allows plugins to wrap loadstring to inspection and profiling - factor out read file logic --- runtime/lua/vim/loader.lua | 62 +++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7ac7f44338..201de18497 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -5,7 +5,7 @@ local loaders = package.loaders local M = {} ----@alias CacheHash {mtime: {sec:number, nsec:number}, size:number} +---@alias CacheHash {mtime: {sec:number, nsec:number}, size:number, type: string} ---@alias CacheEntry {hash:CacheHash, chunk:string} ---@class ModuleFindOpts @@ -32,6 +32,8 @@ local Loader = { VERSION = 3, ---@type table> _indexed = {}, + ---@type table + _hashes = {}, ---@type table _topmods = {}, _loadfile = loadfile, @@ -41,6 +43,18 @@ local Loader = { }, } +--- @param path string +--- @return uv.fs_stat.result +--- @private +function Loader.get_hash(path) + if not Loader._hashes[path] then + -- Note we must never save a stat for a non-existent path. + -- For non-existent paths fs_stat() will return nil. + Loader._hashes[path] = uv.fs_stat(path) + end + return Loader._hashes[path] +end + ---@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) @@ -102,18 +116,28 @@ function Loader.write(name, entry) uv.fs_close(f) end +--- @param path string +--- @param mode integer +--- @return string? data +--- @private +local function readfile(path, mode) + local f = uv.fs_open(path, 'r', mode) + if f then + local hash = assert(uv.fs_fstat(f)) + local data = uv.fs_read(f, hash.size, 0) --[[@as string?]] + uv.fs_close(f) + return data + end +end + --- Loads the cache entry for a given module or file ---@param name string module name or filename ---@return CacheEntry? ---@private function Loader.read(name) local cname = Loader.cache_file(name) - local f = uv.fs_open(cname, 'r', 438) - if f then - local hash = uv.fs_fstat(f) --[[@as CacheHash]] - local data = uv.fs_read(f, hash.size, 0) --[[@as string]] - uv.fs_close(f) - + local data = readfile(cname, 438) + if data then local zero = data:find('\0', 1, true) if not zero then return @@ -141,7 +165,9 @@ end function Loader.loader(modname) local ret = M.find(modname)[1] if ret then - local chunk, err = Loader.load(ret.modpath, { hash = ret.stat }) + -- Make sure to call the global loadfile so we respect any augmentations done elsewhere. + -- E.g. profiling + local chunk, err = loadfile(ret.modpath) return chunk or error(err) end return '\ncache_loader: module ' .. modname .. ' not found' @@ -171,17 +197,17 @@ function Loader.loader_lib(modname) end --- `loadfile` using the cache +--- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible. ---@param filename? string ---@param mode? "b"|"t"|"bt" ---@param env? table ----@param hash? CacheHash ---@return function?, string? error_message ---@private -- luacheck: ignore 312 -function Loader.loadfile(filename, mode, env, hash) +function Loader.loadfile(filename, mode, env) -- ignore mode, since we byte-compile the lua source files mode = nil - return Loader.load(normalize(filename), { mode = mode, env = env, hash = hash }) + return Loader.load(normalize(filename), { mode = mode, env = env }) end --- Checks whether two cache hashes are the same based on: @@ -201,8 +227,7 @@ end --- Loads the given module path using the cache ---@param modpath string ----@param opts? {hash?: CacheHash, mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: ---- - hash: (table) the hash of the file to load if it is already known. (defaults to `vim.loop.fs_stat({modpath})`) +---@param opts? {mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module: --- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`) --- - env: (table) the environment to load the module in. (defaults to `nil`) ---@see |luaL_loadfile()| @@ -210,7 +235,7 @@ end ---@private function Loader.load(modpath, opts) opts = opts or {} - local hash = opts.hash or uv.fs_stat(modpath) + local hash = Loader.get_hash(modpath) ---@type function?, string? local chunk, err @@ -301,7 +326,7 @@ function M.find(modname, opts) for _, pattern in ipairs(patterns) do local modpath = path .. pattern Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1 - local hash = uv.fs_stat(modpath) + local hash = Loader.get_hash(modpath) if hash then results[#results + 1] = { modpath = modpath, stat = hash, modname = modname } if not continue() then @@ -337,7 +362,7 @@ function M.find(modname, opts) return results end ---- Resets the topmods cache for the path, or all the paths +--- Resets the cache for the path, or all the paths --- if path is nil. ---@param path string? path to reset function M.reset(path) @@ -346,6 +371,9 @@ function M.reset(path) else Loader._indexed = {} end + + -- Path could be a directory so just clear all the hashes. + Loader._hashes = {} end --- Enables the experimental Lua module loader: @@ -399,7 +427,7 @@ function Loader.lsmod(path) for name, t in vim.fs.dir(path .. '/lua') do local modpath = path .. '/lua/' .. name -- HACK: type is not always returned due to a bug in luv - t = t or uv.fs_stat(modpath).type + t = t or Loader.get_hash(modpath).type ---@type string local topname local ext = name:sub(-4) -- cgit From d7f7450017b9b05303698a6cda54303ef22c63b3 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 31 Mar 2023 17:09:00 +0200 Subject: refactor(treesitter)!: rename help parser to vimdoc --- runtime/lua/vim/treesitter/language.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index b1c788e6ba..5b74bb6200 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -4,7 +4,9 @@ local a = vim.api local M = {} ---@type table -local ft_to_lang = {} +local ft_to_lang = { + help = 'vimdoc', +} --- Get the filetypes associated with the parser named {lang}. --- @param lang string Name of parser -- cgit From 6a4ebf894fa39bfb09695a129a3300cb99408542 Mon Sep 17 00:00:00 2001 From: Sizhe Zhao Date: Sat, 1 Apr 2023 21:47:20 +0800 Subject: fix(health): stop using deprecated ts.language.inspect_language() (#22850) --- 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 fd1188fde4..dabf2cdf6c 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -22,7 +22,7 @@ function M.check() ) ) else - local lang = ts.language.inspect_language(parsername) + local lang = ts.language.inspect(parsername) health.report_ok( string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) -- cgit From 643c0ed571f1ac6e83f73ab2593132901278b4da Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Sat, 1 Apr 2023 08:02:58 -0600 Subject: feat: allow function passed to defaulttable to take an argument (#22839) Pass the value of the key being accessed to the create function, to allow users to dynamically generate default values. --- runtime/lua/vim/shared.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 884929e33a..9e337e93e8 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -796,13 +796,15 @@ end --- a.b.c = 1 --- --- ----@param create function|nil The function called to create a missing value. +---@param create function?(key:any):any The function called to create a missing value. ---@return table Empty table with metamethod function vim.defaulttable(create) - create = create or vim.defaulttable + create = create or function(_) + return vim.defaulttable() + end return setmetatable({}, { __index = function(tbl, key) - rawset(tbl, key, create()) + rawset(tbl, key, create(key)) return rawget(tbl, key) end, }) -- cgit From a93024555720325ecede9d2d15e4bd0f1fd82739 Mon Sep 17 00:00:00 2001 From: Luuk van Baal Date: Sat, 25 Mar 2023 15:19:22 +0100 Subject: refactor(lua): get all marks instead of iterating over namespaces Inspector now also includes highlights set in anonymous namespaces. Close #22732 --- runtime/lua/vim/_inspector.lua | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 92d380b08c..05983d3f0d 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -81,6 +81,12 @@ function vim.inspect_pos(bufnr, row, col, filter) end end + -- namespace id -> name map + local nsmap = {} + for name, id in pairs(vim.api.nvim_get_namespaces()) do + nsmap[id] = name + end + --- Convert an extmark tuple into a map-like table --- @private local function to_map(extmark) @@ -90,6 +96,8 @@ function vim.inspect_pos(bufnr, row, col, filter) col = extmark[3], opts = resolve_hl(extmark[4]), } + extmark.ns_id = extmark.opts.ns_id + extmark.ns = nsmap[extmark.ns_id] or '' extmark.end_row = extmark.opts.end_row or extmark.row -- inclusive extmark.end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive return extmark @@ -104,17 +112,9 @@ function vim.inspect_pos(bufnr, row, col, filter) end -- all extmarks at this position - local extmarks = {} - for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do - local ns_marks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true }) - ns_marks = vim.tbl_map(to_map, ns_marks) - ns_marks = vim.tbl_filter(is_here, ns_marks) - for _, mark in ipairs(ns_marks) do - mark.ns_id = nsid - mark.ns = ns - end - vim.list_extend(extmarks, ns_marks) - end + local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true }) + extmarks = vim.tbl_map(to_map, extmarks) + extmarks = vim.tbl_filter(is_here, extmarks) if filter.semantic_tokens then results.semantic_tokens = vim.tbl_filter(function(extmark) -- cgit From e30cc8be1950a6d1dec7395807966e1b5d0d9194 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 3 Apr 2023 16:27:49 +0200 Subject: vim-patch:9.0.1438: .fs files are falsely recognized as forth files (#22871) Problem: .fs files are falsely recognized as forth files. Solution: Check 100 lines for something that looks like forth. (Johan Kotlinski, closes vim/vim#12219, closes vim/vim#11988) https://github.com/vim/vim/commit/065088d5549e7711668321cc5a77c9a9b684b142 Co-authored-by: Johan Kotlinski --- runtime/lua/vim/filetype/detect.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index b3d9fedeae..d1eabadc4a 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -473,12 +473,12 @@ function M.fs(bufnr) if vim.g.filetype_fs then return vim.g.filetype_fs end - local line = nextnonblank(bufnr, 1) - if findany(line, { '^%s*%.?%( ', '^%s*\\G? ', '^\\$', '^%s*: %S' }) then - return 'forth' - else - return 'fsharp' + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if line:find('^[:(\\] ') then + return 'forth' + end end + return 'fsharp' end function M.git(bufnr) -- cgit From 469e6bfc56aa18350bfab13bef8a51b02a5b3c65 Mon Sep 17 00:00:00 2001 From: danilax999 <75566563+danilax999@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:26:21 +0300 Subject: fix(treesitter): use capture metadata range if exists use `treesitter.get_range` instead of inline expression --- runtime/lua/vim/treesitter/highlighter.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 729cd34090..d3cc1b698c 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -236,7 +236,8 @@ local function on_line_impl(self, buf, line, is_spell_nav) break end - local start_row, start_col, end_row, end_col = node:range() + local range = vim.treesitter.get_range(node, buf, metadata[capture]) + local start_row, start_col, _, end_row, end_col, _ = unpack(range) local hl = highlighter_query.hl_cache[capture] local capture_name = highlighter_query:query().captures[capture] -- cgit From 090ade4af6344a7bc4ee56a8052c0739c0428c04 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 4 Apr 2023 12:58:16 +0100 Subject: refactor(treesitter): delegate region calculation to treesitter (#22576) --- runtime/lua/vim/treesitter/languagetree.lua | 94 ++++++++++++----------------- 1 file changed, 38 insertions(+), 56 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 922e4881ca..cf0ecbd839 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -57,7 +57,9 @@ local Range = require('vim.treesitter._range') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language ----@field private _regions Range6[][] List of regions this tree should manage and parse +---@field private _regions Range6[][]? +---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 _source (integer|string) Buffer or string to parse ---@field private _trees TSTree[] Reference to parsed tree (one for each language) @@ -91,7 +93,6 @@ function LanguageTree.new(source, lang, opts) _source = source, _lang = lang, _children = {}, - _regions = {}, _trees = {}, _opts = opts, _injection_query = injections[lang] and query.parse(lang, injections[lang]) @@ -237,27 +238,21 @@ function LanguageTree:parse() --- At least 1 region is invalid if not self:is_valid(true) then - local function _parsetree(index) - local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[index], self._source) - - self:_do_callback('changedtree', tree_changes, tree) - self._trees[index] = tree - vim.list_extend(changes, tree_changes) - - total_parse_time = total_parse_time + parse_time - regions_parsed = regions_parsed + 1 - end - - if #self._regions > 0 then - for i, ranges in ipairs(self._regions) do - if not self._valid or not self._valid[i] then - self._parser:set_included_ranges(ranges) - _parsetree(i) - end + -- If there are no ranges, set to an empty list + -- so the included ranges in the parser are cleared. + for i, ranges in ipairs(self:included_regions()) do + if not self._valid or not self._valid[i] then + self._parser:set_included_ranges(ranges) + local parse_time, tree, tree_changes = + tcall(self._parser.parse, self._parser, self._trees[i], self._source) + + self:_do_callback('changedtree', tree_changes, tree) + self._trees[i] = tree + vim.list_extend(changes, tree_changes) + + total_parse_time = total_parse_time + parse_time + regions_parsed = regions_parsed + 1 end - else - _parsetree(1) end end @@ -403,7 +398,7 @@ function LanguageTree:_iter_regions(fn) local all_valid = true - for i, region in ipairs(self._regions) do + for i, region in ipairs(self:included_regions()) do if self._valid[i] == nil then self._valid[i] = true end @@ -454,7 +449,7 @@ function LanguageTree:set_included_regions(new_regions) end end - if #self._regions ~= #new_regions then + if #self:included_regions() ~= #new_regions then self._trees = {} self:invalidate() else @@ -462,13 +457,28 @@ function LanguageTree:set_included_regions(new_regions) return vim.deep_equal(new_regions[i], region) end) end + self._regions = new_regions end ---Gets the set of included regions ---@return integer[][] function LanguageTree:included_regions() - return self._regions + if self._regions then + return self._regions + end + + if #self._trees == 0 then + return { {} } + end + + local regions = {} ---@type Range6[][] + for i, _ in ipairs(self._trees) do + regions[i] = self._trees[i]:included_ranges(true) + end + + self._regions = regions + return regions end ---@private @@ -721,6 +731,8 @@ function LanguageTree:_edit( ) end + self._regions = nil + local changed_range = { start_row, start_col, @@ -730,42 +742,12 @@ function LanguageTree:_edit( end_byte_old, } - local new_range = { - start_row, - start_col, - start_byte, - end_row_new, - end_col_new, - end_byte_new, - } - - if #self._regions == 0 then - self._valid = false - end - -- Validate regions after editing the tree self:_iter_regions(function(_, region) - for i, r in ipairs(region) do + for _, r in ipairs(region) do if Range.intercepts(r, changed_range) then return false end - - -- Range after change. Adjust - if Range.cmp_pos.gt(r[1], r[2], changed_range[4], changed_range[5]) then - local byte_offset = new_range[6] - changed_range[6] - local row_offset = new_range[4] - changed_range[4] - - -- Update the range to avoid invalidation in set_included_regions() - -- which will compare the regions against the parsed injection regions - region[i] = { - r[1] + row_offset, - r[2], - r[3] + byte_offset, - r[4] + row_offset, - r[5], - r[6] + byte_offset, - } - end end return true end) -- cgit From b75acd2f94feac5b55eaa219e7beb2af93f15cf0 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 4 Apr 2023 13:07:27 +0100 Subject: fix(treesitter): disable folding in inspect_tree() (#22885) --- runtime/lua/vim/treesitter.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 2594c1672d..44bf4c9e20 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -538,6 +538,7 @@ function M.inspect_tree(opts) vim.wo[w].scrolloff = 5 vim.wo[w].wrap = false + vim.wo[w].foldmethod = 'manual' -- disable folding vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' -- cgit From a5c572bd446a89be2dccb2f7479ff1b017074640 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 19:07:33 +0200 Subject: docs: fix typos Co-authored-by: Gregory Anders Co-authored-by: Raphael Co-authored-by: C.D. MacEachern Co-authored-by: himanoa --- runtime/lua/vim/loader.lua | 4 ++-- runtime/lua/vim/shared.lua | 2 +- runtime/lua/vim/treesitter/_fold.lua | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 201de18497..38b1e9fc0f 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -11,7 +11,7 @@ local M = {} ---@class ModuleFindOpts ---@field all? boolean Search for all matches (defaults to `false`) ---@field rtp? boolean Search for modname in the runtime path (defaults to `true`) ----@field patterns? string[] Paterns to use (defaults to `{"/init.lua", ".lua"}`) +---@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`) ---@field paths? string[] Extra paths to search for modname ---@class ModuleInfo @@ -469,7 +469,7 @@ end ---@class ProfileOpts ---@field loaders? boolean Add profiling to the loaders ---- Debug function that wrapps all loaders and tracks stats +--- Debug function that wraps all loaders and tracks stats ---@private ---@param opts ProfileOpts? function M._profile(opts) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 9e337e93e8..eb734fb512 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -655,7 +655,7 @@ end --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} --- --> NOP (success) --- ---- vim.validate{arg1={1, {'string', table'}}} +--- vim.validate{arg1={1, {'string', 'table'}}} --- --> error('arg1: expected string|table, got number') --- --- diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 6547ab936e..7df93d1b2e 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -68,7 +68,7 @@ function FoldInfo:add_stop(lnum) self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1 end ----@packag +---@package ---@param lnum integer ---@return integer function FoldInfo:get_start(lnum) -- cgit From b1de4820b7b1a527f4d0cf9a20192d92bea1d9c4 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 1 Apr 2023 12:55:04 +0100 Subject: refactor(treesitter): move inspect_tree impl --- runtime/lua/vim/treesitter.lua | 205 +-------------------------- runtime/lua/vim/treesitter/playground.lua | 225 +++++++++++++++++++++++++++++- 2 files changed, 226 insertions(+), 204 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 44bf4c9e20..092fdf0ae2 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -502,209 +502,8 @@ end --- function, it accepts the buffer number of the source buffer as its only --- argument and should return a string. function M.inspect_tree(opts) - vim.validate({ - opts = { opts, 't', true }, - }) - - opts = opts or {} - - local Playground = require('vim.treesitter.playground') - local buf = a.nvim_get_current_buf() - local win = a.nvim_get_current_win() - local pg = assert(Playground:new(buf, opts.lang)) - - -- Close any existing playground window - if vim.b[buf].playground then - local w = vim.b[buf].playground - if a.nvim_win_is_valid(w) then - a.nvim_win_close(w, true) - end - end - - local w = opts.winid - if not w then - vim.cmd(opts.command or '60vnew') - w = a.nvim_get_current_win() - end - - local b = opts.bufnr - if b then - a.nvim_win_set_buf(w, b) - else - b = a.nvim_win_get_buf(w) - end - - vim.b[buf].playground = w - - vim.wo[w].scrolloff = 5 - vim.wo[w].wrap = false - vim.wo[w].foldmethod = 'manual' -- disable folding - vim.bo[b].buflisted = false - vim.bo[b].buftype = 'nofile' - vim.bo[b].bufhidden = 'wipe' - vim.bo[b].filetype = 'query' - - local title = opts.title - if not title then - local bufname = a.nvim_buf_get_name(buf) - title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) - elseif type(title) == 'function' then - title = title(buf) - end - - assert(type(title) == 'string', 'Window title must be a string') - a.nvim_buf_set_name(b, title) - - pg:draw(b) - - a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - a.nvim_buf_set_keymap(b, 'n', '', '', { - desc = 'Jump to the node under the cursor in the source buffer', - callback = function() - local row = a.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) - a.nvim_set_current_win(win) - a.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) - end, - }) - a.nvim_buf_set_keymap(b, 'n', 'a', '', { - desc = 'Toggle anonymous nodes', - callback = function() - local row, col = unpack(a.nvim_win_get_cursor(w)) - local curnode = pg:get(row) - while curnode and not curnode.named do - row = row - 1 - curnode = pg:get(row) - end - - pg.opts.anon = not pg.opts.anon - pg:draw(b) - - if not curnode then - return - end - - local id = curnode.id - for i, node in pg:iter() do - if node.id == id then - a.nvim_win_set_cursor(w, { i, col }) - break - end - end - end, - }) - a.nvim_buf_set_keymap(b, 'n', 'I', '', { - desc = 'Toggle language display', - callback = function() - pg.opts.lang = not pg.opts.lang - pg:draw(b) - end, - }) - - local group = a.nvim_create_augroup('treesitter/playground', {}) - - a.nvim_create_autocmd('CursorMoved', { - group = group, - buffer = b, - callback = function() - a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - local row = a.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) - a.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { - end_row = pos.end_lnum, - end_col = math.max(0, pos.end_col), - hl_group = 'Visual', - }) - - local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) - - -- Move the cursor if highlighted range is completely out of view - if pos.lnum < topline and pos.end_lnum < topline then - a.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) - elseif pos.lnum > botline and pos.end_lnum > botline then - a.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) - end - end, - }) - - a.nvim_create_autocmd('CursorMoved', { - group = group, - buffer = buf, - callback = function() - if not a.nvim_buf_is_loaded(b) then - return true - end - - a.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - - local cursor_node = M.get_node({ - bufnr = buf, - lang = opts.lang, - ignore_injections = false, - }) - if not cursor_node then - return - end - - local cursor_node_id = cursor_node:id() - for i, v in pg:iter() do - if v.id == cursor_node_id then - local start = v.depth - local end_col = start + #v.text - a.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { - end_col = end_col, - hl_group = 'Visual', - }) - a.nvim_win_set_cursor(w, { i, 0 }) - break - end - end - end, - }) - - a.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { - group = group, - buffer = buf, - callback = function() - if not a.nvim_buf_is_loaded(b) then - return true - end - - pg = assert(Playground:new(buf, opts.lang)) - pg:draw(b) - end, - }) - - a.nvim_create_autocmd('BufLeave', { - group = group, - buffer = b, - callback = function() - a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - end, - }) - - a.nvim_create_autocmd('BufLeave', { - group = group, - buffer = buf, - callback = function() - if not a.nvim_buf_is_loaded(b) then - return true - end - - a.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - end, - }) - - a.nvim_create_autocmd('BufHidden', { - group = group, - buffer = buf, - once = true, - callback = function() - if a.nvim_win_is_valid(w) then - a.nvim_win_close(w, true) - end - end, - }) + ---@cast opts InspectTreeOpts + require('vim.treesitter.playground').inspect_tree(opts) end --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index 35f06f5caf..7eead14579 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -1,5 +1,8 @@ local api = vim.api +---@class TSPlaygroundModule +local M = {} + ---@class TSPlayground ---@field ns integer API namespace ---@field opts table Options table with the following keys: @@ -212,4 +215,224 @@ function TSPlayground:iter() return ipairs(self.opts.anon and self.nodes or self.named) end -return TSPlayground +--- @class InspectTreeOpts +--- @field lang string? The language of the source buffer. If omitted, the +--- filetype of the source buffer is used. +--- @field bufnr integer? Buffer to draw the tree into. If omitted, a new +--- buffer is created. +--- @field winid integer? Window id to display the tree buffer in. If omitted, +--- a new window is created with {command}. +--- @field command string? Vimscript command to create the window. Default +--- value is "60vnew". Only used when {winid} is nil. +--- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a +--- function, it accepts the buffer number of the source +--- buffer as its only argument and should return a string. + +--- @param opts InspectTreeOpts +function M.inspect_tree(opts) + vim.validate({ + opts = { opts, 't', true }, + }) + + opts = opts or {} + + local buf = api.nvim_get_current_buf() + local win = api.nvim_get_current_win() + local pg = assert(TSPlayground:new(buf, opts.lang)) + + -- Close any existing playground window + if vim.b[buf].playground then + local w = vim.b[buf].playground + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end + + local w = opts.winid + if not w then + vim.cmd(opts.command or '60vnew') + w = api.nvim_get_current_win() + end + + local b = opts.bufnr + if b then + api.nvim_win_set_buf(w, b) + else + b = api.nvim_win_get_buf(w) + end + + vim.b[buf].playground = w + + vim.wo[w].scrolloff = 5 + vim.wo[w].wrap = false + vim.wo[w].foldmethod = 'manual' -- disable folding + vim.bo[b].buflisted = false + vim.bo[b].buftype = 'nofile' + vim.bo[b].bufhidden = 'wipe' + vim.bo[b].filetype = 'query' + + local title --- @type string? + local opts_title = opts.title + if not opts_title then + local bufname = api.nvim_buf_get_name(buf) + title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) + elseif type(opts_title) == 'function' then + title = opts_title(buf) + end + + assert(type(title) == 'string', 'Window title must be a string') + api.nvim_buf_set_name(b, title) + + pg:draw(b) + + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_set_keymap(b, 'n', '', '', { + desc = 'Jump to the node under the cursor in the source buffer', + callback = function() + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_set_current_win(win) + api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'a', '', { + desc = 'Toggle anonymous nodes', + callback = function() + local row, col = unpack(api.nvim_win_get_cursor(w)) + local curnode = pg:get(row) + while curnode and not curnode.named do + row = row - 1 + curnode = pg:get(row) + end + + pg.opts.anon = not pg.opts.anon + pg:draw(b) + + if not curnode then + return + end + + local id = curnode.id + for i, node in pg:iter() do + if node.id == id then + api.nvim_win_set_cursor(w, { i, col }) + break + end + end + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'I', '', { + desc = 'Toggle language display', + callback = function() + pg.opts.lang = not pg.opts.lang + pg:draw(b) + end, + }) + + local group = api.nvim_create_augroup('treesitter/playground', {}) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { + end_row = pos.end_lnum, + end_col = math.max(0, pos.end_col), + hl_group = 'Visual', + }) + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if pos.lnum < topline and pos.end_lnum < topline then + api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) + elseif pos.lnum > botline and pos.end_lnum > botline then + api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + end + end, + }) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + + local cursor_node = vim.treesitter.get_node({ + bufnr = buf, + lang = opts.lang, + ignore_injections = false, + }) + if not cursor_node then + return + end + + local cursor_node_id = cursor_node:id() + for i, v in pg:iter() do + if v.id == cursor_node_id then + local start = v.depth + local end_col = start + #v.text + api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { + end_col = end_col, + hl_group = 'Visual', + }) + api.nvim_win_set_cursor(w, { i, 0 }) + break + end + end + end, + }) + + api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + pg = assert(TSPlayground:new(buf, opts.lang)) + pg:draw(b) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufHidden', { + group = group, + buffer = buf, + once = true, + callback = function() + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end, + }) +end + +return M -- cgit From 743860de40502227b3f0ed64317eb937d24d4a36 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 21:59:06 +0200 Subject: test: replace lfs with luv and vim.fs test: replace lfs with luv luv already pretty much does everything lfs does, so this duplication of dependencies isn't needed. --- runtime/lua/vim/_editor.lua | 1 - runtime/lua/vim/_init_packages.lua | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 0c4b634f6f..fa0980563a 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -36,7 +36,6 @@ for k, v in pairs({ keymap = true, ui = true, health = true, - fs = true, secure = true, _watch = true, }) do diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 57c0fc9122..2cf2e91a8c 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -54,6 +54,7 @@ require('vim.shared') vim._submodules = { inspect = true, version = true, + fs = true, } -- These are for loading runtime modules in the vim namespace lazily. -- cgit From e826d09c18ab8840592b6cdbbcfe3e311a047174 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Tue, 4 Apr 2023 23:37:46 +0200 Subject: fix(windows): consistent normalization in fs.find vim.fs.find(".luacheckrc") ``` c:\\projects\\neovim/.luacheckrc # before c:/projects/neovim/.luacheckrc # after ``` Co-authored-by: kylo252 <59826753+kylo252@users.noreply.github.com> --- runtime/lua/vim/fs.lua | 2 +- runtime/lua/vim/treesitter/playground.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 407b334f20..2a51bde263 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -216,7 +216,7 @@ function M.find(names, opts) ---@private local function add(match) - matches[#matches + 1] = match + matches[#matches + 1] = M.normalize(match) if #matches == limit then return true end diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index 7eead14579..2c0a0d1aa6 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -265,7 +265,7 @@ function M.inspect_tree(opts) vim.wo[w].scrolloff = 5 vim.wo[w].wrap = false - vim.wo[w].foldmethod = 'manual' -- disable folding + vim.wo[w].foldmethod = 'manual' -- disable folding vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' -- cgit From 999cb36c2bb64d1f93bb6f8e607e0eb26eadcd63 Mon Sep 17 00:00:00 2001 From: Michal Liszcz Date: Wed, 5 Apr 2023 14:02:08 +0200 Subject: refactor(lsp): do not parse verbose output when overwriting options (#22810) --- runtime/lua/vim/lsp.lua | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 7e8c73ddb6..2d39f2d45d 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1101,21 +1101,16 @@ function lsp.start_client(config) return true end - local last_set_from = vim.fn.gettext('\n\tLast set from ') - local line = vim.fn.gettext(' line ') - local scriptname - - vim.api.nvim_buf_call(bufnr, function() - scriptname = vim.fn - .execute('verbose set ' .. option .. '?') - :match(last_set_from .. '(.*)' .. line .. '%d+') - end) + local info = vim.api.nvim_get_option_info2(option, { buf = bufnr }) + local scriptinfo = vim.tbl_filter(function(e) + return e.sid == info.last_set_sid + end, vim.fn.getscriptinfo()) - if not scriptname then + if #scriptinfo ~= 1 then return false end - return vim.startswith(vim.fn.expand(scriptname), vim.fn.expand('$VIMRUNTIME')) + return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME')) end ---@private -- cgit From 34ac75b32927328a0c691c5bda987c0fdb5ce9eb Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 5 Apr 2023 17:19:53 +0100 Subject: refactor: rename local API alias from a to api Problem: Codebase inconsistently binds vim.api onto a or api. Solution: Use api everywhere. a as an identifier is too short to have at the module level. --- runtime/lua/vim/_meta.lua | 20 +++++++++---------- runtime/lua/vim/treesitter.lua | 30 ++++++++++++++--------------- runtime/lua/vim/treesitter/highlighter.lua | 18 ++++++++--------- runtime/lua/vim/treesitter/language.lua | 4 ++-- runtime/lua/vim/treesitter/languagetree.lua | 12 ++++++------ runtime/lua/vim/treesitter/query.lua | 8 ++++---- 6 files changed, 46 insertions(+), 46 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 104f29c4c0..e3ad4d76c9 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. -- Can be done in a separate PR. @@ -30,7 +30,7 @@ end local options_info = setmetatable({}, { __index = function(t, k) - local info = a.nvim_get_option_info(k) + local info = api.nvim_get_option_info(k) info.metatype = get_option_metatype(k, info) rawset(t, k, info) return rawget(t, k) @@ -74,12 +74,12 @@ local function new_opt_accessor(handle, scope) return new_opt_accessor(k, scope) end opt_validate(k, scope) - return a.nvim_get_option_value(k, { [scope] = handle or 0 }) + return api.nvim_get_option_value(k, { [scope] = handle or 0 }) end, __newindex = function(_, k, v) opt_validate(k, scope) - return a.nvim_set_option_value(k, v, { [scope] = handle or 0 }) + return api.nvim_set_option_value(k, v, { [scope] = handle or 0 }) end, }) end @@ -91,10 +91,10 @@ vim.wo = new_opt_accessor(nil, 'win') -- this ONLY sets the global option. like `setglobal` vim.go = setmetatable({}, { __index = function(_, k) - return a.nvim_get_option_value(k, { scope = 'global' }) + return api.nvim_get_option_value(k, { scope = 'global' }) end, __newindex = function(_, k, v) - return a.nvim_set_option_value(k, v, { scope = 'global' }) + return api.nvim_set_option_value(k, v, { scope = 'global' }) end, }) @@ -102,10 +102,10 @@ vim.go = setmetatable({}, { -- it has no additional metamethod magic. vim.o = setmetatable({}, { __index = function(_, k) - return a.nvim_get_option_value(k, {}) + return api.nvim_get_option_value(k, {}) end, __newindex = function(_, k, v) - return a.nvim_set_option_value(k, v, {}) + return api.nvim_set_option_value(k, v, {}) end, }) @@ -488,7 +488,7 @@ local function create_option_accessor(scope) -- opt[my_option] = value _set = function(self) local value = convert_value_to_vim(self._name, self._info, self._value) - a.nvim_set_option_value(self._name, value, { scope = scope }) + api.nvim_set_option_value(self._name, value, { scope = scope }) end, get = function(self) @@ -526,7 +526,7 @@ local function create_option_accessor(scope) return setmetatable({}, { __index = function(_, k) - return make_option(k, a.nvim_get_option_value(k, {})) + return make_option(k, api.nvim_get_option_value(k, {})) end, __newindex = function(_, k, v) diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 092fdf0ae2..d1f5996768 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api local LanguageTree = require('vim.treesitter.languagetree') local Range = require('vim.treesitter._range') @@ -80,7 +80,7 @@ function M._create_parser(bufnr, lang, opts) local source = self:source() --[[@as integer]] - a.nvim_buf_attach( + api.nvim_buf_attach( source, false, { on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true } @@ -109,7 +109,7 @@ function M.get_parser(bufnr, lang, opts) opts = opts or {} if bufnr == nil or bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = api.nvim_get_current_buf() end if not valid_lang(lang) then @@ -141,7 +141,7 @@ end ---@return boolean function M._has_parser(bufnr) if bufnr == nil or bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = api.nvim_get_current_buf() end return parsers[bufnr] ~= nil end @@ -229,7 +229,7 @@ local function buf_range_get_text(buf, range) end_col = -1 end_row = end_row - 1 end - local lines = a.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) + local lines = api.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {}) return table.concat(lines, '\n') end @@ -294,7 +294,7 @@ end ---@return table[] List of captures `{ capture = "name", metadata = { ... } }` function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = api.nvim_get_current_buf() end local buf_highlighter = M.highlighter.active[bufnr] @@ -345,8 +345,8 @@ end ---@return string[] List of capture names function M.get_captures_at_cursor(winnr) winnr = winnr or 0 - local bufnr = a.nvim_win_get_buf(winnr) - local cursor = a.nvim_win_get_cursor(winnr) + local bufnr = api.nvim_win_get_buf(winnr) + local cursor = api.nvim_win_get_cursor(winnr) local data = M.get_captures_at_pos(bufnr, cursor[1] - 1, cursor[2]) @@ -374,7 +374,7 @@ function M.get_node(opts) local bufnr = opts.bufnr if not bufnr or bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = api.nvim_get_current_buf() end local row, col @@ -383,10 +383,10 @@ function M.get_node(opts) row, col = opts.pos[1], opts.pos[2] else assert( - bufnr == a.nvim_get_current_buf(), + bufnr == api.nvim_get_current_buf(), 'Position must be explicitly provided when not using the current buffer' ) - local pos = a.nvim_win_get_cursor(0) + local pos = api.nvim_win_get_cursor(0) -- Subtract one to account for 1-based row indexing in nvim_win_get_cursor row, col = pos[1] - 1, pos[2] end @@ -417,7 +417,7 @@ end function M.get_node_at_pos(bufnr, row, col, opts) vim.deprecate('vim.treesitter.get_node_at_pos()', 'vim.treesitter.get_node()', '0.10') if bufnr == 0 then - bufnr = a.nvim_get_current_buf() + bufnr = api.nvim_get_current_buf() end local ts_range = { row, col, row, col } @@ -440,7 +440,7 @@ end function M.get_node_at_cursor(winnr) vim.deprecate('vim.treesitter.get_node_at_cursor()', 'vim.treesitter.get_node():type()', '0.10') winnr = winnr or 0 - local bufnr = a.nvim_win_get_buf(winnr) + local bufnr = api.nvim_win_get_buf(winnr) return M.get_node({ bufnr = bufnr, ignore_injections = false }):type() end @@ -465,7 +465,7 @@ end ---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer) ---@param lang (string|nil) Language of the parser (default: buffer filetype) function M.start(bufnr, lang) - bufnr = bufnr or a.nvim_get_current_buf() + bufnr = bufnr or api.nvim_get_current_buf() local parser = M.get_parser(bufnr, lang) M.highlighter.new(parser) end @@ -474,7 +474,7 @@ end --- ---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer) function M.stop(bufnr) - bufnr = bufnr or a.nvim_get_current_buf() + bufnr = bufnr or api.nvim_get_current_buf() if M.highlighter.active[bufnr] then M.highlighter.active[bufnr]:destroy() diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d3cc1b698c..ac2a929487 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api local query = vim.treesitter.query ---@alias TSHlIter fun(): integer, TSNode, TSMetadata @@ -25,7 +25,7 @@ TSHighlighter.active = TSHighlighter.active or {} local TSHighlighterQuery = {} TSHighlighterQuery.__index = TSHighlighterQuery -local ns = a.nvim_create_namespace('treesitter/highlighter') +local ns = api.nvim_create_namespace('treesitter/highlighter') ---@private function TSHighlighterQuery.new(lang, query_string) @@ -36,7 +36,7 @@ function TSHighlighterQuery.new(lang, query_string) local name = self._query.captures[capture] local id = 0 if not vim.startswith(name, '_') then - id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) + id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) end rawset(table, capture, id) @@ -121,7 +121,7 @@ function TSHighlighter.new(tree, opts) vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) end - a.nvim_buf_call(self.bufnr, function() + api.nvim_buf_call(self.bufnr, function() vim.opt_local.spelloptions:append('noplainbuffer') end) @@ -140,7 +140,7 @@ function TSHighlighter:destroy() vim.bo[self.bufnr].spelloptions = self.orig_spelloptions vim.b[self.bufnr].ts_highlight = nil if vim.g.syntax_on == 1 then - a.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) + api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) end end end @@ -168,7 +168,7 @@ end ---@param start_row integer ---@param new_end integer function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end) - a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) + api.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1) end ---@package @@ -180,7 +180,7 @@ end ---@param changes integer[][]? function TSHighlighter:on_changedtree(changes) for _, ch in ipairs(changes or {}) do - a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) + api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) end end @@ -252,7 +252,7 @@ local function on_line_impl(self, buf, line, is_spell_nav) 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 - a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { + api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl, @@ -323,7 +323,7 @@ function TSHighlighter._on_win(_, _win, buf, _topline) return true end -a.nvim_set_decoration_provider(ns, { +api.nvim_set_decoration_provider(ns, { on_buf = TSHighlighter._on_buf, on_win = TSHighlighter._on_win, on_line = TSHighlighter._on_line, diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 5b74bb6200..b616d4d70b 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api ---@class TSLanguageModule local M = {} @@ -89,7 +89,7 @@ function M.add(lang, opts) end local fname = 'parser/' .. lang .. '.*' - local paths = a.nvim_get_runtime_file(fname, false) + local paths = api.nvim_get_runtime_file(fname, false) if #paths == 0 then error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index cf0ecbd839..703d2a1f6d 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -32,7 +32,7 @@ --- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent --- updates. -local a = vim.api +local api = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local Range = require('vim.treesitter._range') @@ -141,16 +141,16 @@ function LanguageTree:_log(...) local prefix = string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions) - a.nvim_out_write(prefix) + api.nvim_out_write(prefix) for _, x in ipairs(args) do if type(x) == 'string' then - a.nvim_out_write(x) + api.nvim_out_write(x) else - a.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) + api.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' })) end - a.nvim_out_write(' ') + api.nvim_out_write(' ') end - a.nvim_out_write('\n') + api.nvim_out_write('\n') end --- Invalidates this parser and all its children diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 25623c1498..5b87e6ac31 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -1,4 +1,4 @@ -local a = vim.api +local api = vim.api local language = require('vim.treesitter.language') ---@class Query @@ -74,7 +74,7 @@ end ---@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) - local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true)) + local lang_files = dedupe_files(api.nvim_get_runtime_file(query_path, true)) if #lang_files == 0 then return {} @@ -635,7 +635,7 @@ end ---@return (fun(): integer, TSNode, TSMetadata): capture id, capture node, metadata function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then - source = vim.api.nvim_get_current_buf() + source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) @@ -690,7 +690,7 @@ end ---@return (fun(): integer, table, table): pattern id, match, metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then - source = vim.api.nvim_get_current_buf() + source = api.nvim_get_current_buf() end start, stop = value_or_node_range(start, stop, node) -- cgit From e29bc03c046b3a137c2e36b4d34c119b277d62b2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 6 Apr 2023 15:16:44 +0100 Subject: fix(treesitter): do not track ranges of the root tree (#22912) Fixes #22911 --- runtime/lua/vim/treesitter/languagetree.lua | 10 +++++++++- 1 file changed, 9 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 703d2a1f6d..4aa07d1b96 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -57,6 +57,7 @@ local Range = require('vim.treesitter._range') ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options ---@field private _parser TSParser Parser for language +---@field private _has_regions boolean ---@field private _regions Range6[][]? ---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() @@ -440,6 +441,8 @@ end ---@private ---@param new_regions Range6[][] List of regions this tree should manage and parse. function LanguageTree:set_included_regions(new_regions) + self._has_regions = true + -- Transform the tables from 4 element long to 6 element long (with byte offset) for _, region in ipairs(new_regions) do for i, range in ipairs(region) do @@ -468,7 +471,8 @@ function LanguageTree:included_regions() return self._regions end - if #self._trees == 0 then + if not self._has_regions or #self._trees == 0 then + -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} return { {} } end @@ -744,6 +748,10 @@ function LanguageTree:_edit( -- Validate regions after editing the tree self:_iter_regions(function(_, region) + if #region == 0 then + -- empty region, use the full source + return false + end for _, r in ipairs(region) do if Range.intercepts(r, changed_range) then return false -- cgit From d675bd01b1e78b93e559320b262bdae40b3b54b2 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 7 Apr 2023 08:22:47 -0600 Subject: feat(lua): allow vim.F.if_nil to take multiple arguments (#22903) The first argument which is non-nil is returned. This is useful when using nested default values (e.g. in the EditorConfig plugin). Before: local enable = vim.F.if_nil(vim.b.editorconfig, vim.F.if_nil(vim.g.editorconfig, true)) After: local enable = vim.F.if_nil(vim.b.editorconfig, vim.g.editorconfig, true) --- runtime/lua/vim/F.lua | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua index 3e370c0a84..16c834b371 100644 --- a/runtime/lua/vim/F.lua +++ b/runtime/lua/vim/F.lua @@ -1,18 +1,29 @@ local F = {} ---- Returns {a} if it is not nil, otherwise returns {b}. +--- Returns the first argument which is not nil. --- ----@generic A ----@generic B +--- If all arguments are nil, returns nil. --- ----@param a A ----@param b B ----@return A | B -function F.if_nil(a, b) - if a == nil then - return b +--- Examples: +---
+--- local a = nil
+--- local b = nil
+--- local c = 42
+--- local d = true
+--- assert(vim.F.if_nil(a, b, c, d) == 42)
+--- 
+--- +---@param ... any +---@return any +function F.if_nil(...) + local nargs = select('#', ...) + for i = 1, nargs do + local v = select(i, ...) + if v ~= nil then + return v + end end - return a + return nil end -- Use in combination with pcall -- cgit From ccc0980f86c6ef9a86b0e5a3a691f37cea8eb776 Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Tue, 11 Apr 2023 16:26:03 +0800 Subject: fix(treesitter): Use the correct replacement args for #gsub! directive (#23015) fix(treesitter): use the correct replacement args for #gsub! directive --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 5b87e6ac31..8a747ba14c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -458,7 +458,7 @@ local directive_handlers = { metadata[id] = {} end - local pattern, replacement = pred[3], pred[3] + local pattern, replacement = pred[3], pred[4] assert(type(pattern) == 'string') assert(type(replacement) == 'string') -- cgit From 9e86f473e0f4e21c5f40bf990c53194d593a0f9f Mon Sep 17 00:00:00 2001 From: NAKAI Tsuyoshi <82267684+uga-rosa@users.noreply.github.com> Date: Tue, 11 Apr 2023 23:28:46 +0900 Subject: feat(lua): vim.region accepts getpos() arg (#22635) --- runtime/lua/vim/_editor.lua | 22 ++++++++++++++++++++-- runtime/lua/vim/highlight.lua | 19 ++++--------------- 2 files changed, 24 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index fa0980563a..c922ec93db 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -402,8 +402,8 @@ end --- Input and output positions are (0,0)-indexed and indicate byte positions. --- ---@param bufnr integer number of buffer ----@param pos1 integer[] (line, column) tuple marking beginning of region ----@param pos2 integer[] (line, column) tuple marking end of region +---@param pos1 integer[]|string start of region as a (line, column) tuple or string accepted by |getpos()| +---@param pos2 integer[]|string end of region as a (line, column) tuple or string accepted by |getpos()| ---@param regtype string type of selection, see |setreg()| ---@param inclusive boolean indicating whether column of pos2 is inclusive ---@return table region Table of the form `{linenr = {startcol,endcol}}`. @@ -414,6 +414,24 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) vim.fn.bufload(bufnr) end + if type(pos1) == 'string' then + local pos = vim.fn.getpos(pos1) + pos1 = { pos[2] - 1, pos[3] - 1 + pos[4] } + end + if type(pos2) == 'string' then + local pos = vim.fn.getpos(pos2) + pos2 = { pos[2] - 1, pos[3] - 1 + pos[4] } + end + + if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then + pos1, pos2 = pos2, pos1 + end + + -- getpos() may return {0,0,0,0} + if pos1[1] < 0 or pos1[2] < 0 then + return {} + end + -- check that region falls within current buffer local buf_line_count = vim.api.nvim_buf_line_count(bufnr) pos1[1] = math.min(pos1[1], buf_line_count - 1) diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index d71a806ea8..a6cfcb730f 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -15,8 +15,8 @@ M.priorities = { ---@param bufnr integer Buffer number to apply highlighting to ---@param ns integer Namespace to add highlight to ---@param higroup string Highlight group to use for highlighting ----@param start { [1]: integer, [2]: integer } Start position {line, col} ----@param finish { [1]: integer, [2]: integer } Finish position {line, col} +---@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) @@ -27,11 +27,6 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local inclusive = opts.inclusive or false local priority = opts.priority or M.priorities.user - -- sanity check - if start[2] < 0 or finish[1] < start[1] then - return - end - local region = vim.region(bufnr, start, finish, regtype, inclusive) for linenr, cols in pairs(region) do local end_row @@ -104,18 +99,12 @@ function M.on_yank(opts) yank_timer:close() end - local pos1 = vim.fn.getpos("'[") - local pos2 = vim.fn.getpos("']") - - pos1 = { pos1[2] - 1, pos1[3] - 1 + pos1[4] } - pos2 = { pos2[2] - 1, pos2[3] - 1 + pos2[4] } - M.range( bufnr, yank_ns, higroup, - pos1, - pos2, + "'[", + "']", { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user } ) -- cgit From 37011bc45ef333776bccea84a1a7ced6c68e3b51 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 12 Apr 2023 15:16:15 +0200 Subject: fix(diagnostic): rename buffer → bufnr in type annotation (#23042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See `:h diagnostic-structure`, the property name is `bufnr`, not `buffer`. --- 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 714038f8e4..d1b50304c7 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -744,7 +744,7 @@ function M.get_namespaces() end ---@class Diagnostic ----@field buffer integer +---@field bufnr integer ---@field lnum integer 0-indexed ---@field end_lnum nil|integer 0-indexed ---@field col integer 0-indexed -- cgit From 66c66d8db8ab5cb6d0c6d85d64556d7cf20b04fa Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 13 Apr 2023 17:34:47 +0100 Subject: fix(loader): reset hashes when running the loader --- runtime/lua/vim/loader.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 38b1e9fc0f..7f953adc21 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -5,7 +5,7 @@ local loaders = package.loaders local M = {} ----@alias CacheHash {mtime: {sec:number, nsec:number}, size:number, type: string} +---@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: uv.aliases.fs_stat_types} ---@alias CacheEntry {hash:CacheHash, chunk:string} ---@class ModuleFindOpts @@ -28,12 +28,11 @@ M.enabled = false ---@field _rtp string[] ---@field _rtp_pure string[] ---@field _rtp_key string +---@field _hashes? table local Loader = { VERSION = 3, ---@type table> _indexed = {}, - ---@type table - _hashes = {}, ---@type table _topmods = {}, _loadfile = loadfile, @@ -44,9 +43,13 @@ local Loader = { } --- @param path string ---- @return uv.fs_stat.result +--- @return CacheHash --- @private function Loader.get_hash(path) + if not Loader._hashes then + return uv.fs_stat(path) --[[@as CacheHash]] + end + if not Loader._hashes[path] then -- Note we must never save a stat for a non-existent path. -- For non-existent paths fs_stat() will return nil. @@ -163,13 +166,16 @@ end ---@return string|function ---@private function Loader.loader(modname) + Loader._hashes = {} local ret = M.find(modname)[1] if ret then -- Make sure to call the global loadfile so we respect any augmentations done elsewhere. -- E.g. profiling local chunk, err = loadfile(ret.modpath) + Loader._hashes = nil return chunk or error(err) end + Loader._hashes = nil return '\ncache_loader: module ' .. modname .. ' not found' end @@ -373,7 +379,9 @@ function M.reset(path) end -- Path could be a directory so just clear all the hashes. - Loader._hashes = {} + if Loader._hashes then + Loader._hashes = {} + end end --- Enables the experimental Lua module loader: -- cgit From bfb28b62dab756ec76a73506c2070ddf491a0cdd Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:29:13 -0600 Subject: refactor: remove modelines from Lua files Now that we have builtin EditorConfig support and a formatting check in CI, these are not necessary. --- runtime/lua/vim/lsp.lua | 1 - runtime/lua/vim/lsp/buf.lua | 1 - runtime/lua/vim/lsp/handlers.lua | 1 - runtime/lua/vim/lsp/log.lua | 1 - runtime/lua/vim/lsp/protocol.lua | 1 - runtime/lua/vim/lsp/rpc.lua | 1 - runtime/lua/vim/lsp/util.lua | 1 - runtime/lua/vim/shared.lua | 1 - runtime/lua/vim/uri.lua | 1 - 9 files changed, 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 2d39f2d45d..3db3545786 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -2384,4 +2384,3 @@ lsp.commands = setmetatable({}, { }) return lsp --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8bf3764f5e..3d9011656f 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -806,4 +806,3 @@ function M.execute_command(command_params) end return M --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index d01f8e6159..71bef43bc1 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -644,4 +644,3 @@ for k, fn in pairs(M) do end return M --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 51dcb7d21d..3d5bc06c3f 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -174,4 +174,3 @@ function log.should_log(level) end return log --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index f4489ad17d..2cb8fc7955 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -894,4 +894,3 @@ function protocol.resolve_capabilities(server_capabilities) end return protocol --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 30b61d01d6..af3190c9bd 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -753,4 +753,3 @@ return { client_errors = client_errors, create_read_loop = create_read_loop, } --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ebde7af16c..dca258e4b9 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2154,4 +2154,3 @@ M._get_line_byte_from_position = get_line_byte_from_position M.buf_versions = {} return M --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index eb734fb512..9245adae3a 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -811,4 +811,3 @@ function vim.defaulttable(create) end return vim --- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 38759fcdc0..08ed829114 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -134,4 +134,3 @@ return { uri_to_fname = uri_to_fname, uri_to_bufnr = uri_to_bufnr, } --- vim:sw=2 ts=2 et -- cgit From 4d04feb6629cb049cb2a13ba35f0c8d3c6b67ff4 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 14 Apr 2023 10:39:57 +0200 Subject: feat(lua): vim.tbl_contains supports general tables and predicates (#23040) * feat(lua): vim.tbl_contains supports general tables and predicates Problem: `vim.tbl_contains` only works for list-like tables (integer keys without gaps) and primitive values (in particular, not for nested tables). Solution: Rename `vim.tbl_contains` to `vim.list_contains` and add new `vim.tbl_contains` that works for general tables and optionally allows `value` to be a predicate function that is checked for every key. --- runtime/lua/vim/loader.lua | 4 ++-- runtime/lua/vim/lsp.lua | 4 ++-- runtime/lua/vim/lsp/_snippet.lua | 4 ++-- runtime/lua/vim/lsp/codelens.lua | 2 +- runtime/lua/vim/lsp/util.lua | 2 +- runtime/lua/vim/shared.lua | 45 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 51 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 7f953adc21..66627fe4e7 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -449,7 +449,7 @@ function Loader.lsmod(path) if topname then Loader._indexed[path][topname] = { modpath = modpath, modname = topname } Loader._topmods[topname] = Loader._topmods[topname] or {} - if not vim.tbl_contains(Loader._topmods[topname], path) then + if not vim.list_contains(Loader._topmods[topname], path) then table.insert(Loader._topmods[topname], path) end end @@ -523,7 +523,7 @@ function M._inspect(opts) { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' }, }) for k, v in pairs(Loader._stats[stat]) do - if not vim.tbl_contains({ 'time', 'total' }, k) then + if not vim.list_contains({ 'time', 'total' }, k) then chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) } chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' } end diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 3db3545786..5c78bd7580 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -171,7 +171,7 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids) if restrict_client_ids and #restrict_client_ids > 0 then local filtered_client_ids = {} for client_id in pairs(client_ids) do - if vim.tbl_contains(restrict_client_ids, client_id) then + if vim.list_contains(restrict_client_ids, client_id) then filtered_client_ids[client_id] = true end end @@ -2186,7 +2186,7 @@ function lsp.formatexpr(opts) opts = opts or {} local timeout_ms = opts.timeout_ms or 500 - if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then + if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then -- `formatexpr` is also called when exceeding `textwidth` in insert mode -- fall back to internal formatting return 1 diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua index 797d8960d5..e7ada5415f 100644 --- a/runtime/lua/vim/lsp/_snippet.lua +++ b/runtime/lua/vim/lsp/_snippet.lua @@ -17,14 +17,14 @@ P.take_until = function(targets, specials) table.insert(raw, '\\') new_pos = new_pos + 1 c = string.sub(input, new_pos, new_pos) - if not vim.tbl_contains(targets, c) and not vim.tbl_contains(specials, c) then + if not vim.list_contains(targets, c) and not vim.list_contains(specials, c) then table.insert(esc, '\\') end table.insert(raw, c) table.insert(esc, c) new_pos = new_pos + 1 else - if vim.tbl_contains(targets, c) then + if vim.list_contains(targets, c) then break end table.insert(raw, c) diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 81cac6a511..005a0047fa 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -42,7 +42,7 @@ local function execute_lens(lens, bufnr, client_id) -- Need to use the client that returned the lens → must not use buf_request local command_provider = client.server_capabilities.executeCommandProvider local commands = type(command_provider) == 'table' and command_provider.commands or {} - if not vim.tbl_contains(commands, command.command) then + if not vim.list_contains(commands, command.command) then vim.notify( string.format( 'Language server does not support command `%s`. This command may require a client extension.', diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index dca258e4b9..31af2afb0b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1476,7 +1476,7 @@ end local function close_preview_window(winnr, bufnrs) vim.schedule(function() -- exit if we are in one of ignored buffers - if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then + if bufnrs and vim.list_contains(bufnrs, api.nvim_get_current_buf()) then return end diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 9245adae3a..7ecb56eb92 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -252,12 +252,53 @@ function vim.tbl_filter(func, t) return rettab end ---- Checks if a list-like (vector) table contains `value`. +--- Checks if a table contains a given value, specified either directly or via +--- a predicate that is checked for each value. +--- +--- Example: +---
lua
+---  vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
+---    return vim.deep_equal(v, { 'b', 'c' })
+---  end, { predicate = true })
+---  -- true
+--- 
+--- +---@see |vim.list_contains()| for checking values in list-like tables --- ---@param t table Table to check +---@param value any Value to compare or predicate function reference +---@param opts (table|nil) Keyword arguments |kwargs|: +--- - predicate: (boolean) `value` is a function reference to be checked (default false) +---@return boolean `true` if `t` contains `value` +function vim.tbl_contains(t, value, opts) + vim.validate({ t = { t, 't' }, opts = { opts, 't', true } }) + + local pred + if opts and opts.predicate then + vim.validate({ value = { value, 'c' } }) + pred = value + else + pred = function(v) + return v == value + end + end + + for _, v in pairs(t) do + if pred(v) then + return true + end + end + return false +end + +--- Checks if a list-like table (integer keys without gaps) contains `value`. +--- +---@see |vim.tbl_contains()| for checking values in general tables +--- +---@param t table Table to check (must be list-like, not validated) ---@param value any Value to compare ---@return boolean `true` if `t` contains `value` -function vim.tbl_contains(t, value) +function vim.list_contains(t, value) vim.validate({ t = { t, 't' } }) for _, v in ipairs(t) do -- cgit From 7caf0eafd83b5a92f2ff219b3a64ffae4174b9af Mon Sep 17 00:00:00 2001 From: NAKAI Tsuyoshi <82267684+uga-rosa@users.noreply.github.com> Date: Fri, 14 Apr 2023 19:01:08 +0900 Subject: feat(lua)!: add stricter vim.tbl_islist() and rename old one to vim.tbl_isarray() (#16440) feat(lua)!: add stricter vim.tbl_islist(), rename vim.tbl_isarray() Problem: `vim.tbl_islist` allows gaps in tables with integer keys ("arrays"). Solution: Rename `vim.tbl_islist` to `vim.tbl_isarray`, add new `vim.tbl.islist` that checks for consecutive integer keys that start from 1. --- runtime/lua/vim/shared.lua | 47 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 7ecb56eb92..f700f4a6b3 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -320,10 +320,10 @@ function vim.tbl_isempty(t) return next(t) == nil end ---- We only merge empty tables or tables that are not a list +--- We only merge empty tables or tables that are not an array (indexed by integers) ---@private local function can_merge(v) - return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_islist(v)) + return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_isarray(v)) end local function tbl_extend(behavior, deep_extend, ...) @@ -554,15 +554,15 @@ function vim.spairs(t) end end ---- Tests if a Lua table can be treated as an array. +--- Tests if a Lua table can be treated as an array (a table indexed by integers). --- --- Empty table `{}` is assumed to be 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|. --- ----@param t table Table ----@return boolean `true` if array-like table, else `false` -function vim.tbl_islist(t) +---@param t table +---@return boolean `true` if array-like table, else `false`. +function vim.tbl_isarray(t) if type(t) ~= 'table' then return false end @@ -570,7 +570,8 @@ function vim.tbl_islist(t) local count = 0 for k, _ in pairs(t) do - if type(k) == 'number' then + --- Check if the number k is an integer + if type(k) == 'number' and k == math.floor(k) then count = count + 1 else return false @@ -589,6 +590,38 @@ function vim.tbl_islist(t) end end +--- Tests if a Lua table can be treated as a list (a table indexed by consecutive integers starting from 1). +--- +--- Empty table `{}` is assumed to be an 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|. +--- +---@param t table +---@return boolean `true` if list-like table, else `false`. +function vim.tbl_islist(t) + if type(t) ~= 'table' then + return false + end + + local num_elem = vim.tbl_count(t) + + if num_elem == 0 then + -- TODO(bfredl): in the future, we will always be inside nvim + -- then this check can be deleted. + if vim._empty_dict_mt == nil then + return nil + end + return getmetatable(t) ~= vim._empty_dict_mt + else + for i = 1, num_elem do + if t[i] == nil then + return false + end + end + return true + end +end + --- Counts the number of non-nil values in table `t`. --- ---
lua
-- 
cgit 


From c08b03076167837cff9eb66c19440d727e6dad31 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sat, 15 Apr 2023 23:40:48 +0200
Subject: refactor: deprecate checkhealth functions

The following functions are deprecated and will be removed in
Nvim v0.11:

- health#report_start()
- health#report_info()
- health#report_ok()
- health#report_warn()
- health#report_error()
- vim.health.report_start()
- vim.health.report_info()
- vim.health.report_ok()
- vim.health.report_warn()
- vim.health.report_error()

Users should instead use these:

- vim.health.start()
- vim.health.info()
- vim.health.ok()
- vim.health.warn()
- vim.health.error()
---
 runtime/lua/vim/health.lua            | 264 ++++++++++++++++++++++++++++++++--
 runtime/lua/vim/lsp/health.lua        |   6 +-
 runtime/lua/vim/treesitter/health.lua |   6 +-
 3 files changed, 261 insertions(+), 15 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index 044880e076..04f8f9695b 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -1,23 +1,196 @@
 local M = {}
 
-function M.report_start(msg)
-  vim.fn['health#report_start'](msg)
+local s_output = {}
+
+-- 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
+  local func
+  local filetype
+  if path:find('vim$') then
+    name = vim.fs.basename(path):gsub('%.vim$', '')
+    func = 'health#' .. name .. '#check'
+    filetype = 'v'
+  else
+    local subpath = path:gsub('.*lua/', '')
+    if vim.fs.basename(subpath) == 'health.lua' then
+      -- */health.lua
+      name = vim.fs.dirname(subpath)
+    else
+      -- */health/init.lua
+      name = vim.fs.dirname(vim.fs.dirname(subpath))
+    end
+    name = name:gsub('/', '.')
+
+    func = 'require("' .. name .. '.health").check()'
+    filetype = 'l'
+  end
+  return { name, func, filetype }
 end
 
-function M.report_info(msg)
-  vim.fn['health#report_info'](msg)
+-- Returns { {name, func, type}, ... } representing healthchecks
+local function get_healthcheck_list(plugin_names)
+  local healthchecks = {}
+  plugin_names = vim.split(plugin_names, ' ')
+  for _, p in pairs(plugin_names) do
+    -- support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
+
+    p = p:gsub('%.', '/')
+    p = p:gsub('*', '**')
+
+    local paths = vim.api.nvim_get_runtime_file('autoload/health/' .. p .. '.vim', true)
+    vim.list_extend(
+      paths,
+      vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health/init.lua', true)
+    )
+    vim.list_extend(paths, vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health.lua', true))
+
+    if vim.tbl_count(paths) == 0 then
+      healthchecks[#healthchecks + 1] = { p, '', '' } -- healthcheck not found
+    else
+      local unique_paths = {}
+      for _, v in pairs(paths) do
+        unique_paths[v] = true
+      end
+      paths = {}
+      for k, _ in pairs(unique_paths) do
+        paths[#paths + 1] = k
+      end
+
+      for _, v in ipairs(paths) do
+        healthchecks[#healthchecks + 1] = filepath_to_healthcheck(v)
+      end
+    end
+  end
+  return healthchecks
 end
 
-function M.report_ok(msg)
-  vim.fn['health#report_ok'](msg)
+-- Returns {name: [func, type], ..} representing healthchecks
+local function get_healthcheck(plugin_names)
+  local health_list = get_healthcheck_list(plugin_names)
+  local healthchecks = {}
+  for _, c in pairs(health_list) do
+    if c[1] ~= 'vim' then
+      healthchecks[c[1]] = { c[2], c[3] }
+    end
+  end
+
+  return healthchecks
 end
 
-function M.report_warn(msg, ...)
-  vim.fn['health#report_warn'](msg, ...)
+-- Indents lines *except* line 1 of a string if it contains newlines.
+local function indent_after_line1(s, columns)
+  local lines = vim.split(s, '\n')
+  local indent = string.rep(' ', columns)
+  for i = 2, #lines do
+    lines[i] = indent .. lines[i]
+  end
+  return table.concat(lines, '\n')
+end
+
+-- Changes ':h clipboard' to ':help |clipboard|'.
+local function help_to_link(s)
+  return vim.fn.substitute(s, [[\v:h%[elp] ([^|][^"\r\n ]+)]], [[:help |\1|]], [[g]])
+end
+
+-- Format a message for a specific report item.
+-- Variable args: Optional advice (string or list)
+local function format_report_message(status, msg, ...)
+  local output = '- ' .. status
+  if status ~= '' then
+    output = output .. ' '
+  end
+
+  output = output .. indent_after_line1(msg, 2)
+
+  local varargs = ...
+
+  -- Optional parameters
+  if varargs then
+    if type(varargs) == 'string' then
+      varargs = { varargs }
+    end
+
+    output = output .. '\n  - ADVICE:'
+
+    -- Report each suggestion
+    for _, v in ipairs(varargs) do
+      if v then
+        output = output .. '\n    - ' .. indent_after_line1(v, 6)
+      end
+    end
+  end
+
+  return help_to_link(output)
+end
+
+local function collect_output(output)
+  vim.list_extend(s_output, vim.split(output, '\n'))
+end
+
+-- Starts a new report.
+function M.start(name)
+  local input = string.format('\n%s ~', name)
+  collect_output(input)
+end
+
+-- Reports a message in the current section.
+function M.info(msg)
+  local input = format_report_message('', msg)
+  collect_output(input)
+end
+
+-- Reports a successful healthcheck.
+function M.ok(msg)
+  local input = format_report_message('OK', msg)
+  collect_output(input)
+end
+
+-- Reports a health warning.
+-- ...: Optional advice (string or table)
+function M.warn(msg, ...)
+  local input = format_report_message('WARNING', msg, ...)
+  collect_output(input)
 end
 
+-- Reports a failed healthcheck.
+-- ...: Optional advice (string or table)
+function M.error(msg, ...)
+  local input = format_report_message('ERROR', msg, ...)
+  collect_output(input)
+end
+
+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
+
+function M.report_start(name)
+  deprecate('start')
+  M.start(name)
+end
+function M.report_info(msg)
+  deprecate('info')
+  M.info(msg)
+end
+function M.report_ok(msg)
+  deprecate('ok')
+  M.ok(msg)
+end
+function M.report_warn(msg, ...)
+  deprecate('warn')
+  M.warn(msg, ...)
+end
 function M.report_error(msg, ...)
-  vim.fn['health#report_error'](msg, ...)
+  deprecate('error')
+  M.error(msg, ...)
 end
 
 local path2name = function(path)
@@ -59,4 +232,77 @@ M._complete = function()
   return vim.tbl_keys(unique)
 end
 
+-- Runs the specified healthchecks.
+-- Runs all discovered healthchecks if plugin_names is empty.
+function M._check(plugin_names)
+  local healthchecks = plugin_names == '' and get_healthcheck('*') or get_healthcheck(plugin_names)
+
+  -- Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
+  local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
+  local mod = emptybuf and 'buffer' or 'tab sbuffer'
+  local bufnr = vim.api.nvim_create_buf(true, true)
+  vim.cmd(mod .. ' ' .. bufnr)
+
+  if vim.fn.bufexists('health://') == 1 then
+    vim.cmd.bwipe('health://')
+  end
+  vim.cmd.file('health://')
+  vim.cmd.setfiletype('checkhealth')
+
+  if healthchecks == nil or next(healthchecks) == nil then
+    vim.fn.setline(1, 'ERROR: No healthchecks found.')
+    return
+  end
+  vim.cmd.redraw()
+  vim.print('Running healthchecks...')
+
+  for name, value in vim.spairs(healthchecks) do
+    local func = value[1]
+    local type = value[2]
+    s_output = {}
+
+    if func == '' then
+      s_output = {}
+      M.error('No healthcheck found for "' .. name .. '" plugin.')
+    end
+    if type == 'v' then
+      vim.cmd.call(func, {})
+    else
+      local f = assert(loadstring(func))
+      local ok, output = pcall(f)
+      if not ok then
+        M.error(
+          string.format('Failed to run healthcheck for "%s" plugin. Exception:\n%s\n', name, output)
+        )
+      end
+    end
+    -- in the event the healthcheck doesn't return anything
+    -- (the plugin author should avoid this possibility)
+    if next(s_output) == nil then
+      s_output = {}
+      M.error('The healthcheck report for "' .. name .. '" plugin is empty.')
+    end
+    local header = { string.rep('=', 78), name .. ': ' .. func, '' }
+    -- remove empty line after header from report_start
+    if s_output[1] == '' then
+      local tmp = {}
+      for i = 2, #s_output do
+        tmp[#tmp + 1] = s_output[i]
+      end
+      s_output = {}
+      for _, v in ipairs(tmp) do
+        s_output[#s_output + 1] = v
+      end
+    end
+    s_output[#s_output + 1] = ''
+    s_output = vim.list_extend(header, s_output)
+    vim.fn.append('$', s_output)
+    vim.cmd.redraw()
+  end
+
+  -- Clear the 'Running healthchecks...' message.
+  vim.cmd.redraw()
+  vim.print('')
+end
+
 return M
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 987707e661..6ca468393e 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -2,8 +2,8 @@ local M = {}
 
 --- Performs a healthcheck for LSP
 function M.check()
-  local report_info = vim.health.report_info
-  local report_warn = vim.health.report_warn
+  local report_info = vim.health.info
+  local report_warn = vim.health.warn
 
   local log = require('vim.lsp.log')
   local current_log_level = log.get_level()
@@ -29,7 +29,7 @@ function M.check()
   report_fn(string.format('Log size: %d KB', log_size / 1000))
 
   local clients = vim.lsp.get_active_clients()
-  vim.health.report_start('vim.lsp: Active Clients')
+  vim.health.start('vim.lsp: Active Clients')
   if next(clients) then
     for _, client in pairs(clients) do
       report_info(
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index dabf2cdf6c..ed1161e97f 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -6,14 +6,14 @@ local health = require('vim.health')
 function M.check()
   local parsers = vim.api.nvim_get_runtime_file('parser/*', true)
 
-  health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version))
+  health.info(string.format('Nvim runtime ABI version: %d', ts.language_version))
 
   for _, parser in pairs(parsers) do
     local parsername = vim.fn.fnamemodify(parser, ':t:r')
     local is_loadable, err_or_nil = pcall(ts.language.add, parsername)
 
     if not is_loadable then
-      health.report_error(
+      health.error(
         string.format(
           'Parser "%s" failed to load (path: %s): %s',
           parsername,
@@ -23,7 +23,7 @@ function M.check()
       )
     else
       local lang = ts.language.inspect(parsername)
-      health.report_ok(
+      health.ok(
         string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser)
       )
     end
-- 
cgit 


From d9e9dc3a06b78d3d1548757348788454f62ea3dc Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 16 Apr 2023 10:59:04 +0200
Subject: vim-patch:9.0.1455: C++ 20 modules are not recognized (#23124)

Problem:    C++ 20 modules are not recognized.
Solution:   Add patterns to recognize C++ 20 modules as "cpp". (Ben Jackson,
            closes vim/vim#12261)

https://github.com/vim/vim/commit/732d69e1918b28ad0fe16eb9bc5a776c7958122b

Co-authored-by: Ben Jackson 
---
 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 87439f9f0c..74ab7d8260 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -257,6 +257,10 @@ local extension = {
   tcc = 'cpp',
   hxx = 'cpp',
   hpp = 'cpp',
+  ccm = 'cpp',
+  cppm = 'cpp',
+  cxxm = 'cpp',
+  ['c++m'] = 'cpp',
   cpp = function(path, bufnr)
     return vim.g.cynlib_syntax_for_cpp and 'cynlib' or 'cpp'
   end,
-- 
cgit 


From 2f779b94e7fe0fb2fba00dd8e644c60605e83179 Mon Sep 17 00:00:00 2001
From: Raphael 
Date: Sun, 16 Apr 2023 17:50:32 +0800
Subject: fix(lua): inspect_pos respect bufnr when get syntax info (#23098)

---
 runtime/lua/vim/_inspector.lua | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index 05983d3f0d..2ebb7a7efd 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -70,15 +70,18 @@ function vim.inspect_pos(bufnr, row, col, filter)
   if filter.treesitter then
     for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do
       capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang
-      table.insert(results.treesitter, resolve_hl(capture))
+      results.treesitter[#results.treesitter + 1] = resolve_hl(capture)
     end
   end
 
   -- syntax
-  if filter.syntax then
-    for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
-      table.insert(results.syntax, resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') }))
-    end
+  if filter.syntax and vim.api.nvim_buf_is_valid(bufnr) then
+    vim.api.nvim_buf_call(bufnr, function()
+      for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
+        results.syntax[#results.syntax + 1] =
+          resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') })
+      end
+    end)
   end
 
   -- namespace id -> name map
-- 
cgit 


From b0978fca6b82a061b345df43745c3ab860e02b58 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Sun, 16 Apr 2023 12:26:13 +0200
Subject: fix(checkhealth): fix crash due to incorrect argument type

---
 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 04f8f9695b..ff338b95ea 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -266,7 +266,7 @@ function M._check(plugin_names)
       M.error('No healthcheck found for "' .. name .. '" plugin.')
     end
     if type == 'v' then
-      vim.cmd.call(func, {})
+      vim.fn.call(func, {})
     else
       local f = assert(loadstring(func))
       local ok, output = pcall(f)
-- 
cgit 


From 07b60efd8058bb515998f50048b511d50f9671f8 Mon Sep 17 00:00:00 2001
From: Isak Samsten 
Date: Mon, 17 Apr 2023 13:53:34 +0200
Subject: feat(diagnostic): specify diagnostic virtual text prefix as a
 function

- vim.diagnostic.config() now accepts a function for the virtual_text.prefix
  option, which allows for rendering e.g., diagnostic severities differently.
---
 runtime/lua/vim/diagnostic.lua | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index d1b50304c7..0d1d01b391 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -600,7 +600,10 @@ end
 ---                                 means to always show the diagnostic source.
 ---                       * spacing: (number) Amount of empty spaces inserted at the beginning
 ---                                  of the virtual text.
----                       * prefix: (string) Prepend diagnostic message with prefix.
+---                       * prefix: (string or function) prepend diagnostic message with prefix.
+---                                 If a function, it must have the signature (diagnostic) -> string,
+---                                 where {diagnostic} is of type |diagnostic-structure|. This can be
+---                                 used to render diagnostic symbols or error codes.
 ---                       * suffix: (string or function) Append diagnostic message with suffix.
 ---                                 If a function, it must have the signature (diagnostic) ->
 ---                                 string, where {diagnostic} is of type |diagnostic-structure|.
@@ -1066,8 +1069,15 @@ function M._get_virt_text_chunks(line_diags, opts)
   -- Create a little more space between virtual text and contents
   local virt_texts = { { string.rep(' ', spacing) } }
 
-  for i = 1, #line_diags - 1 do
-    table.insert(virt_texts, { prefix, virtual_text_highlight_map[line_diags[i].severity] })
+  for i = 1, #line_diags do
+    local resolved_prefix = prefix
+    if type(prefix) == 'function' then
+      resolved_prefix = prefix(line_diags[i]) or ''
+    end
+    table.insert(
+      virt_texts,
+      { resolved_prefix, virtual_text_highlight_map[line_diags[i].severity] }
+    )
   end
   local last = line_diags[#line_diags]
 
@@ -1078,7 +1088,7 @@ function M._get_virt_text_chunks(line_diags, opts)
       suffix = suffix(last) or ''
     end
     table.insert(virt_texts, {
-      string.format('%s %s%s', prefix, last.message:gsub('\r', ''):gsub('\n', '  '), suffix),
+      string.format(' %s%s', last.message:gsub('\r', ''):gsub('\n', '  '), suffix),
       virtual_text_highlight_map[last.severity],
     })
 
-- 
cgit 


From 6cc76011ca28ff61f1c2f8de6d895d4c6d0a1ad8 Mon Sep 17 00:00:00 2001
From: Jon Huhn 
Date: Mon, 17 Apr 2023 11:50:05 -0500
Subject: fix(watchfiles): skip Created events when poll starts (#23139)

---
 runtime/lua/vim/_watch.lua | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index dba1522ec8..dbffd726a2 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -88,6 +88,9 @@ local default_poll_interval_ms = 2000
 ---               be invoked recursively)
 ---     - children (table|nil)
 ---               A mapping of directory entry name to its recursive watches
+--      - started (boolean|nil)
+--                Whether or not the watcher has first been initialized. Used
+--                to prevent a flood of Created events on startup.
 local function poll_internal(path, opts, callback, watches)
   path = vim.fs.normalize(path)
   local interval = opts and opts.interval or default_poll_interval_ms
@@ -112,7 +115,9 @@ local function poll_internal(path, opts, callback, watches)
       end)
     )
     assert(not start_err, start_err)
-    callback(path, M.FileChangeType.Created)
+    if watches.started then
+      callback(path, M.FileChangeType.Created)
+    end
   end
 
   watches.cancel = function()
@@ -132,6 +137,7 @@ local function poll_internal(path, opts, callback, watches)
       if not watches.children[name] then
         watches.children[name] = {
           is_dir = ftype == 'directory',
+          started = watches.started,
         }
         poll_internal(filepath_join(path, name), opts, callback, watches.children[name])
       end
@@ -150,6 +156,8 @@ local function poll_internal(path, opts, callback, watches)
     watches.children = newchildren
   end
 
+  watches.started = true
+
   return watches.cancel
 end
 
-- 
cgit 


From ab1edecfb7c73c82c2d5886cb8e270b44aca7d01 Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Mon, 17 Apr 2023 12:54:19 -0600
Subject: feat(lua): add vim.iter (#23029)

vim.iter wraps a table or iterator function into an `Iter` object with
methods such as `filter`, `map`, and `fold` which can be chained to
produce iterator pipelines that do not create new tables at each step.
---
 runtime/lua/vim/iter.lua   | 836 +++++++++++++++++++++++++++++++++++++++++++++
 runtime/lua/vim/shared.lua | 105 ++++++
 2 files changed, 941 insertions(+)
 create mode 100644 runtime/lua/vim/iter.lua

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
new file mode 100644
index 0000000000..b73c03ba9a
--- /dev/null
+++ b/runtime/lua/vim/iter.lua
@@ -0,0 +1,836 @@
+--- Iterator implementation.
+
+---@class Iter
+local Iter = {}
+Iter.__index = Iter
+Iter.__call = function(self)
+  return self:next()
+end
+
+--- Special case implementations for iterators on list tables.
+---@class ListIter : Iter
+---@field _table table Underlying table data (table iterators only)
+---@field _head number Index to the front of a table iterator (table iterators only)
+---@field _tail number Index to the end of a table iterator (table iterators only)
+local ListIter = {}
+ListIter.__index = setmetatable(ListIter, Iter)
+ListIter.__call = function(self)
+  return self:next()
+end
+
+--- Special case implementations for iterators on non-list tables.
+---@class TableIter : Iter
+local TableIter = {}
+TableIter.__index = setmetatable(TableIter, Iter)
+TableIter.__call = function(self)
+  return self:next()
+end
+
+---@private
+local function unpack(t)
+  if type(t) == 'table' then
+    return _G.unpack(t)
+  end
+  return t
+end
+
+---@private
+local function pack(...)
+  if select('#', ...) > 1 then
+    return { ... }
+  end
+  return ...
+end
+
+--- Add a filter step to the iterator pipeline.
+---
+--- Example:
+--- 
lua
+--- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
+--- 
+--- +---@param f function(...):bool Takes all values returned from the previous stage in the pipeline and +--- returns false or nil if the current iterator element should be +--- removed. +---@return Iter +function Iter.filter(self, f) + ---@private + local function fn(...) + local result = nil + if select(1, ...) ~= nil then + if not f(...) then + return true, nil + else + result = pack(...) + end + end + return false, result + end + + local next = self.next + self.next = function(this) + local cont, result + repeat + cont, result = fn(next(this)) + until not cont + return unpack(result) + end + return self +end + +---@private +function ListIter.filter(self, f) + local inc = self._head < self._tail and 1 or -1 + local n = self._head + for i = self._head, self._tail - inc, inc do + local v = self._table[i] + if f(unpack(v)) then + self._table[n] = v + n = n + inc + end + end + self._tail = n + return self +end + +--- Add a map step to the iterator pipeline. +--- +--- If the map function returns nil, the value is filtered from the iterator. +--- +--- Example: +---
lua
+--- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
+---   if v % 2 == 0 then
+---     return v * 3
+---   end
+--- end)
+--- it:totable()
+--- -- { 6, 12 }
+--- 
+--- +---@param f function(...):any Mapping function. Takes all values returned from the previous stage +--- in the pipeline as arguments and returns one or more new values, +--- which are used in the next pipeline stage. Nil return values returned +--- are filtered from the output. +---@return Iter +function Iter.map(self, f) + ---@private + local function fn(...) + local result = nil + if select(1, ...) ~= nil then + result = pack(f(...)) + if result == nil then + return true, nil + end + end + return false, result + end + + local next = self.next + self.next = function(this) + local cont, result + repeat + cont, result = fn(next(this)) + until not cont + return unpack(result) + end + return self +end + +---@private +function ListIter.map(self, f) + local inc = self._head < self._tail and 1 or -1 + local n = self._head + for i = self._head, self._tail - inc, inc do + local v = pack(f(unpack(self._table[i]))) + if v ~= nil then + self._table[n] = v + n = n + inc + end + end + self._tail = n + return self +end + +--- Call a function once for each item in the pipeline. +--- +--- This is used for functions which have side effects. To modify the values in the iterator, use +--- |Iter:map()|. +--- +--- This function drains the iterator. +--- +---@param f function(...) Function to execute for each item in the pipeline. Takes all of the +--- values returned by the previous stage in the pipeline as arguments. +function Iter.each(self, f) + ---@private + local function fn(...) + if select(1, ...) ~= nil then + f(...) + return true + end + end + while fn(self:next()) do + end +end + +---@private +function ListIter.each(self, 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])) + end + self._head = self._tail +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 table. If multiple values are returned +--- from the final stage in the iterator pipeline, each value will be included in a table. If a +--- map-like table was used as the initial source, then a map-like table is returned. +--- +--- Examples: +---
lua
+--- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
+--- -- { 100, 20, 50 }
+---
+--- vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable()
+--- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
+---
+--- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
+--- -- { a = 1, c = 3 }
+--- 
+--- +---@return table +function Iter.totable(self) + local t = {} + + while true do + local args = pack(self:next()) + if args == nil then + break + end + t[#t + 1] = args + end + return t +end + +---@private +function ListIter.totable(self) + if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then + return self._table + end + + return Iter.totable(self) +end + +---@private +function TableIter.totable(self) + local t = {} + for k, v in self do + t[k] = v + end + return t +end + +--- Fold an iterator or table into a single value. +--- +---@generic A +--- +---@param init A Initial value of the accumulator. +---@param f function(acc:A, ...):A Accumulation function. +---@return A +function Iter.fold(self, init, f) + local acc = init + + --- Use a closure to handle var args returned from iterator + ---@private + local function fn(...) + if select(1, ...) ~= nil then + acc = f(acc, ...) + return true + end + end + + while fn(self:next()) do + end + return acc +end + +---@private +function ListIter.fold(self, init, f) + local acc = init + local inc = self._head < self._tail and 1 or -1 + for i = self._head, self._tail - inc, inc do + acc = f(acc, unpack(self._table[i])) + end + return acc +end + +--- Return the next value from the iterator. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
+--- it:next()
+--- -- 1
+--- it:next()
+--- -- 2
+--- it:next()
+--- -- 3
+---
+--- 
+--- +---@return any +function Iter.next(self) -- luacheck: no unused args + -- This function is provided by the source iterator in Iter.new. This definition exists only for + -- the docstring +end + +---@private +function ListIter.next(self) + if self._head ~= self._tail then + local v = self._table[self._head] + local inc = self._head < self._tail and 1 or -1 + self._head = self._head + inc + return unpack(v) + end +end + +--- Reverse an iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 }):rev()
+--- it:totable()
+--- -- { 12, 9, 6, 3 }
+---
+--- 
+--- +---@return Iter +function Iter.rev(self) + error('rev() requires a list-like table') + return self +end + +---@private +function ListIter.rev(self) + local inc = self._head < self._tail and 1 or -1 + self._head, self._tail = self._tail - inc, self._head - inc + return self +end + +--- Peek at the next value in the iterator without consuming it. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:peek()
+--- -- 3
+--- it:peek()
+--- -- 3
+--- it:next()
+--- -- 3
+---
+--- 
+--- +---@return any +function Iter.peek(self) -- luacheck: no unused args + error('peek() requires a list-like table') +end + +---@private +function ListIter.peek(self) + if self._head ~= self._tail then + return self._table[self._head] + end +end + +--- Find the first value in the iterator that satisfies the given predicate. +--- +--- Advances the iterator. Returns nil and drains the iterator if no value is found. +--- +--- Examples: +---
lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(12)
+--- -- 12
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(20)
+--- -- nil
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(function(v) return v % 4 == 0 end)
+--- -- 12
+---
+--- 
+--- +---@return any +function Iter.find(self, f) + if type(f) ~= 'function' then + local val = f + f = function(v) + return v == val + end + end + + local result = nil + + --- Use a closure to handle var args returned from iterator + ---@private + local function fn(...) + if select(1, ...) ~= nil then + if f(...) then + result = pack(...) + else + return true + end + end + end + + while fn(self:next()) do + end + return unpack(result) +end + +--- Find the first value in the iterator that satisfies the given predicate, starting from the end. +--- +--- Advances the iterator. Returns nil and drains the iterator if no value is found. +--- +--- Only supported for iterators on list-like tables. +--- +--- Examples: +---
lua
+---
+--- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
+--- it:rfind(1)
+--- -- 5	1
+--- it:rfind(1)
+--- -- 1	1
+---
+--- 
+--- +---@see Iter.find +--- +---@return any +function Iter.rfind(self, f) -- luacheck: no unused args + error('rfind() requires a list-like table') +end + +---@private +function ListIter.rfind(self, f) -- luacheck: no unused args + if type(f) ~= 'function' then + local val = f + f = function(v) + return v == val + end + end + + local inc = self._head < self._tail and 1 or -1 + for i = self._tail - inc, self._head, -inc do + local v = self._table[i] + if f(unpack(v)) then + self._tail = i + return unpack(v) + end + end + self._head = self._tail +end + +--- Return the next value from the end of the iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+--- local it = vim.iter({1, 2, 3, 4})
+--- it:nextback()
+--- -- 4
+--- it:nextback()
+--- -- 3
+--- 
+--- +---@return any +function Iter.nextback(self) -- luacheck: no unused args + error('nextback() requires a list-like table') +end + +function ListIter.nextback(self) + if self._head ~= self._tail then + local inc = self._head < self._tail and 1 or -1 + self._tail = self._tail - inc + return self._table[self._tail] + end +end + +--- Return the next value from the end of the iterator without consuming it. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+--- local it = vim.iter({1, 2, 3, 4})
+--- it:peekback()
+--- -- 4
+--- it:peekback()
+--- -- 4
+--- it:nextback()
+--- -- 4
+--- 
+--- +---@return any +function Iter.peekback(self) -- luacheck: no unused args + error('peekback() requires a list-like table') +end + +function ListIter.peekback(self) + if self._head ~= self._tail then + local inc = self._head < self._tail and 1 or -1 + return self._table[self._tail - inc] + end +end + +--- Skip values in the iterator. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
+--- it:next()
+--- -- 9
+---
+--- 
+--- +---@param n number Number of values to skip. +---@return Iter +function Iter.skip(self, n) + for _ = 1, n do + local _ = self:next() + end + return self +end + +---@private +function ListIter.skip(self, 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 + self._head = self._tail + end + return self +end + +--- Skip values in the iterator starting from the end. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2)
+--- it:next()
+--- -- 1
+--- it:nextback()
+--- -- 3
+--- 
+--- +---@param n number Number of values to skip. +---@return Iter +function Iter.skipback(self, n) -- luacheck: no unused args + error('skipback() requires a list-like table') + return self +end + +---@private +function ListIter.skipback(self, 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 + self._head = self._tail + end + return self +end + +--- Return the nth value in the iterator. +--- +--- This function advances the 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(self, n) + if n > 0 then + return self:skip(n - 1):next() + end +end + +--- Return the nth value from the end of the iterator. +--- +--- This function advances the iterator. +--- +--- Only supported for iterators on list-like tables. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:nthback(2)
+--- -- 9
+--- it:nthback(2)
+--- -- 3
+---
+--- 
+--- +---@param n number The index of the value to return. +---@return any +function Iter.nthback(self, n) + if n > 0 then + return self:skipback(n - 1):nextback() + end +end + +--- Slice an iterator, changing its start and end positions. +--- +--- This is equivalent to :skip(first - 1):skipback(len - last + 1) +--- +--- Only supported for iterators on list-like tables. +--- +---@param first number +---@param last number +---@return Iter +function Iter.slice(self, first, last) -- luacheck: no unused args + return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1)) +end + +--- Return true if any of the items in the iterator match the given predicate. +--- +---@param pred function(...):bool Predicate function. Takes all values returned from the previous +--- stage in the pipeline as arguments and returns true if the +--- predicate matches. +function Iter.any(self, pred) + local any = false + + --- Use a closure to handle var args returned from iterator + ---@private + local function fn(...) + if select(1, ...) ~= nil then + if pred(...) then + any = true + else + return true + end + end + end + + while fn(self:next()) do + end + return any +end + +--- Return true if all of the items in the iterator match the given predicate. +--- +---@param pred function(...):bool Predicate function. Takes all values returned from the previous +--- stage in the pipeline as arguments and returns true if the +--- predicate matches. +function Iter.all(self, pred) + local all = true + + ---@private + local function fn(...) + if select(1, ...) ~= nil then + if not pred(...) then + all = false + else + return true + end + end + end + + while fn(self:next()) do + end + return all +end + +--- Return the last item in the iterator. +--- +--- Drains the iterator. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter(vim.gsplit('abcdefg', ''))
+--- it:last()
+--- -- 'g'
+---
+--- local it = vim.iter({ 3, 6, 9, 12, 15 })
+--- it:last()
+--- -- 15
+---
+--- 
+--- +---@return any +function Iter.last(self) + local last = self:next() + local cur = self:next() + while cur do + last = cur + cur = self:next() + end + return last +end + +---@private +function ListIter.last(self) + local inc = self._head < self._tail and 1 or -1 + local v = self._table[self._tail - inc] + self._head = self._tail + return v +end + +--- Add an iterator stage that returns the current iterator count as well as the iterator value. +--- +--- For list tables, prefer +---
lua
+--- vim.iter(ipairs(t))
+--- 
+--- +--- over +--- +---
lua
+--- vim.iter(t):enumerate()
+--- 
+--- +--- as the former is faster. +--- +--- Example: +---
lua
+---
+--- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
+--- it:next()
+--- -- 1	'a'
+--- it:next()
+--- -- 2	'b'
+--- it:next()
+--- -- 3	'c'
+---
+--- 
+--- +---@return Iter +function Iter.enumerate(self) + local i = 0 + return self:map(function(...) + i = i + 1 + return i, ... + end) +end + +---@private +function ListIter.enumerate(self) + local inc = self._head < self._tail and 1 or -1 + for i = self._head, self._tail - inc, inc do + local v = self._table[i] + self._table[i] = { i, v } + end + return self +end + +--- Create a new Iter object from a table or iterator. +--- +---@param src table|function Table or iterator to drain values from +---@return Iter +function Iter.new(src, ...) + local it = {} + if type(src) == 'table' then + local t = {} + + -- Check if source table can be treated like a list (indices are consecutive integers + -- starting from 1) + local count = 0 + for _ in pairs(src) do + count = count + 1 + local v = src[count] + if v == nil then + return TableIter.new(src) + end + t[count] = v + end + return ListIter.new(t) + end + + if type(src) == 'function' then + local s, var = ... + + --- Use a closure to handle var args returned from iterator + ---@private + local function fn(...) + if select(1, ...) ~= nil then + var = select(1, ...) + return ... + end + end + + function it.next() + return fn(src(s, var)) + end + + setmetatable(it, Iter) + else + error('src must be a table or function') + end + return it +end + +--- Create a new ListIter +--- +---@param t table List-like table. Caller guarantees that this table is a valid list. +---@return Iter +---@private +function ListIter.new(t) + local it = {} + it._table = t + it._head = 1 + it._tail = #t + 1 + setmetatable(it, ListIter) + return it +end + +--- Create a new TableIter +--- +---@param t table Table to iterate over. For list-like tables, use ListIter.new instead. +---@return Iter +---@private +function TableIter.new(t) + local it = {} + + local index = nil + function it.next() + local k, v = next(t, index) + if k ~= nil then + index = k + return k, v + end + end + + setmetatable(it, TableIter) + return it +end + +return Iter diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index f700f4a6b3..1a96ef9bc4 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -884,4 +884,109 @@ function vim.defaulttable(create) }) end +--- Create an Iter |lua-iter| object from a table or iterator. +--- +--- The input value can be a table or a function iterator (see |luaref-in|). +--- +--- This function wraps the input value into an interface which allows chaining +--- multiple pipeline stages in an efficient manner. Each pipeline stage +--- receives as input the output values from the prior stage. The values used in +--- the first stage of the pipeline depend on the type passed to this function: +--- +--- - List tables pass only the value of each element +--- - Non-list tables pass both the key and value of each element +--- - Function iterators pass all of the values returned by their respective +--- function +--- +--- Examples: +---
lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 })
+--- it:map(function(v)
+---   return v * 3
+--- end)
+--- it:rev()
+--- it:skip(2)
+--- it:totable()
+--- -- { 9, 6, 3 }
+---
+--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+---   if i > 2 then return v end
+--- end):totable()
+--- -- { 3, 4, 5 }
+---
+--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+--- it:map(function(s) return tonumber(s) end)
+--- for i, d in it:enumerate() do
+---   print(string.format("Column %d is %d", i, d))
+--- end
+--- -- Column 1 is 1
+--- -- Column 2 is 2
+--- -- Column 3 is 3
+--- -- Column 4 is 4
+--- -- Column 5 is 5
+---
+--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+---   return k == 'z'
+--- end)
+--- -- true
+--- 
+--- +---@see |lua-iter| +--- +---@param src table|function Table or iterator. +---@return Iter @|lua-iter| +function vim.iter(src, ...) + local Iter = require('vim.iter') + return Iter.new(src, ...) +end + +--- Collect an iterator into a table. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(f):totable()
+--- 
+--- +---@param f function Iterator function +---@return table +function vim.totable(f, ...) + return vim.iter(f, ...):totable() +end + +--- Filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(src):filter(f):totable()
+--- 
+--- +---@see |Iter:filter()| +--- +---@param f function(...):bool 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 vim.filter(f, src, ...) + return vim.iter(src, ...):filter(f):totable() +end + +--- Map and filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(src):map(f):totable()
+--- 
+--- +---@see |Iter:map()| +--- +---@param f function(...):?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 vim.map(f, src, ...) + return vim.iter(src, ...):map(f):totable() +end + return vim -- cgit From 6d9f5b6bf0fc324b33ce01f74a6030c9271b1a01 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 19 Apr 2023 09:41:26 +0200 Subject: vim-patch:9.0.1464: strace filetype detection is expensive (#23175) Problem: Strace filetype detection is expensive. Solution: Match with a cheap pattern first. (Federico Mengozzi, closes vim/vim#12220) https://github.com/vim/vim/commit/6e5a9f948221b52caaaf106079cb3430c4dd7c77 Co-authored-by: Federico Mengozzi <19249682+fedemengo@users.noreply.github.com> --- runtime/lua/vim/filetype/detect.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index d1eabadc4a..94114ae7c3 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1550,8 +1550,15 @@ local patterns_text = { ['^SNNS pattern definition file'] = 'snnspat', ['^SNNS result file'] = 'snnsres', ['^%%.-[Vv]irata'] = { 'virata', { start_lnum = 1, end_lnum = 5 } }, - ['[0-9:%.]* *execve%('] = 'strace', - ['^__libc_start_main'] = 'strace', + function(lines) + if + -- inaccurate fast match first, then use accurate slow match + (lines[1]:find('execve%(') and lines[1]:find('^[0-9:%.]* *execve%(')) + or lines[1]:find('^__libc_start_main') + then + return 'strace' + end + end, -- VSE JCL ['^\\* $$ JOB\\>'] = { 'vsejcl', { vim_regex = true } }, ['^// *JOB\\>'] = { 'vsejcl', { vim_regex = true } }, -- cgit From cff02e993d920fa4bf0b5dc8b8f12d979850f049 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 19 Apr 2023 10:19:33 +0200 Subject: vim-patch:9.0.1467: Jenkinsfiles are not recognized as groovy (#23195) Problem: Jenkinsfiles are not recognized as groovy. Solution: Add a pattern for Jenkinsfiles. (closes vim/vim#12236) https://github.com/vim/vim/commit/142ffb024dd5123090c2fd02f55702e76520f1df Co-authored-by: dundargoc --- 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 74ab7d8260..6cfe6e6c35 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1469,6 +1469,7 @@ local filename = { ['.gprc'] = 'gp', ['/.gnupg/gpg.conf'] = 'gpg', ['/.gnupg/options'] = 'gpg', + ['Jenkinsfile'] = 'groovy', ['/var/backups/gshadow.bak'] = 'group', ['/etc/gshadow'] = 'group', ['/etc/group-'] = 'group', -- cgit From 6b96122453fda22dc44a581af1d536988c1adf41 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 19 Apr 2023 06:45:56 -0600 Subject: fix(iter): add tag to packed table If pack() is called with a single value, it does not create a table; it simply returns the value it is passed. When unpack is called with a table argument, it interprets that table as a list of values that were packed together into a table. This causes a problem when the single value being packed is _itself_ a table. pack() will not place it into another table, but unpack() sees the table argument and tries to unpack it. To fix this, we add a simple "tag" to packed table values so that unpack() only attempts to unpack tables that have this tag. Other tables are left alone. The tag is simply the length of the table. --- runtime/lua/vim/iter.lua | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index b73c03ba9a..fff7644b6a 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -28,16 +28,17 @@ end ---@private local function unpack(t) - if type(t) == 'table' then - return _G.unpack(t) + if type(t) == 'table' and t.__n ~= nil then + return _G.unpack(t, 1, t.__n) end return t end ---@private local function pack(...) - if select('#', ...) > 1 then - return { ... } + local n = select('#', ...) + if n > 1 then + return { __n = n, ... } end return ... end @@ -210,6 +211,12 @@ function Iter.totable(self) if args == nil then break end + + if type(args) == 'table' then + -- Removed packed table tag if it exists + args.__n = nil + end + t[#t + 1] = args end return t @@ -218,6 +225,14 @@ end ---@private function ListIter.totable(self) if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then + -- Remove any packed table tags + for i = 1, #self._table do + local v = self._table[i] + if type(v) == 'table' then + v.__n = nil + self._table[i] = v + end + end return self._table end @@ -747,7 +762,7 @@ function ListIter.enumerate(self) local inc = self._head < self._tail and 1 or -1 for i = self._head, self._tail - inc, inc do local v = self._table[i] - self._table[i] = { i, v } + self._table[i] = pack(i, v) end return self end -- cgit From 94894068794dbb99804cda689b6c37e70376c8ca Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Wed, 19 Apr 2023 07:05:04 -0600 Subject: fix(iter): remove special case totable for map-like tables This was originally meant as a convenience but prevents possible functionality. For example: -- Get the keys of the table with even values local t = { a = 1, b = 2, c = 3, d = 4 } vim.iter(t):map(function(k, v) if v % 2 == 0 then return k end end):totable() The example above would not work, because the map() function returns only a single value, and cannot be converted back into a table (there are many such examples like this). Instead, to convert an iterator into a map-like table, users can use fold(): vim.iter(t):fold({}, function(t, k, v) t[k] = v return t end) --- runtime/lua/vim/iter.lua | 67 +++++++++++++++++------------------------------- 1 file changed, 23 insertions(+), 44 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index fff7644b6a..c5d5ef835b 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -18,14 +18,6 @@ ListIter.__call = function(self) return self:next() end ---- Special case implementations for iterators on non-list tables. ----@class TableIter : Iter -local TableIter = {} -TableIter.__index = setmetatable(TableIter, Iter) -TableIter.__call = function(self) - return self:next() -end - ---@private local function unpack(t) if type(t) == 'table' and t.__n ~= nil then @@ -185,10 +177,10 @@ 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 table. If multiple values are returned ---- from the final stage in the iterator pipeline, each value will be included in a table. If a ---- map-like table was used as the initial source, then a map-like table is returned. +--- 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 +--- table. If multiple values are returned from the final stage in the iterator +--- pipeline, each value will be included in a table. --- --- Examples: ---
lua
@@ -199,9 +191,13 @@ end
 --- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
 ---
 --- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
---- -- { a = 1, c = 3 }
+--- -- { { 'a', 1 }, { 'c', 3 } }
 --- 
--- +--- The generated table is a list-like table with consecutive, numeric indices. +--- To create a map-like table with arbitrary keys, use |Iter:fold()|. +--- +--- ---@return table function Iter.totable(self) local t = {} @@ -239,17 +235,21 @@ function ListIter.totable(self) return Iter.totable(self) end ----@private -function TableIter.totable(self) - local t = {} - for k, v in self do - t[k] = v - end - return t -end - --- Fold an iterator or table into a single value. --- +--- Examples: +---
+--- -- 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 }
+--- 
+--- ---@generic A --- ---@param init A Initial value of the accumulator. @@ -783,7 +783,7 @@ function Iter.new(src, ...) count = count + 1 local v = src[count] if v == nil then - return TableIter.new(src) + return Iter.new(pairs(src)) end t[count] = v end @@ -827,25 +827,4 @@ function ListIter.new(t) return it end ---- Create a new TableIter ---- ----@param t table Table to iterate over. For list-like tables, use ListIter.new instead. ----@return Iter ----@private -function TableIter.new(t) - local it = {} - - local index = nil - function it.next() - local k, v = next(t, index) - if k ~= nil then - index = k - return k, v - end - end - - setmetatable(it, TableIter) - return it -end - return Iter -- cgit From ab2811746eb72e06309a9877dbe6cb70d9cd3b12 Mon Sep 17 00:00:00 2001 From: William <50717946+BIKA-C@users.noreply.github.com> Date: Thu, 20 Apr 2023 06:42:49 -0700 Subject: fix(treesitter playground): fix the wrong range of a node displayed i… (#23209) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(treesitter playground): wrong range of a node displayed in playground The call parameters order of the function `get_range_str` is flipped for the last two arguments compared to the declaration. --- runtime/lua/vim/treesitter/playground.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index 2c0a0d1aa6..c512710810 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -147,7 +147,7 @@ local decor_ns = api.nvim_create_namespace('ts.playground') ---@param end_lnum integer ---@param end_col integer ---@return string -local function get_range_str(lnum, col, end_col, end_lnum) +local function get_range_str(lnum, col, end_lnum, end_col) if lnum == end_lnum then return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col) end -- cgit From 622b1ae38a36c3d26fad19faa788d622f7835921 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 21 Apr 2023 06:46:18 +0200 Subject: fix(lua): vim.split may trim inner empty items Problem: `vim.split('a:::', ':', {trimempty=true})` trims inner empty items. Regression from 9c49c1047079427ff0a2356cb37302934845108e Solution: Set `empty_start=false` when first non-empty item is found. close #23212 --- runtime/lua/vim/shared.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 1a96ef9bc4..08f8afd087 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -138,7 +138,9 @@ function vim.gsplit(s, sep, opts) local seg = _pass(s:find(sep, start, plain)) -- Trim empty segments from start/end. - if trimempty and seg == '' then + if seg ~= '' then + empty_start = false + elseif trimempty then while not done and seg == '' do empty_segs = empty_segs + 1 seg = _pass(s:find(sep, start, plain)) -- cgit From 824766612d4e7d53b717bd1e2b0d1d895054accd Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Fri, 21 Apr 2023 07:26:44 +0200 Subject: refactor(lua): simplify vim.gsplit impl --- runtime/lua/vim/shared.lua | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 08f8afd087..5f4ea8822b 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -100,10 +100,9 @@ function vim.gsplit(s, sep, opts) local start = 1 local done = false - -- For `trimempty`: + -- For `trimempty`: queue of collected segments, to be emitted at next pass. + local segs = {} local empty_start = true -- Only empty segments seen so far. - local empty_segs = 0 -- Empty segments found between non-empty segments. - local nonemptyseg = nil local function _pass(i, j, ...) if i then @@ -118,14 +117,9 @@ function vim.gsplit(s, sep, opts) end return function() - if trimempty and empty_segs > 0 then - -- trimempty: Pop the collected empty segments. - empty_segs = empty_segs - 1 - return '' - elseif trimempty and nonemptyseg then - local seg = nonemptyseg - nonemptyseg = nil - return seg + if trimempty and #segs > 0 then + -- trimempty: Pop the collected segments. + return table.remove(segs) elseif done or (s == '' and sep == '') then return nil elseif sep == '' then @@ -138,23 +132,24 @@ function vim.gsplit(s, sep, opts) local seg = _pass(s:find(sep, start, plain)) -- Trim empty segments from start/end. - if seg ~= '' then + if trimempty and seg ~= '' then empty_start = false - elseif trimempty then + elseif trimempty and seg == '' then while not done and seg == '' do - empty_segs = empty_segs + 1 + table.insert(segs, 1, '') seg = _pass(s:find(sep, start, plain)) end if done and seg == '' then return nil elseif empty_start then empty_start = false - empty_segs = 0 + segs = {} return seg end - nonemptyseg = seg ~= '' and seg or nil - seg = '' - empty_segs = empty_segs - 1 + if seg ~= '' then + table.insert(segs, 1, seg) + end + return table.remove(segs) end return seg -- cgit From f68af3c3bc92c12f7dbbd32f44df8ab57a58ac98 Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Fri, 21 Apr 2023 16:13:39 -0600 Subject: refactor(iter): use metatable as packed table tag (#23254) This is a more robust method for tagging a packed table as it completely eliminates the possibility of mistaking an actual table key as the packed table tag. --- runtime/lua/vim/iter.lua | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index c5d5ef835b..2545853b41 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -18,10 +18,13 @@ ListIter.__call = function(self) return self:next() end +--- Packed tables use this as their metatable +local packedmt = {} + ---@private local function unpack(t) - if type(t) == 'table' and t.__n ~= nil then - return _G.unpack(t, 1, t.__n) + if getmetatable(t) == packedmt then + return _G.unpack(t, 1, t.n) end return t end @@ -30,11 +33,20 @@ end local function pack(...) local n = select('#', ...) if n > 1 then - return { __n = n, ... } + return setmetatable({ n = n, ... }, packedmt) end return ... end +---@private +local function sanitize(t) + if getmetatable(t) == packedmt then + -- Remove length tag + t.n = nil + end + return t +end + --- Add a filter step to the iterator pipeline. --- --- Example: @@ -208,12 +220,7 @@ function Iter.totable(self) break end - if type(args) == 'table' then - -- Removed packed table tag if it exists - args.__n = nil - end - - t[#t + 1] = args + t[#t + 1] = sanitize(args) end return t end @@ -221,12 +228,10 @@ end ---@private function ListIter.totable(self) if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then - -- Remove any packed table tags - for i = 1, #self._table do - local v = self._table[i] - if type(v) == 'table' then - v.__n = nil - self._table[i] = v + -- Sanitize packed table values + if getmetatable(self._table[1]) == packedmt then + for i = 1, #self._table do + self._table[i] = sanitize(self._table[i]) end end return self._table -- cgit From e9b85acfbb8d3b1dd6f92deb187800be757c6c68 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Sat, 22 Apr 2023 02:37:38 -0500 Subject: feat(lsp): enable workspace/didChangeWatchedFiles by default (#23190) --- runtime/lua/vim/lsp/protocol.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 2cb8fc7955..a7919f12f5 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -837,7 +837,7 @@ function protocol.make_client_capabilities() refreshSupport = true, }, didChangeWatchedFiles = { - dynamicRegistration = false, + dynamicRegistration = true, relativePatternSupport = true, }, }, -- cgit From 1cb46abff1e903e999105b244329f22678ba8a30 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sat, 22 Apr 2023 23:51:53 +0800 Subject: vim-patch:9.0.1475: busted configuration files are not recognized (#23266) Problem: Busted configuration files are not recognized. Solution: Recognize busted configuration files as Lua. (Craig MacEachern, closes vim/vim#12209) Co-authored-by: C.D. MacEachern --- 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 6cfe6e6c35..cbd9aa640b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1522,6 +1522,7 @@ local filename = { ['.lsl'] = function(path, bufnr) return require('vim.filetype.detect').lsl(bufnr) end, + ['.busted'] = 'lua', ['.luacheckrc'] = 'lua', ['lynx.cfg'] = 'lynx', ['m3overrides'] = 'm3build', -- cgit From f17bb4f41102ecec7989bc4c14c626dc595e2f0b Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 23 Apr 2023 14:15:52 +0200 Subject: vim-patch:9.0.1478: filetypes for *.v files not detected properly (#23282) * vim-patch:9.0.1478: filetypes for *.v files not detected properly Problem: Filetypes for *.v files not detected properly. Solution: Use the file contents to detect the filetype. (Turiiya, closes vim/vim#12281) https://github.com/vim/vim/commit/80406c26188219f3773b2e9c49160caeeb386ee2 Co-authored-by: Turiiya <34311583+tobealive@users.noreply.github.com> Co-authored-by: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> --- runtime/lua/vim/filetype.lua | 4 +++- runtime/lua/vim/filetype/detect.lua | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index cbd9aa640b..4fafc4e2e2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1091,7 +1091,9 @@ local extension = { vr = 'vera', vri = 'vera', vrh = 'vera', - v = 'verilog', + v = function(path, bufnr) + return require('vim.filetype.detect').v(bufnr) + end, va = 'verilogams', vams = 'verilogams', vhdl = 'vhdl', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 94114ae7c3..74b01d569c 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1322,6 +1322,24 @@ function M.txt(bufnr) end end +-- Determine if a .v file is Verilog, V, or Coq +function M.v(bufnr) + if vim.fn.did_filetype() ~= 0 then + -- Filetype was already detected + return + end + for _, line in ipairs(getlines(bufnr, 1, 200)) do + if not line:find('^%s*/') then + if findany(line, { ';%s*$', ';%s*/' }) then + return 'verilog' + elseif findany(line, { '%.%s*$', '%.%s*%(%*' }) then + return 'coq' + end + end + end + return 'v' +end + -- WEB (*.web is also used for Winbatch: Guess, based on expecting "%" comment -- lines in a WEB file). function M.web(bufnr) -- cgit From 1e73891d696a00b046ab19d245001424b174c931 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Mon, 24 Apr 2023 19:57:40 -0600 Subject: refactor(iter): move helper functions under vim.iter vim.iter is now both a function and a module (similar to vim.version). --- runtime/lua/vim/_init_packages.lua | 1 + runtime/lua/vim/iter.lua | 112 ++++++++++++++++++++++++++++++++++++- runtime/lua/vim/shared.lua | 105 ---------------------------------- 3 files changed, 110 insertions(+), 108 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 2cf2e91a8c..5db258c011 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -55,6 +55,7 @@ vim._submodules = { inspect = true, version = true, fs = true, + iter = true, } -- These are for loading runtime modules in the vim namespace lazily. diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 2545853b41..c2e2c5bd9f 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,4 +1,56 @@ ---- Iterator implementation. +---@defgroup lua-iter +--- +--- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator +--- functions. +--- +--- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object with methods (such +--- as |Iter:filter()| and |Iter:map()|) that transform the underlying source data. These methods +--- can be chained together to create iterator "pipelines". Each pipeline stage receives as input +--- the output values from the prior stage. The values used in the first stage of the pipeline +--- depend on the type passed to this function: +--- +--- - List tables pass only the value of each element +--- - Non-list tables pass both the key and value of each element +--- - Function iterators pass all of the values returned by their respective +--- function +--- +--- Examples: +---
lua
+---   local it = vim.iter({ 1, 2, 3, 4, 5 })
+---   it:map(function(v)
+---     return v * 3
+---   end)
+---   it:rev()
+---   it:skip(2)
+---   it:totable()
+---   -- { 9, 6, 3 }
+---
+---   vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+---     if i > 2 then return v end
+---   end):totable()
+---   -- { 3, 4, 5 }
+---
+---   local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+---   it:map(function(s) return tonumber(s) end)
+---   for i, d in it:enumerate() do
+---     print(string.format("Column %d is %d", i, d))
+---   end
+---   -- Column 1 is 1
+---   -- Column 2 is 2
+---   -- Column 3 is 3
+---   -- Column 4 is 4
+---   -- Column 5 is 5
+---
+---   vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+---     return k == 'z'
+---   end)
+---   -- true
+--- 
+--- +--- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions +--- like |vim.iter.filter()| and |vim.iter.totable()|. + +local M = {} ---@class Iter local Iter = {} @@ -733,7 +785,6 @@ end ---
--- --- over ---- ---
lua
 --- vim.iter(t):enumerate()
 --- 
@@ -776,6 +827,7 @@ end --- ---@param src table|function Table or iterator to drain values from ---@return Iter +---@private function Iter.new(src, ...) local it = {} if type(src) == 'table' then @@ -807,6 +859,7 @@ function Iter.new(src, ...) end end + ---@private function it.next() return fn(src(s, var)) end @@ -832,4 +885,57 @@ function ListIter.new(t) return it end -return Iter +--- Collect an iterator into a table. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(f):totable()
+--- 
+--- +---@param f function Iterator function +---@return table +function M.totable(f, ...) + return Iter.new(f, ...):totable() +end + +--- Filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(src):filter(f):totable()
+--- 
+--- +---@see |Iter:filter()| +--- +---@param f function(...):bool 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 + +--- Map and filter a table or iterator. +--- +--- This is a convenience function that performs: +---
lua
+--- vim.iter(src):map(f):totable()
+--- 
+--- +---@see |Iter:map()| +--- +---@param f function(...):?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(...) + end, +}) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 5f4ea8822b..a55deb1415 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -881,109 +881,4 @@ function vim.defaulttable(create) }) end ---- Create an Iter |lua-iter| object from a table or iterator. ---- ---- The input value can be a table or a function iterator (see |luaref-in|). ---- ---- This function wraps the input value into an interface which allows chaining ---- multiple pipeline stages in an efficient manner. Each pipeline stage ---- receives as input the output values from the prior stage. The values used in ---- the first stage of the pipeline depend on the type passed to this function: ---- ---- - List tables pass only the value of each element ---- - Non-list tables pass both the key and value of each element ---- - Function iterators pass all of the values returned by their respective ---- function ---- ---- Examples: ----
lua
---- local it = vim.iter({ 1, 2, 3, 4, 5 })
---- it:map(function(v)
----   return v * 3
---- end)
---- it:rev()
---- it:skip(2)
---- it:totable()
---- -- { 9, 6, 3 }
----
---- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
----   if i > 2 then return v end
---- end):totable()
---- -- { 3, 4, 5 }
----
---- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
---- it:map(function(s) return tonumber(s) end)
---- for i, d in it:enumerate() do
----   print(string.format("Column %d is %d", i, d))
---- end
---- -- Column 1 is 1
---- -- Column 2 is 2
---- -- Column 3 is 3
---- -- Column 4 is 4
---- -- Column 5 is 5
----
---- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
----   return k == 'z'
---- end)
---- -- true
---- 
---- ----@see |lua-iter| ---- ----@param src table|function Table or iterator. ----@return Iter @|lua-iter| -function vim.iter(src, ...) - local Iter = require('vim.iter') - return Iter.new(src, ...) -end - ---- Collect an iterator into a table. ---- ---- This is a convenience function that performs: ----
lua
---- vim.iter(f):totable()
---- 
---- ----@param f function Iterator function ----@return table -function vim.totable(f, ...) - return vim.iter(f, ...):totable() -end - ---- Filter a table or iterator. ---- ---- This is a convenience function that performs: ----
lua
---- vim.iter(src):filter(f):totable()
---- 
---- ----@see |Iter:filter()| ---- ----@param f function(...):bool 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 vim.filter(f, src, ...) - return vim.iter(src, ...):filter(f):totable() -end - ---- Map and filter a table or iterator. ---- ---- This is a convenience function that performs: ----
lua
---- vim.iter(src):map(f):totable()
---- 
---- ----@see |Iter:map()| ---- ----@param f function(...):?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 vim.map(f, src, ...) - return vim.iter(src, ...):map(f):totable() -end - return vim -- cgit From 7e70ca0b4808bb9d8f19c28c8f93e8f2b9e0d0f0 Mon Sep 17 00:00:00 2001 From: ii14 <59243201+ii14@users.noreply.github.com> Date: Tue, 25 Apr 2023 16:52:44 +0200 Subject: feat(lua): vim.keycode (#22960) Using nvim_replace_termcodes is too verbose, add vim.keycode for translating keycodes. Co-authored-by: ii14 --- runtime/lua/vim/_editor.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index c922ec93db..20e813d77c 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -829,6 +829,20 @@ function vim.print(...) return ... end +--- Translate keycodes. +--- +--- Example: +---
lua
+---   local k = vim.keycode
+---   vim.g.mapleader = k''
+--- 
+--- @param str string String to be converted. +--- @return string +--- @see |nvim_replace_termcodes()| +function vim.keycode(str) + return vim.api.nvim_replace_termcodes(str, true, true, true) +end + function vim._cs_remote(rcid, server_addr, connect_error, args) local function connection_failure_errmsg(consequence) local explanation -- cgit From 45bcf8386918bbb475fbe20c48b508aa89ed0624 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 20 Apr 2023 13:19:38 +0200 Subject: refactor(build): include lpeg as a library --- runtime/lua/vim/_init_packages.lua | 1 + runtime/lua/vim/re.lua | 269 +++++++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+) create mode 100644 runtime/lua/vim/re.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 5db258c011..8750afba34 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -56,6 +56,7 @@ vim._submodules = { version = true, fs = true, iter = true, + re = true, } -- These are for loading runtime modules in the vim namespace lazily. diff --git a/runtime/lua/vim/re.lua b/runtime/lua/vim/re.lua new file mode 100644 index 0000000000..dffe37186a --- /dev/null +++ b/runtime/lua/vim/re.lua @@ -0,0 +1,269 @@ +-- $Id: re.lua $ +-- vendored from lpeg-1.0.2 +-- Copyright © 2007-2019 Lua.org, PUC-Rio. + +-- imported functions and modules +local tonumber, type, print, error = tonumber, type, print, error +local setmetatable = setmetatable +local m = require"lpeg" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- pattern's metatable +local mt = getmetatable(mm.P(0)) + + + +-- No more global accesses after this point +local version = _VERSION +if version == "Lua 5.2" then _ENV = nil end + + +local any = m.P(1) + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function patt_error (s, i) + local msg = (#s < i + 20) and s:sub(i) + or s:sub(i,i+20) .. "..." + msg = ("pattern error near '%s'"):format(msg) + error(msg, 2) +end + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0 + +local arrow = S * "<-" + +local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1 + +name = m.C(name) + + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then error("undefined name: " .. id) end + return c +end + +-- match a name and return a group of its corresponding definition +-- and 'f' (to be folded in 'Suffix') +local function defwithfunc (f) + return m.Cg(Def / getdef * m.Cc(f)) +end + + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'")^0) * "'" + + '"' * m.C((any - '"')^0) * '"' + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then error ("name '" .. c .. "' undefined") end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = (defined + Range + m.C(any)) / m.P + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(item * (item - "]")^0, mt.__add) / + function (c, p) return c == "^" and any - p or p end + * "]" + +local function adddef (t, k, exp) + if t[k] then + error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) ); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul) + * (#seq_follow + patt_error); + Prefix = "&" * S * m.V"Prefix" / mt.__len + + "!" * S * m.V"Prefix" / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * S * + ( ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * ( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)) + ) + + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + defwithfunc(mt.__div) + ) + + "=>" * S * defwithfunc(m.Cmt) + + "~>" * S * defwithfunc(m.Cf) + ) * S + )^0, function (a,b,f) return f(a,b) end ); + Primary = "(" * m.V"Exp" * ")" + + String / mm.P + + Class + + defined + + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" / + function (n, p) return mm.Cg(p, n) end + + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * m.V"Exp" * "~}" / mm.Cs + + "{|" * m.V"Exp" * "|}" / mm.Ct + + "{" * m.V"Exp" * "}" / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT; + Definition = name * arrow * m.V"Exp"; + Grammar = m.Cg(m.Cc(true), "G") * + m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0, + adddef) / mm.P +} + +local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error) + + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + local cp = pattern:match(p, 1, defs) + if not cp then error("incorrect pattern", 3) end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, +} + +if version == "Lua 5.1" then _G.re = re end + +return re -- cgit From ef1801cc7c3d8fe9fd8524a3b677095d4437fc66 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Fri, 28 Apr 2023 11:37:53 -0600 Subject: perf(iter): reduce number of table allocations Packing and unpacking return values impairs performance considerably. In an attempt to avoid creating tables as much as possible we can instead pass return values between functions (which does not require knowing the number of values a function might return). This makes the code more complex, but improves benchmark numbers non-trivially. --- runtime/lua/vim/iter.lua | 164 +++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 62 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index c2e2c5bd9f..bda3508262 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,13 +1,14 @@ ---@defgroup lua-iter --- ---- The \*vim.iter\* module provides a generic "iterator" interface over tables and iterator ---- functions. +--- The \*vim.iter\* module provides a generic "iterator" interface over tables +--- and iterator functions. --- ---- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object with methods (such ---- as |Iter:filter()| and |Iter:map()|) that transform the underlying source data. These methods ---- can be chained together to create iterator "pipelines". Each pipeline stage receives as input ---- the output values from the prior stage. The values used in the first stage of the pipeline ---- depend on the type passed to this function: +--- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object +--- with methods (such as |Iter:filter()| and |Iter:map()|) that transform the +--- underlying source data. These methods can be chained together to create +--- iterator "pipelines". Each pipeline stage receives as input the output +--- values from the prior stage. The values used in the first stage of the +--- pipeline depend on the type passed to this function: --- --- - List tables pass only the value of each element --- - Non-list tables pass both the key and value of each element @@ -47,8 +48,8 @@ --- -- true --- --- ---- In addition to the |vim.iter()| function, the |vim.iter| module provides convenience functions ---- like |vim.iter.filter()| and |vim.iter.totable()|. +--- In addition to the |vim.iter()| function, the |vim.iter| module provides +--- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|. local M = {} @@ -61,9 +62,9 @@ end --- Special case implementations for iterators on list tables. ---@class ListIter : Iter ----@field _table table Underlying table data (table iterators only) ----@field _head number Index to the front of a table iterator (table iterators only) ----@field _tail number Index to the end of a table iterator (table iterators only) +---@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 local ListIter = {} ListIter.__index = setmetatable(ListIter, Iter) ListIter.__call = function(self) @@ -75,7 +76,7 @@ local packedmt = {} ---@private local function unpack(t) - if getmetatable(t) == packedmt then + if type(t) == 'table' and getmetatable(t) == packedmt then return _G.unpack(t, 1, t.n) end return t @@ -92,13 +93,47 @@ end ---@private local function sanitize(t) - if getmetatable(t) == packedmt then + if type(t) == 'table' and getmetatable(t) == packedmt then -- Remove length tag t.n = nil end return t end +--- Determine if the current iterator stage should continue. +--- +--- If any arguments are passed to this function, then return those arguments +--- and stop the current iterator stage. Otherwise, return true to signal that +--- the current stage should continue. +--- +---@param ... any Function arguments. +---@return boolean True if the iterator stage should continue, false otherwise +---@return any Function arguments. +---@private +local function continue(...) + if select('#', ...) > 0 then + return false, ... + end + return true +end + +--- If no input arguments are given return false, indicating the current +--- iterator stage should stop. Otherwise, apply the arguments to the function +--- f. If that function returns no values, the current iterator stage continues. +--- Otherwise, those values are returned. +--- +---@param f function Function to call with the given arguments +---@param ... any Arguments to apply to f +---@return boolean True if the iterator pipeline should continue, false otherwise +---@return any Return values of f +---@private +local function apply(f, ...) + if select('#', ...) > 0 then + return continue(f(...)) + end + return false +end + --- Add a filter step to the iterator pipeline. --- --- Example: @@ -106,33 +141,16 @@ end --- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded) --- --- ----@param f function(...):bool Takes all values returned from the previous stage in the pipeline and ---- returns false or nil if the current iterator element should be ---- removed. +---@param f function(...):bool Takes all values returned from the previous stage +--- in the pipeline and returns false or nil if the +--- current iterator element should be removed. ---@return Iter function Iter.filter(self, f) - ---@private - local function fn(...) - local result = nil - if select(1, ...) ~= nil then - if not f(...) then - return true, nil - else - result = pack(...) - end + return self:map(function(...) + if f(...) then + return ... end - return false, result - end - - local next = self.next - self.next = function(this) - local cont, result - repeat - cont, result = fn(next(this)) - until not cont - return unpack(result) - end - return self + end) end ---@private @@ -165,31 +183,52 @@ end --- -- { 6, 12 } --- --- ----@param f function(...):any Mapping function. Takes all values returned from the previous stage ---- in the pipeline as arguments and returns one or more new values, ---- which are used in the next pipeline stage. Nil return values returned ---- are filtered from the output. +---@param f function(...):any Mapping function. Takes all values returned from +--- the previous stage in the pipeline as arguments +--- and returns one or more new values, which are used +--- in the next pipeline stage. Nil return values +--- are filtered from the output. ---@return Iter function Iter.map(self, f) + -- Implementation note: the reader may be forgiven for observing that this + -- function appears excessively convoluted. The problem to solve is that each + -- stage of the iterator pipeline can return any number of values, and the + -- number of values could even change per iteration. And the return values + -- must be checked to determine if the pipeline has ended, so we cannot + -- naively forward them along to the next stage. + -- + -- A simple approach is to pack all of the return values into a table, check + -- for nil, then unpack the table for the next stage. However, packing and + -- unpacking tables is quite slow. There is no other way in Lua to handle an + -- unknown number of function return values than to simply forward those + -- values along to another function. Hence the intricate function passing you + -- see here. + + local next = self.next + + --- Drain values from the upstream iterator source until a value can be + --- returned. + --- + --- This is a recursive function. The base case is when the first argument is + --- false, which indicates that the rest of the arguments should be returned + --- as the values for the current iteration stage. + --- + ---@param cont boolean If true, the current iterator stage should continue to + --- pull values from its upstream pipeline stage. + --- Otherwise, this stage is complete and returns the + --- values passed. + ---@param ... any Values to return if cont is false. + ---@return any ---@private - local function fn(...) - local result = nil - if select(1, ...) ~= nil then - result = pack(f(...)) - if result == nil then - return true, nil - end + local function fn(cont, ...) + if cont then + return fn(apply(f, next(self))) end - return false, result + return ... end - local next = self.next - self.next = function(this) - local cont, result - repeat - cont, result = fn(next(this)) - until not cont - return unpack(result) + self.next = function() + return fn(apply(f, next(self))) end return self end @@ -211,17 +250,18 @@ end --- Call a function once for each item in the pipeline. --- ---- This is used for functions which have side effects. To modify the values in the iterator, use ---- |Iter:map()|. +--- This is used for functions which have side effects. To modify the values in +--- the iterator, use |Iter:map()|. --- --- This function drains the iterator. --- ----@param f function(...) Function to execute for each item in the pipeline. Takes all of the ---- values returned by the previous stage in the pipeline as arguments. +---@param f function(...) Function to execute for each item in the pipeline. +--- Takes all of the values returned by the previous stage +--- in the pipeline as arguments. function Iter.each(self, f) ---@private local function fn(...) - if select(1, ...) ~= nil then + if select('#', ...) > 0 then f(...) return true end -- cgit From c194acbfc479d8e5839fa629363f93f6550d035c Mon Sep 17 00:00:00 2001 From: Stephan Seitz Date: Sat, 29 Apr 2023 18:22:26 +0200 Subject: feat(treesitter): add query_linter from nvim-treesitter/playground (#22784) Co-authored-by: clason Co-authored-by: lewis6991 --- runtime/lua/vim/treesitter/_query_linter.lua | 302 +++++++++++++++++++++++++++ runtime/lua/vim/treesitter/playground.lua | 1 + runtime/lua/vim/treesitter/query.lua | 29 +++ 3 files changed, 332 insertions(+) create mode 100644 runtime/lua/vim/treesitter/_query_linter.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua new file mode 100644 index 0000000000..62f28d3097 --- /dev/null +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -0,0 +1,302 @@ +local namespace = vim.api.nvim_create_namespace('vim.treesitter.query_linter') +-- those node names exist for every language +local BUILT_IN_NODE_NAMES = { '_', 'ERROR' } + +local M = {} + +--- @class QueryLinterNormalizedOpts +--- @field langs string[] +--- @field clear boolean + +--- @private +--- Caches parse results for queries for each language. +--- Entries of parse_cache[lang][query_text] will either be true for successful parse or contain the +--- error message of the parse +--- @type table> +local parse_cache = {} + +--- Contains language dependent context for the query linter +--- @class QueryLinterLanguageContext +--- @field lang string? Current `lang` of the targeted parser +--- @field parser_info table? Parser info returned by vim.treesitter.language.inspect +--- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs` + +--- @private +--- Adds a diagnostic for node in the query buffer +--- @param diagnostics Diagnostic[] +--- @param node TSNode +--- @param buf integer +--- @param lint string +--- @param lang string? +local function add_lint_for_node(diagnostics, node, buf, lint, lang) + local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ') + --- @type string + local message = lint .. ': ' .. node_text + local error_range = { node:range() } + diagnostics[#diagnostics + 1] = { + lnum = error_range[1], + end_lnum = error_range[3], + col = error_range[2], + end_col = error_range[4], + severity = vim.diagnostic.ERROR, + message = message, + source = lang, + } +end + +--- @private +--- Determines the target language of a query file by its path: /.scm +--- @param buf integer +--- @return string? +local function guess_query_lang(buf) + local filename = vim.api.nvim_buf_get_name(buf) + if filename ~= '' then + local ok, query_lang = pcall(vim.fn.fnamemodify, filename, ':p:h:t') + if ok then + return query_lang + end + end +end + +--- @private +--- @param buf integer +--- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil +--- @return QueryLinterNormalizedOpts +local function normalize_opts(buf, opts) + opts = opts or {} + if not opts.langs then + opts.langs = guess_query_lang(buf) + end + + if type(opts.langs) ~= 'table' then + --- @diagnostic disable-next-line:assign-type-mismatch + opts.langs = { opts.langs } + end + + --- @cast opts QueryLinterNormalizedOpts + opts.langs = opts.langs or {} + return opts +end + +local lint_query = [[;; query + (program [(named_node) (list) (grouping)] @toplevel) + (named_node + name: _ @node.named) + (anonymous_node + name: _ @node.anonymous) + (field_definition + name: (identifier) @field) + (predicate + name: (identifier) @predicate.name + type: (predicate_type) @predicate.type) + (ERROR) @error +]] + +--- @private +--- @param node TSNode +--- @param buf integer +--- @param lang string +--- @param diagnostics Diagnostic[] +local function check_toplevel(node, buf, lang, diagnostics) + local query_text = vim.treesitter.get_node_text(node, buf) + + if not parse_cache[lang] then + parse_cache[lang] = {} + end + + local lang_cache = parse_cache[lang] + + if lang_cache[query_text] == nil then + local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) + + if not ok and type(err) == 'string' then + err = err:match('.-:%d+: (.+)') + end + + lang_cache[query_text] = ok or err + end + + local cache_entry = lang_cache[query_text] + + if type(cache_entry) == 'string' then + add_lint_for_node(diagnostics, node, buf, cache_entry, lang) + end +end + +--- @private +--- @param node TSNode +--- @param buf integer +--- @param lang string +--- @param parser_info table +--- @param diagnostics Diagnostic[] +local function check_field(node, buf, lang, parser_info, diagnostics) + local field_name = vim.treesitter.get_node_text(node, buf) + if not vim.tbl_contains(parser_info.fields, field_name) then + add_lint_for_node(diagnostics, node, buf, 'Invalid field', lang) + end +end + +--- @private +--- @param node TSNode +--- @param buf integer +--- @param lang string +--- @param parser_info (table) +--- @param diagnostics Diagnostic[] +local function check_node(node, buf, lang, parser_info, diagnostics) + local node_type = vim.treesitter.get_node_text(node, buf) + local is_named = node_type:sub(1, 1) ~= '"' + + if not is_named then + node_type = node_type:gsub('"(.*)".*$', '%1'):gsub('\\(.)', '%1') + end + + local found = vim.tbl_contains(BUILT_IN_NODE_NAMES, node_type) + or vim.tbl_contains(parser_info.symbols, function(s) + return vim.deep_equal(s, { node_type, is_named }) + end, { predicate = true }) + + if not found then + add_lint_for_node(diagnostics, node, buf, 'Invalid node type', lang) + end +end + +--- @private +--- @param node TSNode +--- @param buf integer +--- @param is_predicate boolean +--- @return string +local function get_predicate_name(node, buf, is_predicate) + local name = vim.treesitter.get_node_text(node, buf) + if is_predicate then + if vim.startswith(name, 'not-') then + --- @type string + name = name:sub(string.len('not-') + 1) + end + return name .. '?' + end + return name .. '!' +end + +--- @private +--- @param predicate_node TSNode +--- @param predicate_type_node TSNode +--- @param buf integer +--- @param lang string? +--- @param diagnostics Diagnostic[] +local function check_predicate(predicate_node, predicate_type_node, buf, lang, diagnostics) + local type_string = vim.treesitter.get_node_text(predicate_type_node, buf) + + -- Quirk of the query grammar that directives are also predicates! + if type_string == '?' then + if + not vim.tbl_contains( + vim.treesitter.query.list_predicates(), + get_predicate_name(predicate_node, buf, true) + ) + then + add_lint_for_node(diagnostics, predicate_node, buf, 'Unknown predicate', lang) + end + elseif type_string == '!' then + if + not vim.tbl_contains( + vim.treesitter.query.list_directives(), + get_predicate_name(predicate_node, buf, false) + ) + then + add_lint_for_node(diagnostics, predicate_node, buf, 'Unknown directive', lang) + end + end +end + +--- @private +--- @param buf integer +--- @param match table +--- @param query Query +--- @param lang_context QueryLinterLanguageContext +--- @param diagnostics Diagnostic[] +local function lint_match(buf, match, query, lang_context, diagnostics) + local predicate --- @type TSNode + local predicate_type --- @type TSNode + local lang = lang_context.lang + local parser_info = lang_context.parser_info + + for id, node in pairs(match) do + local cap_id = query.captures[id] + + -- perform language-independent checks only for first lang + if lang_context.is_first_lang then + if cap_id == 'error' then + add_lint_for_node(diagnostics, node, buf, 'Syntax error') + elseif cap_id == 'predicate.name' then + predicate = node + elseif cap_id == 'predicate.type' then + predicate_type = node + end + end + + -- other checks rely on Neovim parser introspection + if lang and parser_info then + if cap_id == 'toplevel' then + check_toplevel(node, buf, lang, diagnostics) + elseif cap_id == 'field' then + check_field(node, buf, lang, parser_info, diagnostics) + elseif cap_id == 'node.named' or cap_id == 'node.anonymous' then + check_node(node, buf, lang, parser_info, diagnostics) + end + end + end + + if predicate and predicate_type then + check_predicate(predicate, predicate_type, buf, lang, diagnostics) + end +end + +--- @private +--- @param buf integer Buffer to lint +--- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil Options for linting +function M.lint(buf, opts) + if buf == 0 then + buf = vim.api.nvim_get_current_buf() + end + + local diagnostics = {} + local query = vim.treesitter.query.parse('query', lint_query) + + opts = normalize_opts(buf, opts) + + -- perform at least one iteration even with no langs to perform language independent checks + for i = 1, math.max(1, #opts.langs) do + local lang = opts.langs[i] + + --- @type boolean, (table|nil) + local ok, parser_info = pcall(vim.treesitter.language.inspect, lang) + if not ok then + parser_info = nil + end + + local parser = vim.treesitter.get_parser(buf) + parser:parse() + parser:for_each_tree(function(tree, ltree) + if ltree:lang() == 'query' then + for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do + local lang_context = { + lang = lang, + parser_info = parser_info, + is_first_lang = i == 1, + } + lint_match(buf, match, query, lang_context, diagnostics) + end + end + end) + end + + vim.diagnostic.set(namespace, buf, diagnostics) +end + +--- @private +--- @param buf integer +function M.clear(buf) + vim.diagnostic.reset(namespace, buf) +end + +return M diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua index c512710810..8293c1bd0a 100644 --- a/runtime/lua/vim/treesitter/playground.lua +++ b/runtime/lua/vim/treesitter/playground.lua @@ -269,6 +269,7 @@ function M.inspect_tree(opts) vim.bo[b].buflisted = false vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' + vim.b[b].disable_query_linter = true vim.bo[b].filetype = 'query' local title --- @type string? diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8a747ba14c..492bfd1ffb 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -714,4 +714,33 @@ function Query:iter_matches(node, source, start, stop) return iter end +---@class QueryLinterOpts +---@field langs (string|string[]|nil) +---@field clear (boolean) + +--- Lint treesitter queries using installed parser, or clear lint errors. +--- +--- Use |treesitter-parsers| in runtimepath to check the query file in {buf} for errors: +--- +--- - verify that used nodes are valid identifiers in the grammar. +--- - verify that predicates and directives are valid. +--- - verify that top-level s-expressions are valid. +--- +--- The found diagnostics are reported using |diagnostic-api|. +--- By default, the parser used for verification is determined by the containing folder +--- of the query file, e.g., if the path is `**/lua/highlights.scm`, the parser for the +--- `lua` language will be used. +---@param buf (integer) Buffer handle +---@param opts (QueryLinterOpts|nil) Optional keyword arguments: +--- - langs (string|string[]|nil) Language(s) to use for checking the query. +--- If multiple languages are specified, queries are validated for all of them +--- - clear (boolean) if `true`, just clear current lint errors +function M.lint(buf, opts) + if opts and opts.clear then + require('vim.treesitter._query_linter').clear(buf) + else + require('vim.treesitter._query_linter').lint(buf, opts) + end +end + return M -- cgit From 668f16bac779ac52d7bd9452e6001a7a6d1e9965 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 30 Apr 2023 11:01:54 +0200 Subject: feat(treesitter): upstream query omnifunc from playground (#23394) and set by default in `ftplugin/query.lua` --- runtime/lua/vim/treesitter/_query_linter.lua | 60 ++++++++++++++++++++++++++-- runtime/lua/vim/treesitter/query.lua | 12 +++++- 2 files changed, 68 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 62f28d3097..ecdee5fc95 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -1,4 +1,6 @@ -local namespace = vim.api.nvim_create_namespace('vim.treesitter.query_linter') +local api = vim.api + +local namespace = api.nvim_create_namespace('vim.treesitter.query_linter') -- those node names exist for every language local BUILT_IN_NODE_NAMES = { '_', 'ERROR' } @@ -49,7 +51,7 @@ end --- @param buf integer --- @return string? local function guess_query_lang(buf) - local filename = vim.api.nvim_buf_get_name(buf) + local filename = api.nvim_buf_get_name(buf) if filename ~= '' then local ok, query_lang = pcall(vim.fn.fnamemodify, filename, ':p:h:t') if ok then @@ -256,7 +258,7 @@ end --- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil Options for linting function M.lint(buf, opts) if buf == 0 then - buf = vim.api.nvim_get_current_buf() + buf = api.nvim_get_current_buf() end local diagnostics = {} @@ -299,4 +301,56 @@ function M.clear(buf) vim.diagnostic.reset(namespace, buf) end +--- @private +--- @param findstart integer +--- @param base string +function M.omnifunc(findstart, base) + if findstart == 1 then + local result = + api.nvim_get_current_line():sub(1, api.nvim_win_get_cursor(0)[2]):find('["#%-%w]*$') + return result - 1 + end + + local buf = api.nvim_get_current_buf() + local query_lang = guess_query_lang(buf) + + local ok, parser_info = pcall(vim.treesitter.language.inspect, query_lang) + if not ok then + return -2 + end + + local items = {} + for _, f in pairs(parser_info.fields) do + if f:find(base, 1, true) then + table.insert(items, f .. ':') + end + end + for _, p in pairs(vim.treesitter.query.list_predicates()) do + local text = '#' .. p + local found = text:find(base, 1, true) + if found and found <= 2 then -- with or without '#' + table.insert(items, text) + end + text = '#not-' .. p + found = text:find(base, 1, true) + if found and found <= 2 then -- with or without '#' + table.insert(items, text) + end + end + for _, p in pairs(vim.treesitter.query.list_directives()) do + local text = '#' .. p + local found = text:find(base, 1, true) + if found and found <= 2 then -- with or without '#' + table.insert(items, text) + end + end + for _, s in pairs(parser_info.symbols) do + local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' + if text:find(base, 1, true) then + table.insert(items, text) + end + end + return { words = items, refresh = 'always' } +end + return M diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 492bfd1ffb..93841bb31e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -728,7 +728,7 @@ end --- --- The found diagnostics are reported using |diagnostic-api|. --- By default, the parser used for verification is determined by the containing folder ---- of the query file, e.g., if the path is `**/lua/highlights.scm`, the parser for the +--- of the query file, e.g., if the path ends in `/lua/highlights.scm`, the parser for the --- `lua` language will be used. ---@param buf (integer) Buffer handle ---@param opts (QueryLinterOpts|nil) Optional keyword arguments: @@ -743,4 +743,14 @@ function M.lint(buf, opts) end end +--- Omnifunc for completing node names and predicates in treesitter queries. +--- +--- Use via +---
lua
+---   vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+--- 
+function M.omnifunc(findstart, base) + return require('vim.treesitter._query_linter').omnifunc(findstart, base) +end + return M -- cgit From 19a793545f15bb7e0bac2fc8f705c600e8f9c9bb Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 30 Apr 2023 16:11:38 +0100 Subject: fix(treesitter): redraw added/removed injections properly (#23287) When injections are added or removed make sure to: - invoke 'changedtree' callbacks for when new trees are added. - invoke 'changedtree' callbacks for when trees are invalidated - redraw regions when languagetree children are removed --- runtime/lua/vim/treesitter/highlighter.lua | 20 ++++--- runtime/lua/vim/treesitter/languagetree.lua | 81 +++++++++++++++++++---------- 2 files changed, 68 insertions(+), 33 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index ac2a929487..4bb764c5c6 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -76,9 +76,6 @@ function TSHighlighter.new(tree, opts) opts = opts or {} ---@type { queries: table } self.tree = tree tree:register_cbs({ - on_changedtree = function(...) - self:on_changedtree(...) - end, on_bytes = function(...) self:on_bytes(...) end, @@ -87,6 +84,17 @@ function TSHighlighter.new(tree, opts) end, }) + tree:register_cbs({ + on_changedtree = function(...) + self:on_changedtree(...) + end, + on_child_removed = function(child) + child:for_each_tree(function(t) + self:on_changedtree(t:included_ranges(true)) + end) + end, + }, true) + self.bufnr = tree:source() --[[@as integer]] self.edit_count = 0 self.redraw_count = 0 @@ -177,10 +185,10 @@ function TSHighlighter:on_detach() end ---@package ----@param changes integer[][]? +---@param changes Range6[][] function TSHighlighter:on_changedtree(changes) - for _, ch in ipairs(changes or {}) do - api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1) + for _, ch in ipairs(changes) do + api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1) end end diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4aa07d1b96..19cea32367 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -51,8 +51,18 @@ local Range = require('vim.treesitter._range') ---| 'on_child_added' ---| 'on_child_removed' +--- @type table +local TSCallbackNames = { + on_changedtree = 'changedtree', + on_bytes = 'bytes', + on_detach = 'detach', + on_child_added = 'child_added', + on_child_removed = 'child_removed', +} + ---@class LanguageTree ---@field private _callbacks table Callback handlers +---@field package _callbacks_rec table Callback handlers (recursive) ---@field private _children table Injected languages ---@field private _injection_query Query Queries defining injected languages ---@field private _opts table Options @@ -79,7 +89,7 @@ LanguageTree.__index = LanguageTree --- "injected" language parsers, which themselves may inject other languages, recursively. --- ---@param source (integer|string) Buffer or text string to parse ----@param lang string|nil Root language of this tree +---@param lang string Root language of this tree ---@param opts (table|nil) Optional arguments: --- - injections table Map of language to injection query strings. Overrides the --- built-in runtime file searching for language injections. @@ -100,15 +110,15 @@ function LanguageTree.new(source, lang, opts) or query.get(lang, 'injections'), _valid = false, _parser = vim._create_ts_parser(lang), - _callbacks = { - changedtree = {}, - bytes = {}, - detach = {}, - child_added = {}, - child_removed = {}, - }, + _callbacks = {}, + _callbacks_rec = {}, }, LanguageTree) + for _, name in pairs(TSCallbackNames) do + self._callbacks[name] = {} + self._callbacks_rec[name] = {} + end + return self end @@ -121,6 +131,7 @@ local function tcall(f, ...) local start = vim.loop.hrtime() ---@diagnostic disable-next-line local r = { f(...) } + --- @type number local duration = (vim.loop.hrtime() - start) / 1000000 return duration, unpack(r) end @@ -161,6 +172,9 @@ function LanguageTree:invalidate(reload) -- buffer was reloaded, reparse all trees if reload then + for _, t in ipairs(self._trees) do + self:_do_callback('changedtree', t:included_ranges(true), t) + end self._trees = {} end @@ -245,9 +259,12 @@ function LanguageTree:parse() if not self._valid or not self._valid[i] then self._parser:set_included_ranges(ranges) local parse_time, tree, tree_changes = - tcall(self._parser.parse, self._parser, self._trees[i], self._source) + tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) + + -- Pass ranges if this is an initial parse + local cb_changes = self._trees[i] and tree_changes or ranges - self:_do_callback('changedtree', tree_changes, tree) + self:_do_callback('changedtree', cb_changes, tree) self._trees[i] = tree vim.list_extend(changes, tree_changes) @@ -341,7 +358,14 @@ function LanguageTree:add_child(lang) self:remove_child(lang) end - self._children[lang] = LanguageTree.new(self._source, lang, self._opts) + 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 + + self._children[lang] = child self:invalidate() self:_do_callback('child_added', self._children[lang]) @@ -453,6 +477,10 @@ function LanguageTree:set_included_regions(new_regions) end if #self:included_regions() ~= #new_regions then + -- TODO(lewis6991): inefficient; invalidate trees incrementally + for _, t in ipairs(self._trees) do + self:_do_callback('changedtree', t:included_ranges(true), t) + end self._trees = {} self:invalidate() else @@ -707,6 +735,9 @@ function LanguageTree:_do_callback(cb_name, ...) for _, cb in ipairs(self._callbacks[cb_name]) do cb(...) end + for _, cb in ipairs(self._callbacks_rec[cb_name]) do + cb(...) + end end ---@package @@ -855,30 +886,26 @@ end --- changed. --- - `on_child_added` : emitted when a child is added to the tree. --- - `on_child_removed` : emitted when a child is removed from the tree. -function LanguageTree:register_cbs(cbs) +--- @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 - if cbs.on_changedtree then - table.insert(self._callbacks.changedtree, cbs.on_changedtree) - end - - if cbs.on_bytes then - table.insert(self._callbacks.bytes, cbs.on_bytes) - end + local callbacks = recursive and self._callbacks_rec or self._callbacks - if cbs.on_detach then - table.insert(self._callbacks.detach, cbs.on_detach) - end - - if cbs.on_child_added then - table.insert(self._callbacks.child_added, cbs.on_child_added) + for name, cbname in pairs(TSCallbackNames) do + if cbs[name] then + table.insert(callbacks[cbname], cbs[name]) + end end - if cbs.on_child_removed then - table.insert(self._callbacks.child_removed, cbs.on_child_removed) + if recursive then + self:for_each_child(function(child) + child:register_cbs(cbs, true) + end) end end -- cgit From edf05b005f34f59dd40468c36cc139e217345a71 Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Mon, 1 May 2023 00:15:32 -0500 Subject: perf(lsp): process semantic tokens response in a coroutine that yields every 5ms (#23375) --- runtime/lua/vim/lsp/semantic_tokens.lua | 45 +++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 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 03532c33b7..b6b09c58b1 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -1,7 +1,8 @@ local api = vim.api +local bit = require('bit') local handlers = require('vim.lsp.handlers') local util = require('vim.lsp.util') -local bit = require('bit') +local uv = vim.loop --- @class STTokenRange --- @field line integer line number 0-based @@ -94,15 +95,38 @@ end --- ---@private ---@return STTokenRange[] -local function tokens_to_ranges(data, bufnr, client) +local function tokens_to_ranges(data, bufnr, client, request) local legend = client.server_capabilities.semanticTokensProvider.legend local token_types = legend.tokenTypes local token_modifiers = legend.tokenModifiers local ranges = {} + local start = uv.hrtime() + local ms_to_ns = 1000 * 1000 + local yield_interval_ns = 5 * ms_to_ns + local co, is_main = coroutine.running() + local line local start_char = 0 for i = 1, #data, 5 do + -- if this function is called from the main coroutine, let it run to completion with no yield + if not is_main then + local elapsed_ns = uv.hrtime() - start + + if elapsed_ns > yield_interval_ns then + vim.schedule(function() + coroutine.resume(co, util.buf_versions[bufnr]) + end) + if request.version ~= coroutine.yield() then + -- request became stale since the last time the coroutine ran. + -- abandon it by yielding without a way to resume + coroutine.yield() + end + + start = uv.hrtime() + end + end + local delta_line = data[i] line = line and line + delta_line or delta_line local delta_start = data[i + 1] @@ -280,7 +304,7 @@ function STHighlighter:send_request() local c = vim.lsp.get_client_by_id(ctx.client_id) local highlighter = STHighlighter.active[ctx.bufnr] if not err and c and highlighter then - highlighter:process_response(response, c, version) + coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version) end end, self.bufnr) @@ -315,11 +339,9 @@ function STHighlighter:process_response(response, client, version) return end - -- reset active request - state.active_request = {} - -- skip nil responses if response == nil then + state.active_request = {} return end @@ -347,12 +369,19 @@ function STHighlighter:process_response(response, client, version) tokens = response.data end - -- Update the state with the new results + -- convert token list to highlight ranges + -- this could yield and run over multiple event loop iterations + local highlights = tokens_to_ranges(tokens, self.bufnr, client, state.active_request) + + -- reset active request + state.active_request = {} + + -- update the state with the new results local current_result = state.current_result current_result.version = version current_result.result_id = response.resultId current_result.tokens = tokens - current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client) + current_result.highlights = highlights current_result.namespace_cleared = false -- redraw all windows displaying buffer -- cgit From 37dd818722c50a5dbe0771a88a1cdaf98d9596b5 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 1 May 2023 10:55:51 +0200 Subject: vim-patch:9.0.1503: Luau files are not recognized (#23412) Problem: Luau files are not recognized. Solution: Add a patter for Luau files. (Amaan Qureshi, closes vim/vim#12317) https://github.com/vim/vim/commit/2dcfe9ae1df61e1249520ed435dd8cf60e157103 Co-authored-by: Amaan Qureshi --- 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 4fafc4e2e2..5c799b23f2 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', + luau = 'luau', lrc = 'lyrics', m = function(path, bufnr) return require('vim.filetype.detect').m(bufnr) -- cgit From 26cc946226d96bf6b474d850b961e1060346c96f Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 1 May 2023 10:32:29 +0100 Subject: fix(treesitter): foldexpr tweaks Some small general fixes found working on developing async parsing. --- runtime/lua/vim/treesitter/_fold.lua | 40 ++++++++++++++++++++++++----- runtime/lua/vim/treesitter/languagetree.lua | 2 +- 2 files changed, 34 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7df93d1b2e..51e60bf495 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -1,3 +1,5 @@ +local ts = vim.treesitter + local Range = require('vim.treesitter._range') local api = vim.api @@ -90,21 +92,45 @@ local function trim_level(level) return level 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? +--- @return integer +local function normalise_erow(bufnr, erow) + local max_erow = api.nvim_buf_line_count(bufnr) - 1 + return math.min(erow or max_erow, max_erow) +end + ---@param bufnr integer ---@param info TS.FoldInfo ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) + if not api.nvim_buf_is_valid(bufnr) then + return false + end + srow = srow or 0 - erow = erow or api.nvim_buf_line_count(bufnr) + erow = normalise_erow(bufnr, erow) info:invalidate_range(srow, erow) local prev_start = -1 local prev_stop = -1 - vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree) - local query = vim.treesitter.query.get(ltree:lang(), 'folds') + local parser = ts.get_parser(bufnr) + + if not parser:is_valid() then + return + end + + parser:for_each_tree(function(tree, ltree) + local query = ts.query.get(ltree:lang(), 'folds') if not query then return end @@ -112,9 +138,9 @@ local function get_folds_levels(bufnr, info, srow, erow) -- erow in query is end-exclusive local q_erow = erow and erow + 1 or -1 - for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do + for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do if query.captures[id] == 'fold' then - local range = vim.treesitter.get_range(node, bufnr, metadata[id]) + local range = ts.get_range(node, bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) if stop_col == 0 then @@ -226,7 +252,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - if not vim.treesitter._has_parser(bufnr) or not lnum then + if not ts._has_parser(bufnr) or not lnum then return '0' end @@ -234,7 +260,7 @@ function M.foldexpr(lnum) foldinfos[bufnr] = FoldInfo.new() get_folds_levels(bufnr, foldinfos[bufnr]) - local parser = vim.treesitter.get_parser(bufnr) + local parser = ts.get_parser(bufnr) parser:register_cbs({ on_changedtree = function(tree_changes) on_changedtree(bufnr, foldinfos[bufnr], tree_changes) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 19cea32367..1adf6759fa 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -262,7 +262,7 @@ function LanguageTree:parse() tcall(self._parser.parse, self._parser, self._trees[i], self._source, true) -- Pass ranges if this is an initial parse - local cb_changes = self._trees[i] and tree_changes or ranges + local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true) self:_do_callback('changedtree', cb_changes, tree) self._trees[i] = tree -- cgit From fba18a3b62310f4535d979a05288101b9af2ef50 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 2 May 2023 10:07:18 +0100 Subject: fix(treesitter): do not calc folds on unloaded buffers Fixes #23423 --- runtime/lua/vim/treesitter/_fold.lua | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 51e60bf495..edceb8217a 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -111,10 +111,6 @@ end ---@param srow integer? ---@param erow integer? local function get_folds_levels(bufnr, info, srow, erow) - if not api.nvim_buf_is_valid(bufnr) then - return false - end - srow = srow or 0 erow = normalise_erow(bufnr, erow) @@ -210,13 +206,25 @@ local function recompute_folds() vim._foldupdate() end +--- Schedule a function only if bufnr is loaded +---@param bufnr integer +---@param fn function +local function schedule_if_loaded(bufnr, fn) + vim.schedule(function() + if not api.nvim_buf_is_loaded(bufnr) then + return + end + fn() + end) +end + ---@param bufnr integer ---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) -- For some reason, queries seem to use the old buffer state in on_bytes. -- Get around this by scheduling and manually updating folds. - vim.schedule(function() + schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) @@ -238,7 +246,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) foldinfo:remove_range(end_row_new, end_row_old) elseif new_row > old_row then foldinfo:add_range(start_row, end_row_new) - vim.schedule(function() + schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) recompute_folds() end) -- cgit From 3ba930844c302dc43d32a30ed453667409596c4a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 2 May 2023 22:27:14 +0100 Subject: perf(treesitter): insert/remove items efficiently (#23443) --- runtime/lua/vim/treesitter/_fold.lua | 63 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index edceb8217a..f6425d7cb9 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -34,15 +34,58 @@ function FoldInfo:invalidate_range(srow, erow) end end +--- Efficiently remove items from middle of a list a list. +--- +--- Calling table.remove() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +local function list_remove(t, first, last) + local n = #t + for i = 0, n - first do + t[first + i] = t[last + 1 + i] + t[last + 1 + i] = nil + end +end + ---@package ---@param srow integer ---@param erow integer function FoldInfo:remove_range(srow, erow) - for i = erow - 1, srow, -1 do - table.remove(self.levels, i + 1) - table.remove(self.levels0, i + 1) - table.remove(self.start_counts, i + 1) - table.remove(self.stop_counts, i + 1) + list_remove(self.levels, srow + 1, erow) + list_remove(self.levels0, srow + 1, erow) + list_remove(self.start_counts, srow + 1, erow) + list_remove(self.stop_counts, srow + 1, erow) +end + +--- Efficiently insert items into the middle of a list. +--- +--- Calling table.insert() in a loop will re-index the tail of the table on +--- every iteration, instead this function will re-index the table exactly +--- once. +--- +--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524 +--- +---@param t any[] +---@param first integer +---@param last integer +---@param v any +local function list_insert(t, first, last, v) + local n = #t + + -- Shift table forward + for i = n - first, 0, -1 do + t[last + 1 + i] = t[first + i] + end + + -- Fill in new values + for i = first, last do + t[i] = v end end @@ -50,12 +93,10 @@ end ---@param srow integer ---@param erow integer function FoldInfo:add_range(srow, erow) - for i = srow, erow - 1 do - table.insert(self.levels, i + 1, '-1') - table.insert(self.levels0, i + 1, -1) - table.insert(self.start_counts, i + 1, nil) - table.insert(self.stop_counts, i + 1, nil) - end + list_insert(self.levels, srow + 1, erow, '-1') + list_insert(self.levels0, srow + 1, erow, -1) + list_insert(self.start_counts, srow + 1, erow, nil) + list_insert(self.stop_counts, srow + 1, erow, nil) end ---@package -- cgit From 648f777931d49b8013146f69d7e2776f69c52900 Mon Sep 17 00:00:00 2001 From: jdrouhard Date: Fri, 5 May 2023 00:41:36 -0500 Subject: perf(lsp): load buffer contents once when processing semantic tokens responses (#23484) perf(lsp): load buffer contents once when processing semantic token responses Using _get_line_byte_from_position() for each token's boundaries was a pretty huge bottleneck, since that function would load individual buffer lines via nvim_buf_get_lines() (plus a lot of extra overhead). So each token caused two calls to nvim_buf_get_lines() (once for the start position, and once for the end position). For semantic tokens, we only attach to buffers that have already been loaded, so we can safely just get all the lines for the entire buffer at once, and lift the rest of the _get_line_byte_from_position() implementation directly while bypassing the part that loads the buffer line. While I was looking at get_lines (used by _get_line_byte_from_position), I noticed that we were checking for non-file URIs before we even looked to see if we already had the buffer loaded. Moving the buffer-loaded check to be the first thing done in get_lines() more than halved the average time spent transforming the token list into highlight ranges vs when it was still using _get_line_byte_from_position. I ended up improving that loop more by not using get_lines, but figured the performance improvement it provided was worth leaving in. --- runtime/lua/vim/lsp/semantic_tokens.lua | 17 ++++++++++++----- runtime/lua/vim/lsp/util.lua | 12 ++++++------ 2 files changed, 18 insertions(+), 11 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 b6b09c58b1..376cac19a7 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -99,6 +99,7 @@ local function tokens_to_ranges(data, bufnr, client, request) local legend = client.server_capabilities.semanticTokensProvider.legend local token_types = legend.tokenTypes local token_modifiers = legend.tokenModifiers + local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) local ranges = {} local start = uv.hrtime() @@ -137,11 +138,17 @@ local function tokens_to_ranges(data, bufnr, client, request) local modifiers = modifiers_from_number(data[i + 4], token_modifiers) ---@private - local function _get_byte_pos(char_pos) - return util._get_line_byte_from_position(bufnr, { - line = line, - character = char_pos, - }, client.offset_encoding) + local function _get_byte_pos(col) + if col > 0 then + local buf_line = lines[line + 1] or '' + local ok, result + ok, result = pcall(util._str_byteindex_enc, buf_line, col, client.offset_encoding) + if ok then + return result + end + return math.min(#buf_line, col) + end + return col end local start_col = _get_byte_pos(start_char) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 31af2afb0b..8274361f6d 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -253,12 +253,17 @@ local function get_lines(bufnr, rows) ---@private local function buf_lines() local lines = {} - for _, row in pairs(rows) do + for _, row in ipairs(rows) do lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1] end return lines end + -- use loaded buffers if available + if vim.fn.bufloaded(bufnr) == 1 then + return buf_lines() + end + local uri = vim.uri_from_bufnr(bufnr) -- load the buffer if this is not a file uri @@ -268,11 +273,6 @@ local function get_lines(bufnr, rows) return buf_lines() end - -- use loaded buffers if available - if vim.fn.bufloaded(bufnr) == 1 then - return buf_lines() - end - local filename = api.nvim_buf_get_name(bufnr) -- get the data from the file -- cgit From 9248dd77ac58bd23721dc4e156e43ed5e9ada338 Mon Sep 17 00:00:00 2001 From: marcoSven Date: Sat, 6 May 2023 21:53:36 +0200 Subject: feat(lua): add hl priority opts on yank (#23509) feat(lua): add hl priority opts on_yank Signed-off-by: marcoSven --- runtime/lua/vim/highlight.lua | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index a6cfcb730f..86e1adb49e 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -61,6 +61,7 @@ local yank_timer -- - on_macro highlight when executing macro (default false) -- - on_visual highlight when yanking visual selection (default true) -- - event event structure (default vim.v.event) +-- - priority integer priority (default |vim.highlight.priorities|`.user`) function M.on_yank(opts) vim.validate({ opts = { @@ -99,14 +100,11 @@ function M.on_yank(opts) yank_timer:close() end - M.range( - bufnr, - yank_ns, - higroup, - "'[", - "']", - { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user } - ) + M.range(bufnr, yank_ns, higroup, "'[", "']", { + regtype = event.regtype, + inclusive = event.inclusive, + priority = opts.priority or M.priorities.user, + }) yank_timer = vim.defer_fn(function() yank_timer = nil -- cgit From 02f92978fe4f4d465260a9936dca1526bbe76931 Mon Sep 17 00:00:00 2001 From: hituzi no sippo <43565959+hituzi-no-sippo@users.noreply.github.com> Date: Wed, 10 May 2023 00:24:49 +0900 Subject: docs(lsp): fix type of `config.cmd` argument for `vim.lsp.start_client` (#23550) --- 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 5c78bd7580..b86d41b234 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -901,7 +901,7 @@ end --- Field `cmd` in {config} is required. --- ---@param config (table) Configuration for the server: ---- - cmd: (table|string|fun(dispatchers: table):table) command string or +--- - cmd: (string[]|fun(dispatchers: table):table) command string or --- list treated like |jobstart()|. The command must launch the language server --- process. `cmd` can also be a function that creates an RPC client. --- The function receives a dispatchers table and must return a table with the -- cgit From 075a72d5ff9d49b1e93c0253b54931ecdcf673f3 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Tue, 9 May 2023 11:12:54 -0500 Subject: fix(lsp): fix relative patterns for `workspace/didChangeWatchedFiles` (#23548) --- runtime/lua/vim/lsp/_watchfiles.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 533a955925..3a78724d79 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -198,16 +198,17 @@ function M.register(reg, ctx) end local watch_regs = {} for _, w in ipairs(reg.registerOptions.watchers) do + local relative_pattern = false local glob_patterns = {} if type(w.globPattern) == 'string' then for _, folder in ipairs(client.workspace_folders) do table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern }) end else + relative_pattern = true table.insert(glob_patterns, w.globPattern) end for _, glob_pattern in ipairs(glob_patterns) do - local pattern = parse(glob_pattern.pattern) local base_dir = nil if type(glob_pattern.baseUri) == 'string' then base_dir = glob_pattern.baseUri @@ -216,9 +217,16 @@ function M.register(reg, ctx) end assert(base_dir, "couldn't identify root of watch") base_dir = vim.uri_to_fname(base_dir) + local kind = w.kind or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + local pattern = glob_pattern.pattern + if relative_pattern then + pattern = base_dir .. '/' .. pattern + end + pattern = parse(pattern) + table.insert(watch_regs, { base_dir = base_dir, pattern = pattern, -- cgit From 4e5061dba765df2a74ac4a8182f6e7fe21da125d Mon Sep 17 00:00:00 2001 From: hituzi no sippo <43565959+hituzi-no-sippo@users.noreply.github.com> Date: Wed, 10 May 2023 04:00:29 +0900 Subject: docs(lsp): fix `config.cmd` argument for `vim.lsp.start_client` (#23560) --- runtime/lua/vim/lsp.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index b86d41b234..a724593188 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -901,8 +901,8 @@ end --- Field `cmd` in {config} is required. --- ---@param config (table) Configuration for the server: ---- - cmd: (string[]|fun(dispatchers: table):table) command string or ---- list treated like |jobstart()|. The command must launch the language server +--- - cmd: (string[]|fun(dispatchers: table):table) command a list of +--- strings treated like |jobstart()|. The command must launch the language server --- process. `cmd` can also be a function that creates an RPC client. --- The function receives a dispatchers table and must return a table with the --- functions `request`, `notify`, `is_closing` and `terminate` -- cgit From e90b506903babc84f8c5b796c3b25c7f20e183d2 Mon Sep 17 00:00:00 2001 From: Gaétan Lepage <33058747+GaetanLepage@users.noreply.github.com> Date: Thu, 11 May 2023 09:43:02 +0200 Subject: vim-patch:9.0.1539: typst filetype is not recognized (#23578) Problem: Typst filetype is not recognized. Solution: Distinguish between sql and typst. (Gaetan Lepage, closes vim/vim#12363) https://github.com/vim/vim/commit/4ce1bda869e4ec0152d7dcbe1e491ceac5341d5e --- runtime/lua/vim/filetype.lua | 4 +++- runtime/lua/vim/filetype/detect.lua | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5c799b23f2..c746eef1bb 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -997,7 +997,9 @@ local extension = { spi = 'spyce', spy = 'spyce', tyc = 'sql', - typ = 'sql', + typ = function(path, bufnr) + return require('vim.filetype.detect').typ(bufnr) + end, pkb = 'sql', tyb = 'sql', pks = 'sql', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 74b01d569c..94106a3547 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1322,6 +1322,28 @@ function M.txt(bufnr) end end +function M.typ(bufnr) + if vim.g.filetype_typ then + return vim.g.filetype_typ + end + + for _, line in ipairs(getlines(bufnr, 1, 200)) do + if + findany(line, { + '^CASE[%s]?=[%s]?SAME$', + '^CASE[%s]?=[%s]?LOWER$', + '^CASE[%s]?=[%s]?UPPER$', + '^CASE[%s]?=[%s]?OPPOSITE$', + '^TYPE%s', + }) + then + return 'sql' + end + end + + return 'typst' +end + -- Determine if a .v file is Verilog, V, or Coq function M.v(bufnr) if vim.fn.did_filetype() ~= 0 then -- cgit From af040c3a079f6e25db0ad6b908aa1327f67deb82 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 11 May 2023 11:13:32 +0100 Subject: feat(treesitter): add support for setting query depths --- runtime/lua/vim/treesitter/_meta.lua | 14 ++++++++------ runtime/lua/vim/treesitter/query.lua | 8 ++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 4d0f43d030..c1009f5f5d 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -31,17 +31,19 @@ local TSNode = {} ---@param query userdata ---@param captures true ----@param start integer ----@param end_ integer +---@param start? integer +---@param end_? integer +---@param opts? table ---@return fun(): integer, TSNode, any -function TSNode:_rawquery(query, captures, start, end_) end +function TSNode:_rawquery(query, captures, start, end_, opts) end ---@param query userdata ---@param captures false ----@param start integer ----@param end_ integer +---@param start? integer +---@param end_? integer +---@param opts? table ---@return fun(): string, any -function TSNode:_rawquery(query, captures, start, end_) end +function TSNode:_rawquery(query, captures, start, end_, opts) end ---@class TSParser ---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[] diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 93841bb31e..e6a117557a 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -686,16 +686,20 @@ end ---@param source (integer|string) Source buffer or string to search ---@param start integer Starting line for the search ---@param stop integer Stopping line for the search (end-exclusive) +---@param opts table|nil Options: +--- - 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. +--- Requires treesitter >= 0.20.9. --- ---@return (fun(): integer, table, table): pattern id, match, metadata -function Query:iter_matches(node, source, start, stop) +function Query:iter_matches(node, source, start, stop, opts) 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) + local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@cast raw_iter fun(): string, any local function iter() local pattern, match = raw_iter() -- cgit From 512a90520e3287b9962ed7853d1f1021b564a133 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 13 May 2023 17:27:05 +0800 Subject: refactor(lsp): mark server_ready function as deprecated (#23520) --- runtime/lua/vim/lsp/buf.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 3d9011656f..a307dea673 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -31,7 +31,9 @@ end --- ready. --- ---@returns `true` if server responds. +---@deprecated function M.server_ready() + vim.deprecate('vim.lsp.buf.server_ready', nil, '0.10.0') return not not vim.lsp.buf_notify(0, 'window/progress', {}) end -- cgit From 32dc484ec9ec2d86a5fc7127e37f1ef115b9be76 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 May 2023 13:29:11 +0200 Subject: fix(treesitter): support subfiletypes in get_lang (#23605) --- runtime/lua/vim/treesitter/language.lua | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index b616d4d70b..08c297c9ad 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -27,6 +27,11 @@ function M.get_lang(filetype) if filetype == '' then return end + if ft_to_lang[filetype] then + return ft_to_lang[filetype] + end + -- support subfiletypes like html.glimmer + filetype = vim.split(filetype, '.', { plain = true })[1] return ft_to_lang[filetype] end -- cgit From 6f29c68928c3f20d9a1b1a7a311cfac65aa1b4e6 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 13 May 2023 15:00:08 +0200 Subject: vim-patch:9.0.1549: USD filetype is not recognized (#23608) Problem: USD filetype is not recognized. Solution: Add patterns for USD filetype. (Colin Kennedy, closes vim/vim#12370) https://github.com/vim/vim/commit/b848ce6b7e27f24aff47a4d63933e0f96663acfe Co-authored-by: Colin Kennedy --- 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 c746eef1bb..5075c18976 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1080,6 +1080,8 @@ local extension = { uit = 'uil', uil = 'uil', ungram = 'ungrammar', + usd = 'usd', + usda = 'usd', sba = 'vb', vb = 'vb', dsm = 'vb', -- cgit From 08991b078267e5de0a19a136d00d4f71ad651a32 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Sat, 13 May 2023 21:33:22 +0200 Subject: docs: small fixes Co-authored-by: Christian Clason Co-authored-by: Gregory Anders Co-authored-by: HiPhish Co-authored-by: Julio B Co-authored-by: T727 <74924917+T-727@users.noreply.github.com> Co-authored-by: camoz Co-authored-by: champignoom <66909116+champignoom@users.noreply.github.com> --- runtime/lua/vim/_editor.lua | 2 +- runtime/lua/vim/_inspector.lua | 2 +- runtime/lua/vim/lsp.lua | 2 +- runtime/lua/vim/shared.lua | 8 ++++---- runtime/lua/vim/treesitter/languagetree.lua | 6 ++++-- runtime/lua/vim/treesitter/query.lua | 4 ++-- runtime/lua/vim/version.lua | 2 +- 7 files changed, 14 insertions(+), 12 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 20e813d77c..b26def5958 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -778,7 +778,7 @@ do -- some bugs, so fake the two-step dance for now. local matches - --- Omnifunc for completing lua values from from the runtime lua interpreter, + --- Omnifunc for completing lua values from the runtime lua interpreter, --- similar to the builtin completion for the `:lua` command. --- --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a lua buffer. diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index 2ebb7a7efd..ecd39c35bc 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -90,7 +90,7 @@ function vim.inspect_pos(bufnr, row, col, filter) nsmap[id] = name end - --- Convert an extmark tuple into a map-like table + --- Convert an extmark tuple into a table --- @private local function to_map(extmark) extmark = { diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a724593188..92f5653158 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -913,7 +913,7 @@ end --- the `cmd` process. Not related to `root_dir`. --- --- - cmd_env: (table) Environment flags to pass to the LSP on ---- spawn. Must be specified using a map-like table. +--- spawn. Must be specified using a table. --- Non-string values are coerced to string. --- Example: ---
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index a55deb1415..4f230c4412 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -361,7 +361,7 @@ local function tbl_extend(behavior, deep_extend, ...)
   return ret
 end
 
---- Merges two or more map-like tables.
+--- Merges two or more tables.
 ---
 ---@see |extend()|
 ---
@@ -369,13 +369,13 @@ end
 ---      - "error": raise an error
 ---      - "keep":  use value from the leftmost map
 ---      - "force": use value from the rightmost map
----@param ... table Two or more map-like tables
+---@param ... table Two or more tables
 ---@return table Merged table
 function vim.tbl_extend(behavior, ...)
   return tbl_extend(behavior, false, ...)
 end
 
---- Merges recursively two or more map-like tables.
+--- Merges recursively two or more tables.
 ---
 ---@see |vim.tbl_extend()|
 ---
@@ -385,7 +385,7 @@ end
 ---      - "error": raise an error
 ---      - "keep":  use value from the leftmost map
 ---      - "force": use value from the rightmost map
----@param ... T2 Two or more map-like tables
+---@param ... T2 Two or more tables
 ---@return T1|T2 (table) Merged table
 function vim.tbl_deep_extend(behavior, ...)
   return tbl_extend(behavior, true, ...)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 1adf6759fa..0efe3af85c 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -882,10 +882,12 @@ end
 ---@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 only be passed one argument, which is a table of the ranges (as node ranges) that
----              changed.
+---              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|.
+---              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)
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index e6a117557a..75e5bf8870 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -609,10 +609,10 @@ end
 ---
 --- {source} is needed if the query contains predicates; then the caller
 --- must ensure to use a freshly parsed tree consistent with the current
---- text of the buffer (if relevant). {start_row} and {end_row} can be used to limit
+--- text of the buffer (if relevant). {start} and {stop} can be used to limit
 --- matches inside a row range (this is typically used with root node
 --- as the {node}, i.e., to get syntax highlight matches in the current
---- viewport). When omitted, the {start} and {end} row values are used from the given node.
+--- 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.
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index 3aacf3d4e0..ebe8f4e053 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -333,7 +333,7 @@ local function create_err_msg(v)
   return string.format('invalid version: %s (%s)', tostring(v), type(v))
 end
 
---- Parses and compares two version version objects (the result of |vim.version.parse()|, or
+--- Parses and compares two version objects (the result of |vim.version.parse()|, or
 --- specified literally as a `{major, minor, patch}` tuple, e.g. `{1, 0, 3}`).
 ---
 --- Example:
-- 
cgit 


From 9ff59517cbf309d31f979a49b7dc82b237ecfcc4 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 13 May 2023 12:56:21 +0200
Subject: fix(treesitter): update c queries

---
 runtime/lua/vim/treesitter/query.lua | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 75e5bf8870..73b561c777 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -382,6 +382,39 @@ local predicate_handlers = {
 
     return string_set[node_text]
   end,
+
+  ['has-ancestor?'] = function(match, _, _, predicate)
+    local node = match[predicate[2]]
+    if not node then
+      return true
+    end
+
+    local ancestor_types = {}
+    for _, type in ipairs({ unpack(predicate, 3) }) do
+      ancestor_types[type] = true
+    end
+
+    node = node:parent()
+    while node do
+      if ancestor_types[node:type()] then
+        return true
+      end
+      node = node:parent()
+    end
+    return false
+  end,
+
+  ['has-parent?'] = function(match, _, _, predicate)
+    local node = match[predicate[2]]
+    if not node then
+      return true
+    end
+
+    if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then
+      return true
+    end
+    return false
+  end,
 }
 
 -- As we provide lua-match? also expose vim-match?
-- 
cgit 


From 6b19170d44ca56cf65542ee184d2bc89c6d622a9 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Tue, 16 May 2023 16:41:47 +0100
Subject: fix(treesitter): correctly calculate bytes for text sources (#23655)

Fixes #20419
---
 runtime/lua/vim/treesitter/_range.lua | 38 ++++++++++++++++++++++++-----------
 1 file changed, 26 insertions(+), 12 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua
index f4db5016ac..35081c6400 100644
--- a/runtime/lua/vim/treesitter/_range.lua
+++ b/runtime/lua/vim/treesitter/_range.lua
@@ -143,6 +143,29 @@ function M.contains(r1, r2)
   return true
 end
 
+--- @param source integer|string
+--- @param index integer
+--- @return integer
+local function get_offset(source, index)
+  if index == 0 then
+    return 0
+  end
+
+  if type(source) == 'number' then
+    return api.nvim_buf_get_offset(source, index)
+  end
+
+  local byte = 0
+  local next_offset = source:gmatch('()\n')
+  local line = 1
+  while line <= index do
+    byte = next_offset() --[[@as integer]]
+    line = line + 1
+  end
+
+  return byte
+end
+
 ---@private
 ---@param source integer|string
 ---@param range Range
@@ -152,19 +175,10 @@ function M.add_bytes(source, range)
     return range --[[@as Range6]]
   end
 
-  local start_row, start_col, end_row, end_col = range[1], range[2], range[3], range[4]
-  local start_byte = 0
-  local end_byte = 0
+  local start_row, start_col, end_row, end_col = M.unpack4(range)
   -- TODO(vigoux): proper byte computation here, and account for EOL ?
-  if type(source) == 'number' then
-    -- Easy case, this is a buffer parser
-    start_byte = api.nvim_buf_get_offset(source, start_row) + start_col
-    end_byte = api.nvim_buf_get_offset(source, end_row) + end_col
-  elseif type(source) == 'string' then
-    -- string parser, single `\n` delimited string
-    start_byte = vim.fn.byteidx(source, start_col)
-    end_byte = vim.fn.byteidx(source, end_col)
-  end
+  local start_byte = get_offset(source, start_row) + start_col
+  local end_byte = get_offset(source, end_row) + end_col
 
   return { start_row, start_col, start_byte, end_row, end_col, end_byte }
 end
-- 
cgit 


From 189fb6203262340e7a59e782be970bcd8ae28e61 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Wed, 17 May 2023 11:42:18 +0100
Subject: feat(treesitter): improved logging (#23638)

- Add bindings to Treesitter ts_parser_set_logger and ts_parser_logger
- Add logfile with path STDPATH('log')/treesitter.c
- Rework existing LanguageTree loggin to use logfile
- Begin implementing log levels for vim.g.__ts_debug
---
 runtime/lua/vim/fs.lua                      |  6 ++-
 runtime/lua/vim/treesitter/_meta.lua        |  4 ++
 runtime/lua/vim/treesitter/languagetree.lua | 67 +++++++++++++++++++++++++----
 3 files changed, 67 insertions(+), 10 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 2a51bde263..7f9f3a2bce 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -73,10 +73,14 @@ function M.basename(file)
 end
 
 ---@private
-local function join_paths(...)
+---@param ... string
+---@return string
+function M._join_paths(...)
   return (table.concat({ ... }, '/'):gsub('//+', '/'))
 end
 
+local join_paths = M._join_paths
+
 ---@alias Iterator fun(): string?, string?
 
 --- Return an iterator over the files and directories located in {path}
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index c1009f5f5d..9ca4b560c6 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -45,6 +45,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
 ---@return fun(): string, any
 function TSNode:_rawquery(query, captures, start, end_, opts) end
 
+---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
+
 ---@class TSParser
 ---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean?): TSTree, integer[]
 ---@field reset fun(self: TSParser)
@@ -52,6 +54,8 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
 ---@field set_included_ranges fun(self: TSParser, ranges: Range6[])
 ---@field set_timeout fun(self: TSParser, timeout: integer)
 ---@field timeout fun(self: TSParser): integer
+---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
+---@field _logger fun(self: TSParser): TSLoggerCallback
 
 ---@class TSTree
 ---@field root fun(self: TSTree): TSNode
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 0efe3af85c..6c780f33c4 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -31,8 +31,17 @@
 --- shouldn't be done directly in the change callback anyway as they will be very frequent. Rather
 --- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent
 --- updates.
+---
+
+-- Debugging:
+--
+-- vim.g.__ts_debug levels:
+--    - 1. Messages from languagetree.lua
+--    - 2. Parse messages from treesitter
+--    - 2. Lex messages from treesitter
+--
+-- Log file can be found in stdpath('log')/treesitter.log
 
-local api = vim.api
 local query = require('vim.treesitter.query')
 local language = require('vim.treesitter.language')
 local Range = require('vim.treesitter._range')
@@ -75,6 +84,8 @@ local TSCallbackNames = {
 ---@field private _source (integer|string) Buffer or string to parse
 ---@field private _trees TSTree[] Reference to parsed tree (one for each language)
 ---@field private _valid boolean|table If the parsed tree is valid
+---@field private _logger? fun(logtype: string, msg: string)
+---@field private _logfile? file*
 local LanguageTree = {}
 
 ---@class LanguageTreeOpts
@@ -114,6 +125,10 @@ function LanguageTree.new(source, lang, opts)
     _callbacks_rec = {},
   }, LanguageTree)
 
+  if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then
+    self:_set_logger()
+  end
+
   for _, name in pairs(TSCallbackNames) do
     self._callbacks[name] = {}
     self._callbacks_rec[name] = {}
@@ -122,6 +137,33 @@ function LanguageTree.new(source, lang, opts)
   return self
 end
 
+function LanguageTree:_set_logger()
+  local source = self:source()
+  source = type(source) == 'string' and 'text' or tostring(source)
+
+  local lang = self:lang()
+
+  local logfilename = vim.fs._join_paths(vim.fn.stdpath('log'), 'treesitter.log')
+
+  local logfile, openerr = io.open(logfilename, 'a+')
+
+  if not logfile or openerr then
+    error(string.format('Could not open file (%s) for logging: %s', logfilename, openerr))
+    return
+  end
+
+  self._logfile = logfile
+
+  self._logger = function(logtype, msg)
+    self._logfile:write(string.format('%s:%s:(%s) %s\n', source, lang, logtype, msg))
+    self._logfile:flush()
+  end
+
+  local log_lex = vim.g.__ts_debug >= 3
+  local log_parse = vim.g.__ts_debug >= 2
+  self._parser:_set_logger(log_lex, log_parse, self._logger)
+end
+
 ---@private
 ---Measure execution time of a function
 ---@generic R1, R2, R3
@@ -139,7 +181,11 @@ end
 ---@private
 ---@vararg any
 function LanguageTree:_log(...)
-  if vim.g.__ts_debug == nil then
+  if not self._logger then
+    return
+  end
+
+  if not vim.g.__ts_debug or vim.g.__ts_debug < 1 then
     return
   end
 
@@ -150,19 +196,17 @@ function LanguageTree:_log(...)
 
   local info = debug.getinfo(2, 'nl')
   local nregions = #self:included_regions()
-  local prefix =
-    string.format('%s:%d: [%s:%d] ', info.name, info.currentline, self:lang(), nregions)
+  local prefix = string.format('%s:%d: (#regions=%d) ', info.name, info.currentline, nregions)
 
-  api.nvim_out_write(prefix)
+  local msg = { prefix }
   for _, x in ipairs(args) do
     if type(x) == 'string' then
-      api.nvim_out_write(x)
+      msg[#msg + 1] = x
     else
-      api.nvim_out_write(vim.inspect(x, { newline = ' ', indent = '' }))
+      msg[#msg + 1] = vim.inspect(x, { newline = ' ', indent = '' })
     end
-    api.nvim_out_write(' ')
   end
-  api.nvim_out_write('\n')
+  self._logger('nvim', table.concat(msg, ' '))
 end
 
 --- Invalidates this parser and all its children
@@ -876,6 +920,11 @@ end
 function LanguageTree:_on_detach(...)
   self:invalidate(true)
   self:_do_callback('detach', ...)
+  if self._logfile then
+    self._logger('nvim', 'detaching')
+    self._logger = nil
+    self._logfile:close()
+  end
 end
 
 --- Registers callbacks for the |LanguageTree|.
-- 
cgit 


From ef64e225f6f6c01280aa8472bebe812016f357bf Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Thu, 18 May 2023 10:52:01 +0100
Subject: fix(treesitter): allow foldexpr without highlights (#23672)

Ref nvim-treesitter/nvim-treesitter#4748
---
 runtime/lua/vim/treesitter.lua       | 10 ----------
 runtime/lua/vim/treesitter/_fold.lua |  4 ++--
 2 files changed, 2 insertions(+), 12 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index d1f5996768..12fbe1654f 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -136,16 +136,6 @@ function M.get_parser(bufnr, lang, opts)
   return parsers[bufnr]
 end
 
----@package
----@param bufnr (integer|nil) Buffer number
----@return boolean
-function M._has_parser(bufnr)
-  if bufnr == nil or bufnr == 0 then
-    bufnr = api.nvim_get_current_buf()
-  end
-  return parsers[bufnr] ~= nil
-end
-
 --- Returns a string parser
 ---
 ---@param str string Text to parse
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index f6425d7cb9..a8f8c7967e 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -301,7 +301,8 @@ function M.foldexpr(lnum)
   lnum = lnum or vim.v.lnum
   local bufnr = api.nvim_get_current_buf()
 
-  if not ts._has_parser(bufnr) or not lnum then
+  local parser = vim.F.npcall(ts.get_parser, bufnr)
+  if not parser then
     return '0'
   end
 
@@ -309,7 +310,6 @@ function M.foldexpr(lnum)
     foldinfos[bufnr] = FoldInfo.new()
     get_folds_levels(bufnr, foldinfos[bufnr])
 
-    local parser = ts.get_parser(bufnr)
     parser:register_cbs({
       on_changedtree = function(tree_changes)
         on_changedtree(bufnr, foldinfos[bufnr], tree_changes)
-- 
cgit 


From 201f366b370a9df724258b3c9bf01ffaadb8dd01 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Fri, 19 May 2023 09:11:22 +0200
Subject: vim-patch:9.0.1565: json lines files are not recognized (#23677)

Problem:    Json lines files are not recognized.
Solution:   Add a pattern to detect "jsonl" files. (issue vim/vim#7520)

https://github.com/vim/vim/commit/6fadbc1e8c1f4c5b03eb6a78aeb023ca2c2a9a7d

Co-authored-by: Bram Moolenaar 
---
 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 5075c18976..1b04666161 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -579,6 +579,7 @@ local extension = {
   ['json-patch'] = 'json',
   json5 = 'json5',
   jsonc = 'jsonc',
+  jsonl = 'jsonl',
   jsonnet = 'jsonnet',
   libsonnet = 'jsonnet',
   jsp = 'jsp',
-- 
cgit 


From 073035a030f5ef8ae9b9ca51552f887944c52eaa Mon Sep 17 00:00:00 2001
From: Jon Huhn 
Date: Sat, 20 May 2023 00:45:39 -0500
Subject: fix(lsp): don't register didChangeWatchedFiles when capability not
 set (#23689)

Some LSP servers (tailwindcss, rome) are known to request registration
for `workspace/didChangeWatchedFiles` even when the corresponding client
capability does not advertise support. This change adds an extra check
in the `client/registerCapability` handler not to start a watch unless
the client capability is set appropriately.
---
 runtime/lua/vim/lsp/_watchfiles.lua | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 3a78724d79..cf2c57db1f 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -193,7 +193,12 @@ local to_lsp_change_type = {
 function M.register(reg, ctx)
   local client_id = ctx.client_id
   local client = vim.lsp.get_client_by_id(client_id)
-  if not client.workspace_folders then
+  if
+    -- Ill-behaved servers may not honor the client capability and try to register
+    -- anyway, so ignore requests when the user has opted out of the feature.
+    not client.config.capabilities.workspace.didChangeWatchedFiles.dynamicRegistration
+    or not client.workspace_folders
+  then
     return
   end
   local watch_regs = {}
-- 
cgit 


From e3e6fadfd82861471c32fdcabe00bbef3de84563 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sat, 20 May 2023 17:30:48 +0200
Subject: feat(fs): expose join_paths as `vim.fs.joinpath` (#23685)

This is a small function but used a lot in some plugins.
---
 runtime/lua/vim/fs.lua | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 7f9f3a2bce..864ba495f1 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -72,15 +72,15 @@ function M.basename(file)
   return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/'))
 end
 
----@private
+--- Concatenate directories and/or file into a single path with normalization
+--- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+---
 ---@param ... string
 ---@return string
-function M._join_paths(...)
+function M.joinpath(...)
   return (table.concat({ ... }, '/'):gsub('//+', '/'))
 end
 
-local join_paths = M._join_paths
-
 ---@alias Iterator fun(): string?, string?
 
 --- Return an iterator over the files and directories located in {path}
@@ -120,14 +120,14 @@ function M.dir(path, opts)
     local dirs = { { path, 1 } }
     while #dirs > 0 do
       local dir0, level = unpack(table.remove(dirs, 1))
-      local dir = level == 1 and dir0 or join_paths(path, dir0)
+      local dir = level == 1 and dir0 or M.joinpath(path, dir0)
       local fs = vim.loop.fs_scandir(M.normalize(dir))
       while fs do
         local name, t = vim.loop.fs_scandir_next(fs)
         if not name then
           break
         end
-        local f = level == 1 and name or join_paths(dir0, name)
+        local f = level == 1 and name or M.joinpath(dir0, name)
         coroutine.yield(f, t)
         if
           opts.depth
@@ -234,7 +234,7 @@ function M.find(names, opts)
         local t = {}
         for name, type in M.dir(p) do
           if (not opts.type or opts.type == type) and names(name, p) then
-            table.insert(t, join_paths(p, name))
+            table.insert(t, M.joinpath(p, name))
           end
         end
         return t
@@ -243,7 +243,7 @@ function M.find(names, opts)
       test = function(p)
         local t = {}
         for _, name in ipairs(names) do
-          local f = join_paths(p, name)
+          local f = M.joinpath(p, name)
           local stat = vim.loop.fs_stat(f)
           if stat and (not opts.type or opts.type == stat.type) then
             t[#t + 1] = f
@@ -280,7 +280,7 @@ function M.find(names, opts)
       end
 
       for other, type_ in M.dir(dir) do
-        local f = join_paths(dir, other)
+        local f = M.joinpath(dir, other)
         if type(names) == 'function' then
           if (not opts.type or opts.type == type_) and names(other, dir) then
             if add(f) then
-- 
cgit 


From 1fe1bb084d0099fc4f9bfdc11189485d0f74b75a Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Mon, 19 Dec 2022 16:37:45 +0000
Subject: refactor(options): deprecate nvim[_buf|_win]_[gs]et_option

Co-authored-by: zeertzjq 
Co-authored-by: famiu 
---
 runtime/lua/vim/lsp.lua          | 16 ++++++----------
 runtime/lua/vim/lsp/handlers.lua |  2 +-
 runtime/lua/vim/lsp/util.lua     | 30 +++++++++++++-----------------
 3 files changed, 20 insertions(+), 28 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 92f5653158..2e6ca7a0ac 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -8,12 +8,8 @@ local sync = require('vim.lsp.sync')
 local semantic_tokens = require('vim.lsp.semantic_tokens')
 
 local api = vim.api
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
-  api.nvim_err_writeln,
-  api.nvim_buf_get_lines,
-  api.nvim_command,
-  api.nvim_buf_get_option,
-  api.nvim_exec_autocmds
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
+  api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
 local uv = vim.loop
 local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
 local validate = vim.validate
@@ -137,7 +133,7 @@ local format_line_ending = {
 ---@param bufnr (number)
 ---@return string
 local function buf_get_line_ending(bufnr)
-  return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n'
+  return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
 end
 
 local client_index = 0
@@ -319,7 +315,7 @@ end
 local function buf_get_full_text(bufnr)
   local line_ending = buf_get_line_ending(bufnr)
   local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
-  if nvim_buf_get_option(bufnr, 'eol') then
+  if vim.bo[bufnr].eol then
     text = text .. line_ending
   end
   return text
@@ -709,7 +705,7 @@ local function text_document_did_open_handler(bufnr, client)
   if not api.nvim_buf_is_loaded(bufnr) then
     return
   end
-  local filetype = nvim_buf_get_option(bufnr, 'filetype')
+  local filetype = vim.bo[bufnr].filetype
 
   local params = {
     textDocument = {
@@ -2177,7 +2173,7 @@ end
 ---
 --- Currently only supports a single client. This can be set via
 --- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach`
---- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``.
+--- via ``vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'``.
 ---
 ---@param opts table options for customizing the formatting expression which takes the
 ---                   following optional keys:
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 71bef43bc1..8e926c4644 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -454,7 +454,7 @@ function M.signature_help(_, result, ctx, config)
   local client = vim.lsp.get_client_by_id(ctx.client_id)
   local triggers =
     vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
-  local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
+  local ft = vim.bo[ctx.bufnr].filetype
   local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
   lines = util.trim_empty_lines(lines)
   if vim.tbl_isempty(lines) then
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 8274361f6d..9fffc845b1 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -401,7 +401,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
   if not api.nvim_buf_is_loaded(bufnr) then
     vim.fn.bufload(bufnr)
   end
-  api.nvim_buf_set_option(bufnr, 'buflisted', true)
+  vim.bo[bufnr].buflisted = true
 
   -- Fix reversed range and indexing each text_edits
   local index = 0
@@ -530,11 +530,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
 
   -- Remove final line if needed
   local fix_eol = has_eol_text_edit
-  fix_eol = fix_eol
-    and (
-      api.nvim_buf_get_option(bufnr, 'eol')
-      or (api.nvim_buf_get_option(bufnr, 'fixeol') and not api.nvim_buf_get_option(bufnr, 'binary'))
-    )
+  fix_eol = fix_eol and (vim.bo[bufnr].eol or (vim.bo[bufnr].fixeol and not vim.bo[bufnr].binary))
   fix_eol = fix_eol and get_line(bufnr, max - 1) == ''
   if fix_eol then
     api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
@@ -1076,7 +1072,7 @@ function M.make_floating_popup_options(width, height, opts)
 
   local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol()
 
-  if wincol + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then
+  if wincol + width + (opts.offset_x or 0) <= vim.o.columns then
     anchor = anchor .. 'W'
     col = 0
   else
@@ -1142,7 +1138,7 @@ function M.show_document(location, offset_encoding, opts)
     or focus and api.nvim_get_current_win()
     or create_window_without_focus()
 
-  api.nvim_buf_set_option(bufnr, 'buflisted', true)
+  vim.bo[bufnr].buflisted = true
   api.nvim_win_set_buf(win, bufnr)
   if focus then
     api.nvim_set_current_win(win)
@@ -1201,12 +1197,12 @@ function M.preview_location(location, opts)
   end
   local range = location.targetRange or location.range
   local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range['end'].line + 1, false)
-  local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
+  local syntax = vim.bo[bufnr].syntax
   if syntax == '' then
     -- When no syntax is set, we use filetype as fallback. This might not result
     -- in a valid syntax definition. See also ft detection in stylize_markdown.
     -- An empty syntax is more common now with TreeSitter, since TS disables syntax.
-    syntax = api.nvim_buf_get_option(bufnr, 'filetype')
+    syntax = vim.bo[bufnr].filetype
   end
   opts = opts or {}
   opts.focus_id = 'location'
@@ -1665,7 +1661,7 @@ function M.open_floating_preview(contents, syntax, opts)
     contents = M.stylize_markdown(floating_bufnr, contents, opts)
   else
     if syntax then
-      api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
+      vim.bo[floating_bufnr].syntax = syntax
     end
     api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
   end
@@ -1681,16 +1677,16 @@ function M.open_floating_preview(contents, syntax, opts)
   local float_option = M.make_floating_popup_options(width, height, opts)
   local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
   if do_stylize then
-    api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
-    api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n')
+    vim.wo[floating_winnr].conceallevel = 2
+    vim.wo[floating_winnr].concealcursor = 'n'
   end
   -- disable folding
-  api.nvim_win_set_option(floating_winnr, 'foldenable', false)
+  vim.wo[floating_winnr].foldenable = false
   -- soft wrapping
-  api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap)
+  vim.wo[floating_winnr].wrap = opts.wrap
 
-  api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
-  api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
+  vim.bo[floating_bufnr].modifiable = false
+  vim.bo[floating_bufnr].bufhidden = 'wipe'
   api.nvim_buf_set_keymap(
     floating_bufnr,
     'n',
-- 
cgit 


From 44d4ae448d0ab357a3aade773ea58c66c3c969cc Mon Sep 17 00:00:00 2001
From: Matthieu Coudron 
Date: Fri, 26 May 2023 21:51:18 +0200
Subject: fix: function was renamed (#23772)

---
 runtime/lua/vim/treesitter/languagetree.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 6c780f33c4..244d88f3e0 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -143,7 +143,7 @@ function LanguageTree:_set_logger()
 
   local lang = self:lang()
 
-  local logfilename = vim.fs._join_paths(vim.fn.stdpath('log'), 'treesitter.log')
+  local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'treesitter.log')
 
   local logfile, openerr = io.open(logfilename, 'a+')
 
-- 
cgit 


From ddd92a70d2aab5247895e89abaaa79c62ba7dbb4 Mon Sep 17 00:00:00 2001
From: Folke Lemaitre 
Date: Sun, 28 May 2023 07:51:28 +0200
Subject: feat(lsp): initial support for dynamic capabilities (#23681)

- `client.dynamic_capabilities` is an object that tracks client register/unregister
- `client.supports_method` will additionally check if a dynamic capability supports the method, taking document filters into account. But only if the client enabled `dynamicRegistration` for the capability
- updated the default client capabilities to include dynamicRegistration for:
    - formatting
    - rangeFormatting
    - hover
    - codeAction
    - hover
    - rename
---
 runtime/lua/vim/lsp.lua          | 121 ++++++++++++++++++++++-----------------
 runtime/lua/vim/lsp/_dynamic.lua | 109 +++++++++++++++++++++++++++++++++++
 runtime/lua/vim/lsp/buf.lua      |   6 +-
 runtime/lua/vim/lsp/handlers.lua |  28 ++++++---
 runtime/lua/vim/lsp/protocol.lua |  13 ++++-
 runtime/lua/vim/lsp/types.lua    |  28 +++++++++
 6 files changed, 236 insertions(+), 69 deletions(-)
 create mode 100644 runtime/lua/vim/lsp/_dynamic.lua

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 2e6ca7a0ac..5337abea25 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -50,6 +50,7 @@ lsp._request_name_to_capability = {
   ['textDocument/codeAction'] = { 'codeActionProvider' },
   ['textDocument/codeLens'] = { 'codeLensProvider' },
   ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
+  ['codeAction/resolve'] = { 'codeActionProvider', 'resolveProvider' },
   ['workspace/executeCommand'] = { 'executeCommandProvider' },
   ['workspace/symbol'] = { 'workspaceSymbolProvider' },
   ['textDocument/references'] = { 'referencesProvider' },
@@ -886,6 +887,47 @@ function lsp.start(config, opts)
   return client_id
 end
 
+---@private
+-- Determines whether the given option can be set by `set_defaults`.
+local function is_empty_or_default(bufnr, option)
+  if vim.bo[bufnr][option] == '' then
+    return true
+  end
+
+  local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
+  local scriptinfo = vim.tbl_filter(function(e)
+    return e.sid == info.last_set_sid
+  end, vim.fn.getscriptinfo())
+
+  if #scriptinfo ~= 1 then
+    return false
+  end
+
+  return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME'))
+end
+
+---@private
+---@param client lsp.Client
+function lsp._set_defaults(client, bufnr)
+  if
+    client.supports_method('textDocument/definition') and is_empty_or_default(bufnr, 'tagfunc')
+  then
+    vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
+  end
+  if
+    client.supports_method('textDocument/completion') and is_empty_or_default(bufnr, 'omnifunc')
+  then
+    vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
+  end
+  if
+    client.supports_method('textDocument/rangeFormatting')
+    and is_empty_or_default(bufnr, 'formatprg')
+    and is_empty_or_default(bufnr, 'formatexpr')
+  then
+    vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
+  end
+end
+
 -- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
 -- documented twice: Here, and on the methods themselves (e.g.
 -- `client.request()`). This is a workaround for the vimdoc generator script
@@ -1090,43 +1132,6 @@ function lsp.start_client(config)
     end
   end
 
-  ---@private
-  -- Determines whether the given option can be set by `set_defaults`.
-  local function is_empty_or_default(bufnr, option)
-    if vim.bo[bufnr][option] == '' then
-      return true
-    end
-
-    local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
-    local scriptinfo = vim.tbl_filter(function(e)
-      return e.sid == info.last_set_sid
-    end, vim.fn.getscriptinfo())
-
-    if #scriptinfo ~= 1 then
-      return false
-    end
-
-    return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME'))
-  end
-
-  ---@private
-  local function set_defaults(client, bufnr)
-    local capabilities = client.server_capabilities
-    if capabilities.definitionProvider and is_empty_or_default(bufnr, 'tagfunc') then
-      vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
-    end
-    if capabilities.completionProvider and is_empty_or_default(bufnr, 'omnifunc') then
-      vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
-    end
-    if
-      capabilities.documentRangeFormattingProvider
-      and is_empty_or_default(bufnr, 'formatprg')
-      and is_empty_or_default(bufnr, 'formatexpr')
-    then
-      vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
-    end
-  end
-
   ---@private
   --- Reset defaults set by `set_defaults`.
   --- Must only be called if the last client attached to a buffer exits.
@@ -1228,7 +1233,9 @@ function lsp.start_client(config)
     requests = {},
     -- for $/progress report
     messages = { name = name, messages = {}, progress = {}, status = {} },
+    dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
   }
+  client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
 
   -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
   uninitialized_clients[client_id] = client
@@ -1291,7 +1298,7 @@ function lsp.start_client(config)
       -- User provided initialization options.
       initializationOptions = config.init_options,
       -- The capabilities provided by the client (editor or tool)
-      capabilities = config.capabilities or protocol.make_client_capabilities(),
+      capabilities = config.capabilities,
       -- The initial trace setting. If omitted trace is disabled ("off").
       -- trace = "off" | "messages" | "verbose";
       trace = valid_traces[config.trace] or 'off',
@@ -1300,6 +1307,26 @@ function lsp.start_client(config)
       -- TODO(ashkan) handle errors here.
       pcall(config.before_init, initialize_params, config)
     end
+
+    --- @param method string
+    --- @param opts? {bufnr?: number}
+    client.supports_method = function(method, opts)
+      opts = opts or {}
+      local required_capability = lsp._request_name_to_capability[method]
+      -- if we don't know about the method, assume that the client supports it.
+      if not required_capability then
+        return true
+      end
+      if vim.tbl_get(client.server_capabilities or {}, unpack(required_capability)) then
+        return true
+      else
+        if client.dynamic_capabilities:supports_registration(method) then
+          return client.dynamic_capabilities:supports(method, opts)
+        end
+        return false
+      end
+    end
+
     local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
     rpc.request('initialize', initialize_params, function(init_err, result)
       assert(not init_err, tostring(init_err))
@@ -1314,18 +1341,6 @@ function lsp.start_client(config)
       client.server_capabilities =
         assert(result.capabilities, "initialize result doesn't contain capabilities")
       client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
-      client.supports_method = function(method)
-        local required_capability = lsp._request_name_to_capability[method]
-        -- if we don't know about the method, assume that the client supports it.
-        if not required_capability then
-          return true
-        end
-        if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
-          return true
-        else
-          return false
-        end
-      end
 
       if next(config.settings) then
         client.notify('workspace/didChangeConfiguration', { settings = config.settings })
@@ -1522,7 +1537,7 @@ function lsp.start_client(config)
   function client._on_attach(bufnr)
     text_document_did_open_handler(bufnr, client)
 
-    set_defaults(client, bufnr)
+    lsp._set_defaults(client, bufnr)
 
     nvim_exec_autocmds('LspAttach', {
       buffer = bufnr,
@@ -1946,7 +1961,7 @@ function lsp.buf_request(bufnr, method, params, handler)
   local supported_clients = {}
   local method_supported = false
   for_each_buffer_client(bufnr, function(client, client_id)
-    if client.supports_method(method) then
+    if client.supports_method(method, { bufnr = bufnr }) then
       method_supported = true
       table.insert(supported_clients, client_id)
     end
@@ -2002,7 +2017,7 @@ function lsp.buf_request_all(bufnr, method, params, callback)
 
   local set_expected_result_count = once(function()
     for_each_buffer_client(bufnr, function(client)
-      if client.supports_method(method) then
+      if client.supports_method(method, { bufnr = bufnr }) then
         expected_result_count = expected_result_count + 1
       end
     end)
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
new file mode 100644
index 0000000000..04040e8e28
--- /dev/null
+++ b/runtime/lua/vim/lsp/_dynamic.lua
@@ -0,0 +1,109 @@
+local wf = require('vim.lsp._watchfiles')
+
+--- @class lsp.DynamicCapabilities
+--- @field capabilities table
+--- @field client_id number
+local M = {}
+
+--- @param client_id number
+function M.new(client_id)
+  return setmetatable({
+    capabilities = {},
+    client_id = client_id,
+  }, { __index = M })
+end
+
+function M:supports_registration(method)
+  local client = vim.lsp.get_client_by_id(self.client_id)
+  if not client then
+    return false
+  end
+  local capability = vim.tbl_get(client.config.capabilities, unpack(vim.split(method, '/')))
+  return type(capability) == 'table' and capability.dynamicRegistration
+end
+
+--- @param registrations lsp.Registration[]
+--- @private
+function M:register(registrations)
+  -- remove duplicates
+  self:unregister(registrations)
+  for _, reg in ipairs(registrations) do
+    local method = reg.method
+    if not self.capabilities[method] then
+      self.capabilities[method] = {}
+    end
+    table.insert(self.capabilities[method], reg)
+  end
+end
+
+--- @param unregisterations lsp.Unregistration[]
+--- @private
+function M:unregister(unregisterations)
+  for _, unreg in ipairs(unregisterations) do
+    local method = unreg.method
+    if not self.capabilities[method] then
+      return
+    end
+    local id = unreg.id
+    for i, reg in ipairs(self.capabilities[method]) do
+      if reg.id == id then
+        table.remove(self.capabilities[method], i)
+        break
+      end
+    end
+  end
+end
+
+--- @param method string
+--- @param opts? {bufnr?: number}
+--- @return lsp.Registration? (table|nil) the registration if found
+--- @private
+function M:get(method, opts)
+  opts = opts or {}
+  opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
+  for _, reg in ipairs(self.capabilities[method] or {}) do
+    if not reg.registerOptions then
+      return reg
+    end
+    local documentSelector = reg.registerOptions.documentSelector
+    if not documentSelector then
+      return reg
+    end
+    if M.match(opts.bufnr, documentSelector) then
+      return reg
+    end
+  end
+end
+
+--- @param method string
+--- @param opts? {bufnr?: number}
+--- @private
+function M:supports(method, opts)
+  return self:get(method, opts) ~= nil
+end
+
+--- @param bufnr number
+--- @param documentSelector lsp.DocumentSelector
+--- @private
+function M.match(bufnr, documentSelector)
+  local ft = vim.bo[bufnr].filetype
+  local uri = vim.uri_from_bufnr(bufnr)
+  local fname = vim.uri_to_fname(uri)
+  for _, filter in ipairs(documentSelector) do
+    local matches = true
+    if filter.language and ft ~= filter.language then
+      matches = false
+    end
+    if matches and filter.scheme and not vim.startswith(uri, filter.scheme .. ':') then
+      matches = false
+    end
+    if matches and filter.pattern and not wf._match(filter.pattern, fname) then
+      matches = false
+    end
+    if matches then
+      return true
+    end
+  end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index a307dea673..b2f202c4ba 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -683,11 +683,7 @@ local function on_code_action_results(results, ctx, options)
     --
     local client = vim.lsp.get_client_by_id(action_tuple[1])
     local action = action_tuple[2]
-    if
-      not action.edit
-      and client
-      and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
-    then
+    if not action.edit and client and client.supports_method('codeAction/resolve') then
       client.request('codeAction/resolve', action, function(err, resolved_action)
         if err then
           vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 8e926c4644..5346160871 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -118,22 +118,30 @@ end
 
 --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
 M['client/registerCapability'] = function(_, result, ctx)
-  local log_unsupported = false
+  local client_id = ctx.client_id
+  ---@type lsp.Client
+  local client = vim.lsp.get_client_by_id(client_id)
+
+  client.dynamic_capabilities:register(result.registrations)
+  for bufnr, _ in ipairs(client.attached_buffers) do
+    vim.lsp._set_defaults(client, bufnr)
+  end
+
+  ---@type string[]
+  local unsupported = {}
   for _, reg in ipairs(result.registrations) do
     if reg.method == 'workspace/didChangeWatchedFiles' then
       require('vim.lsp._watchfiles').register(reg, ctx)
-    else
-      log_unsupported = true
+    elseif not client.dynamic_capabilities:supports_registration(reg.method) then
+      unsupported[#unsupported + 1] = reg.method
     end
   end
-  if log_unsupported then
-    local client_id = ctx.client_id
+  if #unsupported > 0 then
     local warning_tpl = 'The language server %s triggers a registerCapability '
-      .. 'handler despite dynamicRegistration set to false. '
+      .. 'handler for %s despite dynamicRegistration set to false. '
       .. 'Report upstream, this warning is harmless'
-    local client = vim.lsp.get_client_by_id(client_id)
     local client_name = client and client.name or string.format('id=%d', client_id)
-    local warning = string.format(warning_tpl, client_name)
+    local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', '))
     log.warn(warning)
   end
   return vim.NIL
@@ -141,6 +149,10 @@ end
 
 --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
 M['client/unregisterCapability'] = function(_, result, ctx)
+  local client_id = ctx.client_id
+  local client = vim.lsp.get_client_by_id(client_id)
+  client.dynamic_capabilities:unregister(result.unregisterations)
+
   for _, unreg in ipairs(result.unregisterations) do
     if unreg.method == 'workspace/didChangeWatchedFiles' then
       require('vim.lsp._watchfiles').unregister(unreg, ctx)
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index a7919f12f5..a28ff407b7 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -697,7 +697,7 @@ function protocol.make_client_capabilities()
         didSave = true,
       },
       codeAction = {
-        dynamicRegistration = false,
+        dynamicRegistration = true,
 
         codeActionLiteralSupport = {
           codeActionKind = {
@@ -714,6 +714,12 @@ function protocol.make_client_capabilities()
           properties = { 'edit' },
         },
       },
+      formatting = {
+        dynamicRegistration = true,
+      },
+      rangeFormatting = {
+        dynamicRegistration = true,
+      },
       completion = {
         dynamicRegistration = false,
         completionItem = {
@@ -747,6 +753,7 @@ function protocol.make_client_capabilities()
       },
       definition = {
         linkSupport = true,
+        dynamicRegistration = true,
       },
       implementation = {
         linkSupport = true,
@@ -755,7 +762,7 @@ function protocol.make_client_capabilities()
         linkSupport = true,
       },
       hover = {
-        dynamicRegistration = false,
+        dynamicRegistration = true,
         contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
       },
       signatureHelp = {
@@ -790,7 +797,7 @@ function protocol.make_client_capabilities()
         hierarchicalDocumentSymbolSupport = true,
       },
       rename = {
-        dynamicRegistration = false,
+        dynamicRegistration = true,
         prepareSupport = true,
       },
       publishDiagnostics = {
diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua
index 779f313aa7..e77e1fb63a 100644
--- a/runtime/lua/vim/lsp/types.lua
+++ b/runtime/lua/vim/lsp/types.lua
@@ -35,3 +35,31 @@
 ---@field source string
 ---@field tags? lsp.DiagnosticTag[]
 ---@field relatedInformation DiagnosticRelatedInformation[]
+
+--- @class lsp.DocumentFilter
+--- @field language? string
+--- @field scheme? string
+--- @field pattern? string
+
+--- @alias lsp.DocumentSelector lsp.DocumentFilter[]
+
+--- @alias lsp.RegisterOptions any | lsp.StaticRegistrationOptions | lsp.TextDocumentRegistrationOptions
+
+--- @class lsp.Registration
+--- @field id string
+--- @field method string
+--- @field registerOptions? lsp.RegisterOptions
+
+--- @alias lsp.RegistrationParams {registrations: lsp.Registration[]}
+
+--- @class lsp.StaticRegistrationOptions
+--- @field id? string
+
+--- @class lsp.TextDocumentRegistrationOptions
+--- @field documentSelector? lsp.DocumentSelector
+
+--- @class lsp.Unregistration
+--- @field id string
+--- @field method string
+
+--- @alias lsp.UnregistrationParams {unregisterations: lsp.Unregistration[]}
-- 
cgit 


From 8e17054a2333318340f23abd7a94abfe474fb52f Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 28 May 2023 11:47:07 +0200
Subject: vim-patch:9.0.1584: not all meson files are recognized (#23797)

Problem:    Not all meson files are recognized.
Solution:   Add "meson.options". (Liam Beguin, closes vim/vim#12444)

https://github.com/vim/vim/commit/1ba0b9e36f36926a7675b31efeda7d3e495c9157

Co-authored-by: Bram Moolenaar 
---
 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 1b04666161..57df621153 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1547,6 +1547,7 @@ local filename = {
   ['man.config'] = 'manconf',
   ['maxima-init.mac'] = 'maxima',
   ['meson.build'] = 'meson',
+  ['meson.options'] = 'meson',
   ['meson_options.txt'] = 'meson',
   ['/etc/conf.modules'] = 'modconf',
   ['/etc/modules'] = 'modconf',
-- 
cgit 


From b8a2220f5eb32ead6b66f7c49b7e915883bf3539 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Mon, 29 May 2023 10:25:05 +0200
Subject: vim-patch:9.0.1587: Corn config files are not recognized (#23807)

Problem:    Corn config files are not recognized.
Solution:   Add a pattern for Corn config files. (Jake Stanger, closes vim/vim#12449)

https://github.com/vim/vim/commit/05843e89601c03389a2d7978e7e16aec641a3dc4

Co-authored-by: Jake Stanger 
---
 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 57df621153..6990de3391 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -241,6 +241,7 @@ local extension = {
   copyright = function(path, bufnr)
     return require('vim.filetype.detect').copyright(bufnr)
   end,
+  corn = 'corn',
   csh = function(path, bufnr)
     return require('vim.filetype.detect').csh(path, bufnr)
   end,
-- 
cgit 


From e8776074f50593df90402a3642236990edfba6f0 Mon Sep 17 00:00:00 2001
From: Omar El Halabi 
Date: Tue, 30 May 2023 00:44:23 +0200
Subject: vim-patch:9.0.1591: some "gomod" files are not recognized (#23820)

Problem:    Some "gomod" files are not recognized.
Solution:   Check for "go.mod" file name before checking out the contents.
            (Omar El Halabi, closes vim/vim#12462)

https://github.com/vim/vim/commit/c9fbd2560f24180d2efa40028ed68427341d2d99
---
 runtime/lua/vim/filetype/detect.lua | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 94106a3547..70e590ed10 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -771,14 +771,14 @@ end
 function M.mod(path, bufnr)
   if vim.g.filetype_mod then
     return vim.g.filetype_mod
+  elseif matchregex(path, [[\c\
Date: Tue, 30 May 2023 19:15:07 +0200
Subject: fix(lsp): fix dynamic registration of code actions (#23826)

---
 runtime/lua/vim/lsp/buf.lua | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index b2f202c4ba..bb3ca0e6d6 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -681,9 +681,16 @@ local function on_code_action_results(results, ctx, options)
     --  command: string
     --  arguments?: any[]
     --
+    ---@type lsp.Client
     local client = vim.lsp.get_client_by_id(action_tuple[1])
     local action = action_tuple[2]
-    if not action.edit and client and client.supports_method('codeAction/resolve') then
+
+    local reg = client.dynamic_capabilities:get('textDocument/codeAction', { bufnr = ctx.bufnr })
+
+    local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider')
+      or client.supports_method('codeAction/resolve')
+
+    if not action.edit and client and supports_resolve then
       client.request('codeAction/resolve', action, function(err, resolved_action)
         if err then
           vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
-- 
cgit 


From 58618d208acd3827c4e86668529edb619bb9b8dd Mon Sep 17 00:00:00 2001
From: jdrouhard 
Date: Tue, 30 May 2023 13:56:29 -0500
Subject: feat(lsp)!: promote LspRequest to a full autocmd and enrich with
 additional data (#23694)

BREAKING CHANGE: LspRequest is no longer a User autocmd but is now a
first class citizen.

LspRequest as a User autocmd had limited functionality. Namely, the only
thing you could do was use the notification to do a lookup on all the
clients' requests tables to figure out what changed.

Promoting the autocmd to a full autocmd lets us set the buffer the
request was initiated on (so people can set buffer-local autocmds for
listening to these events).

Additionally, when used from Lua, we can pass additional metadata about
the request along with the notification, including the client ID, the
request ID, and the actual request object stored on the client's
requests table. Users can now listen for these events and act on them
proactively instead of polling all of the requests tables and looking
for changes.
---
 runtime/lua/vim/lsp.lua | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 5337abea25..c9ca8cd224 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -799,7 +799,9 @@ end
 ---    to the server. Entries are key-value pairs with the key
 ---    being the request ID while the value is a table with `type`,
 ---    `bufnr`, and `method` key-value pairs. `type` is either "pending"
----    for an active request, or "cancel" for a cancel request.
+---    for an active request, or "cancel" for a cancel request. It will
+---    be "complete" ephemerally while executing |LspRequest| autocmds
+---    when replies are received from the server.
 ---
 ---  - {config} (table): copy of the table that was passed by the user
 ---    to |vim.lsp.start_client()|.
@@ -1408,13 +1410,24 @@ function lsp.start_client(config)
         { method = method, client_id = client_id, bufnr = bufnr, params = params }
       )
     end, function(request_id)
+      local request = client.requests[request_id]
+      request.type = 'complete'
+      nvim_exec_autocmds('LspRequest', {
+        buffer = bufnr,
+        modeline = false,
+        data = { client_id = client_id, request_id = request_id, request = request },
+      })
       client.requests[request_id] = nil
-      nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
     end)
 
     if success and request_id then
-      client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
-      nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+      local request = { type = 'pending', bufnr = bufnr, method = method }
+      client.requests[request_id] = request
+      nvim_exec_autocmds('LspRequest', {
+        buffer = bufnr,
+        modeline = false,
+        data = { client_id = client_id, request_id = request_id, request = request },
+      })
     end
 
     return success, request_id
@@ -1486,7 +1499,11 @@ function lsp.start_client(config)
     local request = client.requests[id]
     if request and request.type == 'pending' then
       request.type = 'cancel'
-      nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+      nvim_exec_autocmds('LspRequest', {
+        buffer = request.bufnr,
+        modeline = false,
+        data = { client_id = client_id, request_id = id, request = request },
+      })
     end
     return rpc.notify('$/cancelRequest', { id = id })
   end
-- 
cgit 


From be5e3611541051d9fa5b752a4fe1da6ab78b141e Mon Sep 17 00:00:00 2001
From: Raphael 
Date: Thu, 1 Jun 2023 14:38:38 +0800
Subject: fix(lsp): add param assert in client_is_stopped (#23857)

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

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index c9ca8cd224..d64ed0b5a3 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -2275,7 +2275,8 @@ end
 ---@param client_id (integer)
 ---@return boolean stopped true if client is stopped, false otherwise.
 function lsp.client_is_stopped(client_id)
-  return active_clients[client_id] == nil
+  assert(client_id, 'missing client_id param')
+  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
-- 
cgit 


From fb54e6980ea6fec218a11f118e97ef65f250395a Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Thu, 1 Jun 2023 11:15:33 -0500
Subject: feat(lsp): set client offset_encoding if server supports
 positionEncoding

If the server sends the positionEncoding capability in its
initialization response, automatically set the client's offset_encoding
to use the value provided.
---
 runtime/lua/vim/lsp.lua | 4 ++++
 1 file changed, 4 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index d64ed0b5a3..baf8b5c1a8 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1344,6 +1344,10 @@ function lsp.start_client(config)
         assert(result.capabilities, "initialize result doesn't contain capabilities")
       client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
 
+      if client.server_capabilities.positionEncoding then
+        client.offset_encoding = client.server_capabilities.positionEncoding
+      end
+
       if next(config.settings) then
         client.notify('workspace/didChangeConfiguration', { settings = config.settings })
       end
-- 
cgit 


From 15641f38cf4b489a7c83e2c3aa6efc4c63009f00 Mon Sep 17 00:00:00 2001
From: Gregory Anders 
Date: Thu, 1 Jun 2023 11:39:51 -0500
Subject: feat(lsp): include positionEncodings in default client capabilities

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

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index a28ff407b7..7e49a572e7 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -634,6 +634,13 @@ export interface WorkspaceClientCapabilities {
 --- capabilities.
 function protocol.make_client_capabilities()
   return {
+    general = {
+      positionEncodings = {
+        'utf-8',
+        'utf-16',
+        'utf-32',
+      },
+    },
     textDocument = {
       semanticTokens = {
         dynamicRegistration = false,
-- 
cgit 


From aa130d0c7ea69a05330d0b054b414cc3a15dac45 Mon Sep 17 00:00:00 2001
From: dundargoc <33953936+dundargoc@users.noreply.github.com>
Date: Fri, 2 Jun 2023 16:59:58 +0200
Subject: docs: small fixes (#23619)

Co-authored-by: Evgeni Chasnovski 
Co-authored-by: Gustavo Ferreira 
Co-authored-by: Kai Moschcau 
Co-authored-by: Lampros 
---
 runtime/lua/vim/iter.lua | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index bda3508262..9112b6c3bb 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -335,7 +335,7 @@ end
 --- Fold an iterator or table into a single value.
 ---
 --- 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)
-- 
cgit 


From c65e2203f70cd5d66fcb8ffb26f8cef38f50e04f Mon Sep 17 00:00:00 2001
From: Sebastian Lyng Johansen 
Date: Sat, 3 Jun 2023 09:18:05 +0200
Subject: docs(iter): add emmylua type to iter module (#23845)

---
 runtime/lua/vim/iter.lua | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 9112b6c3bb..56012e9b9f 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -51,6 +51,8 @@
 --- In addition to the |vim.iter()| function, the |vim.iter| module provides
 --- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
 
+---@class IterMod
+---@operator call:Iter
 local M = {}
 
 ---@class Iter
@@ -974,6 +976,7 @@ function M.map(f, src, ...)
   return Iter.new(src, ...):map(f):totable()
 end
 
+---@type IterMod
 return setmetatable(M, {
   __call = function(_, ...)
     return Iter.new(...)
-- 
cgit 


From 2db719f6c2b677fcbc197b02fe52764a851523b2 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sat, 3 Jun 2023 11:06:00 +0100
Subject: feat(lua): rename vim.loop -> vim.uv (#22846)

---
 runtime/lua/vim/_editor.lua                 |  8 ++++++--
 runtime/lua/vim/_watch.lua                  |  6 +++---
 runtime/lua/vim/filetype/options.lua        |  2 +-
 runtime/lua/vim/fs.lua                      | 20 ++++++++++----------
 runtime/lua/vim/loader.lua                  |  4 ++--
 runtime/lua/vim/lsp.lua                     |  2 +-
 runtime/lua/vim/lsp/health.lua              |  2 +-
 runtime/lua/vim/lsp/log.lua                 |  4 ++--
 runtime/lua/vim/lsp/rpc.lua                 |  4 ++--
 runtime/lua/vim/lsp/semantic_tokens.lua     |  2 +-
 runtime/lua/vim/lsp/util.lua                |  2 +-
 runtime/lua/vim/secure.lua                  |  6 +++---
 runtime/lua/vim/treesitter/languagetree.lua |  4 ++--
 13 files changed, 35 insertions(+), 31 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index b26def5958..7b946a55e4 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -42,6 +42,10 @@ for k, v in pairs({
   vim._submodules[k] = v
 end
 
+-- Remove at Nvim 1.0
+---@deprecated
+vim.loop = vim.uv
+
 -- There are things which have special rules in vim._init_packages
 -- for legacy reasons (uri) or for performance (_inspector).
 -- most new things should go into a submodule namespace ( vim.foobar.do_thing() )
@@ -159,7 +163,7 @@ do
   ---                - 3: ends the paste (exactly once)
   ---@returns boolean # false if client should cancel the paste.
   function vim.paste(lines, phase)
-    local now = vim.loop.now()
+    local now = vim.uv.now()
     local is_first_chunk = phase < 2
     local is_last_chunk = phase == -1 or phase == 3
     if is_first_chunk then -- Reset flags.
@@ -483,7 +487,7 @@ end
 ---@return table timer luv timer object
 function vim.defer_fn(fn, timeout)
   vim.validate({ fn = { fn, 'c', true } })
-  local timer = vim.loop.new_timer()
+  local timer = vim.uv.new_timer()
   timer:start(
     timeout,
     0,
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index dbffd726a2..3bd8a56f6e 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -46,7 +46,7 @@ function M.watch(path, opts, callback)
 
   path = vim.fs.normalize(path)
   local uvflags = opts and opts.uvflags or {}
-  local handle, new_err = vim.loop.new_fs_event()
+  local handle, new_err = vim.uv.new_fs_event()
   assert(not new_err, new_err)
   local _, start_err = handle:start(path, uvflags, function(err, filename, events)
     assert(not err, err)
@@ -57,7 +57,7 @@ function M.watch(path, opts, callback)
     end
     local change_type = events.change and M.FileChangeType.Changed or 0
     if events.rename then
-      local _, staterr, staterrname = vim.loop.fs_stat(fullpath)
+      local _, staterr, staterrname = vim.uv.fs_stat(fullpath)
       if staterrname == 'ENOENT' then
         change_type = M.FileChangeType.Deleted
       else
@@ -99,7 +99,7 @@ local function poll_internal(path, opts, callback, watches)
   }
 
   if not watches.handle then
-    local poll, new_err = vim.loop.new_fs_poll()
+    local poll, new_err = vim.uv.new_fs_poll()
     assert(not new_err, new_err)
     watches.handle = poll
     local _, start_err = poll:start(
diff --git a/runtime/lua/vim/filetype/options.lua b/runtime/lua/vim/filetype/options.lua
index a093c249f7..2a28b5a8e3 100644
--- a/runtime/lua/vim/filetype/options.lua
+++ b/runtime/lua/vim/filetype/options.lua
@@ -34,7 +34,7 @@ local ft_option_cache = {} ---@type table>
 --- @param path string
 --- @return integer
 local function hash(path)
-  local mtime0 = vim.loop.fs_stat(path).mtime
+  local mtime0 = vim.uv.fs_stat(path).mtime
   return mtime0.sec * 1000000000 + mtime0.nsec
 end
 
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 864ba495f1..5e63fb6237 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -1,6 +1,6 @@
 local M = {}
 
-local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
 
 --- Iterate over all the parents of the given file or directory.
 ---
@@ -106,12 +106,12 @@ function M.dir(path, opts)
   })
 
   if not opts.depth or opts.depth == 1 then
-    local fs = vim.loop.fs_scandir(M.normalize(path))
+    local fs = vim.uv.fs_scandir(M.normalize(path))
     return function()
       if not fs then
         return
       end
-      return vim.loop.fs_scandir_next(fs)
+      return vim.uv.fs_scandir_next(fs)
     end
   end
 
@@ -121,9 +121,9 @@ function M.dir(path, opts)
     while #dirs > 0 do
       local dir0, level = unpack(table.remove(dirs, 1))
       local dir = level == 1 and dir0 or M.joinpath(path, dir0)
-      local fs = vim.loop.fs_scandir(M.normalize(dir))
+      local fs = vim.uv.fs_scandir(M.normalize(dir))
       while fs do
-        local name, t = vim.loop.fs_scandir_next(fs)
+        local name, t = vim.uv.fs_scandir_next(fs)
         if not name then
           break
         end
@@ -158,7 +158,7 @@ end
 --- -- location of Cargo.toml from the current buffer's path
 --- local cargo = vim.fs.find('Cargo.toml', {
 ---   upward = true,
----   stop = vim.loop.os_homedir(),
+---   stop = vim.uv.os_homedir(),
 ---   path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)),
 --- })
 ---
@@ -212,7 +212,7 @@ function M.find(names, opts)
 
   names = type(names) == 'string' and { names } or names
 
-  local path = opts.path or vim.loop.cwd()
+  local path = opts.path or vim.uv.cwd()
   local stop = opts.stop
   local limit = opts.limit or 1
 
@@ -244,7 +244,7 @@ function M.find(names, opts)
         local t = {}
         for _, name in ipairs(names) do
           local f = M.joinpath(p, name)
-          local stat = vim.loop.fs_stat(f)
+          local stat = vim.uv.fs_stat(f)
           if stat and (not opts.type or opts.type == stat.type) then
             t[#t + 1] = f
           end
@@ -337,7 +337,7 @@ function M.normalize(path, opts)
   })
 
   if path:sub(1, 1) == '~' then
-    local home = vim.loop.os_homedir() or '~'
+    local home = vim.uv.os_homedir() or '~'
     if home:sub(-1) == '\\' or home:sub(-1) == '/' then
       home = home:sub(1, -2)
     end
@@ -345,7 +345,7 @@ function M.normalize(path, opts)
   end
 
   if opts.expand_env == nil or opts.expand_env then
-    path = path:gsub('%$([%w_]+)', vim.loop.os_getenv)
+    path = path:gsub('%$([%w_]+)', vim.uv.os_getenv)
   end
 
   path = path:gsub('\\', '/'):gsub('/+', '/')
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index 66627fe4e7..9024bdb231 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -1,4 +1,4 @@
-local uv = vim.loop
+local uv = vim.uv
 
 --- @type (fun(modename: string): fun()|string)[]
 local loaders = package.loaders
@@ -465,7 +465,7 @@ end
 --- @private
 function Loader.track(stat, f)
   return function(...)
-    local start = vim.loop.hrtime()
+    local start = vim.uv.hrtime()
     local r = { f(...) }
     Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 }
     Loader._stats[stat].total = Loader._stats[stat].total + 1
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index baf8b5c1a8..5f7a95ae14 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -10,7 +10,7 @@ local semantic_tokens = require('vim.lsp.semantic_tokens')
 local api = vim.api
 local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
   api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
-local uv = vim.loop
+local uv = vim.uv
 local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
 local validate = vim.validate
 local if_nil = vim.F.if_nil
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 6ca468393e..8817bb71de 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -22,7 +22,7 @@ function M.check()
   local log_path = vim.lsp.get_log_path()
   report_info(string.format('Log path: %s', log_path))
 
-  local log_file = vim.loop.fs_stat(log_path)
+  local log_file = vim.uv.fs_stat(log_path)
   local log_size = log_file and log_file.size or 0
 
   local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 3d5bc06c3f..07d0500797 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -31,7 +31,7 @@ do
     end
   end
 
-  local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
+  local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/'
   ---@private
   local function path_join(...)
     return table.concat(vim.tbl_flatten({ ... }), path_sep)
@@ -68,7 +68,7 @@ do
       return false
     end
 
-    local log_info = vim.loop.fs_stat(logfilename)
+    local log_info = vim.uv.fs_stat(logfilename)
     if log_info and log_info.size > 1e9 then
       local warn_msg = string.format(
         'LSP client log is large (%d MB): %s',
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index af3190c9bd..5f48effebf 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -1,4 +1,4 @@
-local uv = vim.loop
+local uv = vim.uv
 local log = require('vim.lsp.log')
 local protocol = require('vim.lsp.protocol')
 local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
@@ -691,7 +691,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
   })
 
   ---@private
-  --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher.
+  --- Callback for |vim.uv.spawn()| Closes all streams and runs the `on_exit` dispatcher.
   ---@param code (integer) Exit code
   ---@param signal (integer) Signal that was used to terminate (if any)
   local function onexit(code, signal)
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 376cac19a7..191cc7b400 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -2,7 +2,7 @@ local api = vim.api
 local bit = require('bit')
 local handlers = require('vim.lsp.handlers')
 local util = require('vim.lsp.util')
-local uv = vim.loop
+local uv = vim.uv
 
 --- @class STTokenRange
 --- @field line integer line number 0-based
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 9fffc845b1..53f8dba814 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -4,7 +4,7 @@ local validate = vim.validate
 local api = vim.api
 local list_extend = vim.list_extend
 local highlight = require('vim.highlight')
-local uv = vim.loop
+local uv = vim.uv
 
 local npcall = vim.F.npcall
 local split = vim.split
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index 443b152273..1a04e11231 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -51,7 +51,7 @@ end
 ---        trusted, or nil otherwise.
 function M.read(path)
   vim.validate({ path = { path, 's' } })
-  local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
+  local fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
   if not fullpath then
     return nil
   end
@@ -149,13 +149,13 @@ function M.trust(opts)
 
   local fullpath
   if path then
-    fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
+    fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
   elseif bufnr then
     local bufname = vim.api.nvim_buf_get_name(bufnr)
     if bufname == '' then
       return false, 'buffer is not associated with a file'
     end
-    fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname))
+    fullpath = vim.uv.fs_realpath(vim.fs.normalize(bufname))
   else
     error('one of "path" or "bufnr" is required')
   end
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 244d88f3e0..cabfa8ccc0 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -170,11 +170,11 @@ end
 ---@param f fun(): R1, R2, R2
 ---@return integer, R1, R2, R3
 local function tcall(f, ...)
-  local start = vim.loop.hrtime()
+  local start = vim.uv.hrtime()
   ---@diagnostic disable-next-line
   local r = { f(...) }
   --- @type number
-  local duration = (vim.loop.hrtime() - start) / 1000000
+  local duration = (vim.uv.hrtime() - start) / 1000000
   return duration, unpack(r)
 end
 
-- 
cgit 


From 40db569014471deb5bd17860be00d6833387be79 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Sat, 3 Jun 2023 11:06:10 +0100
Subject: perf(iter): make ListIter.totable more efficient (#23714)

---
 runtime/lua/vim/iter.lua | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 56012e9b9f..0e98d0437e 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -66,7 +66,7 @@ end
 ---@class ListIter : 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
+---@field _tail number Index to the end of a table iterator (exclusive)
 local ListIter = {}
 ListIter.__index = setmetatable(ListIter, Iter)
 ListIter.__call = function(self)
@@ -321,17 +321,33 @@ end
 
 ---@private
 function ListIter.totable(self)
-  if self._head == 1 and self._tail == #self._table + 1 and self.next == ListIter.next then
-    -- Sanitize packed table values
-    if getmetatable(self._table[1]) == packedmt then
-      for i = 1, #self._table do
-        self._table[i] = sanitize(self._table[i])
-      end
+  if self.next ~= ListIter.next or self._head >= self._tail then
+    return Iter.totable(self)
+  end
+
+  local needs_sanitize = getmetatable(self._table[1]) == packedmt
+
+  -- Reindex and sanitize.
+  local len = self._tail - self._head
+
+  if needs_sanitize then
+    for i = 1, len do
+      self._table[i] = sanitize(self._table[self._head - 1 + i])
+    end
+  else
+    for i = 1, len do
+      self._table[i] = self._table[self._head - 1 + i]
     end
-    return self._table
   end
 
-  return Iter.totable(self)
+  for i = len + 1, table.maxn(self._table) do
+    self._table[i] = nil
+  end
+
+  self._head = 1
+  self._tail = len + 1
+
+  return self._table
 end
 
 --- Fold an iterator or table into a single value.
-- 
cgit 


From 0a439e38634542fd961bb4f04b1cdda304cb1bb5 Mon Sep 17 00:00:00 2001
From: Christian Clason 
Date: Sun, 4 Jun 2023 00:39:46 +0200
Subject: vim-patch:9.0.1601: filetype detection fails for *.conf file without
 comments (#23896)

Problem:    Filetype detection fails for *.conf file without comments.
            (Dmitrii Tcyganok)
Solution:   Use "conf" filetype as a fallback for an empty .conf file.
            (closes vim/vim#12487, closes vim/vim#12483)

https://github.com/vim/vim/commit/664fd12aa27a3c6bd19cfa474c4630d6c03fcc61

Co-authored-by: zeertzjq 
---
 runtime/lua/vim/filetype/detect.lua | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 70e590ed10..b347215895 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -196,6 +196,9 @@ function M.conf(path, bufnr)
   if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
     return
   end
+  if path:find('%.conf$') then
+    return 'conf'
+  end
   for _, line in ipairs(getlines(bufnr, 1, 5)) do
     if line:find('^#') then
       return 'conf'
-- 
cgit 


From 96e19533f60add50eea4598560bbd56de3b1fca3 Mon Sep 17 00:00:00 2001
From: Artyom Andreev 
Date: Sun, 4 Jun 2023 04:03:25 +0300
Subject: feat(lsp): set kind in select call for codelens #23889

---
 runtime/lua/vim/lsp/codelens.lua | 1 +
 1 file changed, 1 insertion(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 005a0047fa..ea8c52c334 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -97,6 +97,7 @@ function M.run()
   else
     vim.ui.select(options, {
       prompt = 'Code lenses:',
+      kind = 'codelens',
       format_item = function(option)
         return option.lens.command.title
       end,
-- 
cgit 


From 5282d3299c9b1b07f3e02a9014bc2632cf3b4fed Mon Sep 17 00:00:00 2001
From: Folke Lemaitre 
Date: Mon, 5 Jun 2023 01:45:01 +0200
Subject: fix(lsp): restore marks after apply_text_edits() #14630

PROBLEM:
Whenever any text edits are applied to the buffer, the `marks` part of those
lines will be lost. This is mostly problematic for code formatters that format
the whole buffer like `prettier`, `luafmt`, ...

When doing atomic changes inside a vim doc, vim keeps track of those changes and
can update the positions of marks accordingly, but in this case we have a whole
doc that changed. There's no simple way to update the positions of all marks
from the previous document state to the new document state.

SOLUTION:
* save marks right before `nvim_buf_set_lines` is called inside `apply_text_edits`
* check if any marks were lost after doing `nvim_buf_set_lines`
* restore those marks to the previous positions

TEST CASE:
* have a formatter enabled
* open any file
* create a couple of marks
* indent the whole file to the right
* save the file
Before this change: all marks will be removed.
After this change: they will be preserved.

Fixes #14307
---
 runtime/lua/vim/lsp/util.lua | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 53f8dba814..ba8c72128e 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -451,6 +451,14 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
     }
   end)()
 
+  -- save and restore local marks since they get deleted by nvim_buf_set_lines
+  local marks = {}
+  for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+    if m.mark:match("^'[a-z]$") then
+      marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed
+    end
+  end
+
   -- Apply text edits.
   local is_cursor_fixed = false
   local has_eol_text_edit = false
@@ -518,6 +526,20 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
 
   local max = api.nvim_buf_line_count(bufnr)
 
+  -- no need to restore marks that still exist
+  for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+    marks[m.mark:sub(2, 2)] = nil
+  end
+  -- restore marks
+  for mark, pos in pairs(marks) do
+    if pos then
+      -- make sure we don't go out of bounds
+      pos[1] = math.min(pos[1], max)
+      pos[2] = math.min(pos[2], #(get_line(bufnr, pos[1] - 1) or ''))
+      vim.api.nvim_buf_set_mark(bufnr or 0, mark, pos[1], pos[2], {})
+    end
+  end
+
   -- Apply fixed cursor position.
   if is_cursor_fixed then
     local is_valid_cursor = true
-- 
cgit 


From 3c6d971e5488dc75b7db07c14d01f87827f28a67 Mon Sep 17 00:00:00 2001
From: Raphael 
Date: Mon, 5 Jun 2023 13:17:38 +0800
Subject: fix(lsp): set extra info only when it has a value (#23868)

---
 runtime/lua/vim/lsp/util.lua | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index ba8c72128e..e36014d07d 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -716,15 +716,18 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
   local matches = {}
 
   for _, completion_item in ipairs(items) do
-    local info = ' '
+    local info = ''
     local documentation = completion_item.documentation
     if documentation then
       if type(documentation) == 'string' and documentation ~= '' then
         info = documentation
       elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
         info = documentation.value
-        -- else
-        -- TODO(ashkan) Validation handling here?
+      else
+        vim.notify(
+          ('invalid documentation value %s'):format(vim.inspect(documentation)),
+          vim.log.levels.WARN
+        )
       end
     end
 
@@ -734,7 +737,7 @@ function M.text_document_completion_list_to_complete_items(result, prefix)
       abbr = completion_item.label,
       kind = M._get_completion_item_kind_name(completion_item.kind),
       menu = completion_item.detail or '',
-      info = info,
+      info = #info > 0 and info or nil,
       icase = 1,
       dup = 1,
       empty = 1,
-- 
cgit 


From 416fe8d185dcc072df7942953d867d9c605e9ffd Mon Sep 17 00:00:00 2001
From: Jon Huhn 
Date: Mon, 5 Jun 2023 00:19:31 -0500
Subject: refactor(lsp): use LPeg for watchfiles matching (#23788)

---
 runtime/lua/vim/lsp/_watchfiles.lua | 191 +++++++++++-------------------------
 1 file changed, 56 insertions(+), 135 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index cf2c57db1f..14e5dc6cf8 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -1,152 +1,81 @@
 local bit = require('bit')
+local lpeg = require('lpeg')
 local watch = require('vim._watch')
 local protocol = require('vim.lsp.protocol')
 
 local M = {}
 
 ---@private
----Parses the raw pattern into a number of Lua-native patterns.
+--- Parses the raw pattern into an |lpeg| pattern. LPeg patterns natively support the "this" or "that"
+--- alternative constructions described in the LSP spec that cannot be expressed in a standard Lua pattern.
 ---
 ---@param pattern string The raw glob pattern
----@return table A list of Lua patterns. A match with any of them matches the input glob pattern.
+---@return userdata An |lpeg| representation of the pattern, or nil if the pattern is invalid.
 local function parse(pattern)
-  local patterns = { '' }
+  local l = lpeg
 
-  local path_sep = '[/\\]'
-  local non_path_sep = '[^/\\]'
+  local P, S, V = lpeg.P, lpeg.S, lpeg.V
+  local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf
 
-  local function append(chunks)
-    local new_patterns = {}
-    for _, p in ipairs(patterns) do
-      for _, chunk in ipairs(chunks) do
-        table.insert(new_patterns, p .. chunk)
-      end
-    end
-    patterns = new_patterns
-  end
+  local pathsep = '/'
 
-  local function split(s, sep)
-    local segments = {}
-    local segment = ''
-    local in_braces = false
-    local in_brackets = false
-    for i = 1, #s do
-      local c = string.sub(s, i, i)
-      if c == sep and not in_braces and not in_brackets then
-        table.insert(segments, segment)
-        segment = ''
-      else
-        if c == '{' then
-          in_braces = true
-        elseif c == '}' then
-          in_braces = false
-        elseif c == '[' then
-          in_brackets = true
-        elseif c == ']' then
-          in_brackets = false
-        end
-        segment = segment .. c
-      end
+  local function class(inv, ranges)
+    for i, r in ipairs(ranges) do
+      ranges[i] = r[1] .. r[2]
     end
-    if segment ~= '' then
-      table.insert(segments, segment)
+    local patt = l.R(unpack(ranges))
+    if inv == '!' then
+      patt = P(1) - patt
     end
-    return segments
+    return patt
   end
 
-  local function escape(c)
-    if
-      c == '?'
-      or c == '.'
-      or c == '('
-      or c == ')'
-      or c == '%'
-      or c == '['
-      or c == ']'
-      or c == '*'
-      or c == '+'
-      or c == '-'
-    then
-      return '%' .. c
-    end
-    return c
+  local function add(acc, a)
+    return acc + a
   end
 
-  local segments = split(pattern, '/')
-  for i, segment in ipairs(segments) do
-    local last_seg = i == #segments
-    if segment == '**' then
-      local chunks = {
-        path_sep .. '-',
-        '.-' .. path_sep,
-      }
-      if last_seg then
-        chunks = { '.-' }
-      end
-      append(chunks)
-    else
-      local in_braces = false
-      local brace_val = ''
-      local in_brackets = false
-      local bracket_val = ''
-      for j = 1, #segment do
-        local char = string.sub(segment, j, j)
-        if char ~= '}' and in_braces then
-          brace_val = brace_val .. char
-        else
-          if in_brackets and (char ~= ']' or bracket_val == '') then
-            local res
-            if char == '-' then
-              res = char
-            elseif bracket_val == '' and char == '!' then
-              res = '^'
-            elseif char == '/' then
-              res = ''
-            else
-              res = escape(char)
-            end
-            bracket_val = bracket_val .. res
-          else
-            if char == '{' then
-              in_braces = true
-            elseif char == '[' then
-              in_brackets = true
-            elseif char == '}' then
-              local choices = split(brace_val, ',')
-              local parsed_choices = {}
-              for _, choice in ipairs(choices) do
-                table.insert(parsed_choices, parse(choice))
-              end
-              append(vim.tbl_flatten(parsed_choices))
-              in_braces = false
-              brace_val = ''
-            elseif char == ']' then
-              append({ '[' .. bracket_val .. ']' })
-              in_brackets = false
-              bracket_val = ''
-            elseif char == '?' then
-              append({ non_path_sep })
-            elseif char == '*' then
-              append({ non_path_sep .. '-' })
-            else
-              append({ escape(char) })
-            end
-          end
-        end
-      end
+  local function mul(acc, m)
+    return acc * m
+  end
 
-      if not last_seg and (segments[i + 1] ~= '**' or i + 1 < #segments) then
-        append({ path_sep })
-      end
-    end
+  local function star(stars, after)
+    return (-after * (l.P(1) - pathsep)) ^ #stars * after
   end
 
-  return patterns
+  local function dstar(after)
+    return (-after * l.P(1)) ^ 0 * after
+  end
+
+  local p = P({
+    'Pattern',
+    Pattern = V('Elem') ^ -1 * V('End'),
+    Elem = Cf(
+      (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal'))
+        * (V('Elem') + V('End')),
+      mul
+    ),
+    DStar = P('**') * (P(pathsep) * (V('Elem') + V('End')) + V('End')) / dstar,
+    Star = C(P('*') ^ 1) * (V('Elem') + V('End')) / star,
+    Ques = P('?') * Cc(l.P(1) - pathsep),
+    Class = P('[') * C(P('!') ^ -1) * Ct(Ct(C(1) * '-' * C(P(1) - ']')) ^ 1 * ']') / class,
+    CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * '}',
+    -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same
+    -- wildcard semantics it usually has.
+    -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the
+    -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {}
+    -- condition means "everything after the {}" where several other options separated by ',' may
+    -- exist in between that should not be matched by '*'.
+    Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul)
+      + Cc(l.P(0)),
+    Literal = P(1) / l.P,
+    End = P(-1) * Cc(l.P(-1)),
+  })
+
+  return p:match(pattern)
 end
 
 ---@private
 --- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern
---- Modeled after VSCode's implementation: https://github.com/microsoft/vscode/blob/0319eed971719ad48e9093daba9d65a5013ec5ab/src/vs/base/common/glob.ts#L509
 ---
 ---@param pattern string|table The glob pattern (raw or parsed) to match.
 ---@param s string The string to match against pattern.
@@ -155,15 +84,7 @@ function M._match(pattern, s)
   if type(pattern) == 'string' then
     pattern = parse(pattern)
   end
-  -- Since Lua's built-in string pattern matching does not have an alternate
-  -- operator like '|', `parse` will construct one pattern for each possible
-  -- alternative. Any pattern that matches thus matches the glob.
-  for _, p in ipairs(pattern) do
-    if s:match('^' .. p .. '$') then
-      return true
-    end
-  end
-  return false
+  return pattern:match(s) ~= nil
 end
 
 M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
@@ -226,11 +147,11 @@ function M.register(reg, ctx)
       local kind = w.kind
         or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete
 
-      local pattern = glob_pattern.pattern
+      local pattern = parse(glob_pattern.pattern)
+      assert(pattern, 'invalid pattern: ' .. glob_pattern.pattern)
       if relative_pattern then
-        pattern = base_dir .. '/' .. pattern
+        pattern = lpeg.P(base_dir .. '/') * pattern
       end
-      pattern = parse(pattern)
 
       table.insert(watch_regs, {
         base_dir = base_dir,
-- 
cgit 


From ca26ec34386dfe98b0edf3de9aeb7b66f40d5efd Mon Sep 17 00:00:00 2001
From: Gregory Anders <8965202+gpanders@users.noreply.github.com>
Date: Mon, 5 Jun 2023 08:21:23 -0500
Subject: fix(lsp): use only utf-16 in default client positionEncodings
 (#23903)

The Nvim client does not yet support multiple offset encodings for
clients in the same buffer. Until it does, stick to utf-16 by default.
---
 runtime/lua/vim/lsp/protocol.lua | 2 --
 1 file changed, 2 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 7e49a572e7..7558b5c6ba 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -636,9 +636,7 @@ function protocol.make_client_capabilities()
   return {
     general = {
       positionEncodings = {
-        'utf-8',
         'utf-16',
-        'utf-32',
       },
     },
     textDocument = {
-- 
cgit 


From ca887b80a911df4db4ab5f5496075b9415b9990a Mon Sep 17 00:00:00 2001
From: Gianmaria Bajo 
Date: Tue, 6 Jun 2023 15:38:45 +0200
Subject: fix: version-range < and <= #23539

vim.version.range() couldn't parse them correctly.
For example, vim.version.range('<0.9.0'):has('0.9.0') returned `true`.

fix: range:has() accepts vim.version()
So that it's possible to compare a range with:

    vim.version.range(spec):has(vim.version())
---
 runtime/lua/vim/version.lua | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index ebe8f4e053..d4a3752e37 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -226,8 +226,13 @@ function Range:has(version)
   if type(version) == 'string' then
     ---@diagnostic disable-next-line: cast-local-type
     version = M.parse(version)
+  else
+    -- Need metatable to compare versions.
+    version = setmetatable(vim.deepcopy(version), Version)
   end
   if version then
+    -- Workaround: vim.version() reports "prerelease" as a boolean.
+    version.prerelease = version.prerelease or nil
     if version.prerelease ~= self.from.prerelease then
       return false
     end
@@ -244,11 +249,14 @@ end
 ---   }
 ---   
--- ---- `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`). Example: +--- `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`). +--- +--- Example: ---
lua
 ---   local r = vim.version.range('1.0.0 - 2.0.0')
----   print(r:has('1.9.9'))  -- true
----   print(r:has('2.0.0'))  -- false
+---   print(r:has('1.9.9'))       -- true
+---   print(r:has('2.0.0'))       -- false
+---   print(r:has(vim.version())) -- check against current Nvim version
 ---   
--- --- Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly: @@ -279,7 +287,7 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim }, { __index = Range }) end ---@type string, string - local mods, version = spec:lower():match('^([%^=>~]*)(.*)$') + local mods, version = spec:lower():match('^([%^=<>~]*)(.*)$') version = version:gsub('%.[%*x]', '') local parts = vim.split(version:gsub('%-.*', ''), '.', { plain = true }) if #parts < 3 and mods == '' then @@ -292,6 +300,11 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim local to = vim.deepcopy(semver) if mods == '' or mods == '=' then to.patch = to.patch + 1 + elseif mods == '<' then + from = M._version({}) + elseif mods == '<=' then + from = M._version({}) + to.patch = to.patch + 1 elseif mods == '>' then from.patch = from.patch + 1 to = nil ---@diagnostic disable-line: cast-local-type -- cgit From c48b1421af28d0317c807bca00c7e2fff97d9ad0 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 6 Jun 2023 08:23:20 -0700 Subject: refactor!: rename "playground" => "dev" #23919 Problem: "playground" is new jargon that overlaps with existing concepts: "dev" (`:help dev`) and "view" (also "scratch" `:help scratch-buffer`) . Solution: We should consistently use "dev" as the namespace for where "developer tools" live. For purposes of a "throwaway sandbox object", we can use the name "view". - Rename `TSPlayground` => `TSView` - Rename `playground.lua` => `dev.lua` --- runtime/lua/vim/treesitter.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 441 ++++++++++++++++++++++++++++++ runtime/lua/vim/treesitter/playground.lua | 439 ----------------------------- 3 files changed, 442 insertions(+), 440 deletions(-) create mode 100644 runtime/lua/vim/treesitter/dev.lua delete mode 100644 runtime/lua/vim/treesitter/playground.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 12fbe1654f..5526c9858c 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -493,7 +493,7 @@ end --- argument and should return a string. function M.inspect_tree(opts) ---@cast opts InspectTreeOpts - require('vim.treesitter.playground').inspect_tree(opts) + require('vim.treesitter.dev').inspect_tree(opts) end --- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr': diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua new file mode 100644 index 0000000000..99cd147658 --- /dev/null +++ b/runtime/lua/vim/treesitter/dev.lua @@ -0,0 +1,441 @@ +local api = vim.api + +---@class TSDevModule +local M = {} + +---@class TSTreeView +---@field ns integer API namespace +---@field opts table Options table with the following keys: +--- - anon (boolean): If true, display anonymous nodes +--- - lang (boolean): If true, display the language alongside each node +---@field nodes TSP.Node[] +---@field named TSP.Node[] +local TSTreeView = {} + +---@class TSP.Node +---@field id integer Node id +---@field text string Node text +---@field named boolean True if this is a named (non-anonymous) node +---@field depth integer Depth of the node within the tree +---@field lnum integer Beginning line number of this node in the source buffer +---@field col integer Beginning column number of this node in the source buffer +---@field end_lnum integer Final line number of this node in the source buffer +---@field end_col integer Final column number of this node in the source buffer +---@field lang string Source language of this node +---@field root TSNode + +--- Traverse all child nodes starting at {node}. +--- +--- This is a recursive function. The {depth} parameter indicates the current recursion level. +--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed +--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order +--- they were visited. +--- +--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each +--- injected language has a series of trees nested within the primary language's tree, and the root +--- node of each of these trees is contained within a node in the primary tree. The {injections} +--- table maps nodes in the primary tree to root nodes of injected trees. +--- +---@param node TSNode Starting node to begin traversal |tsnode| +---@param depth integer Current recursion depth +---@param lang string Language of the tree currently being traversed +---@param injections table Mapping of node ids to root nodes of injected language trees (see +--- explanation above) +---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree +---@private +local function traverse(node, depth, lang, injections, tree) + local injection = injections[node:id()] + if injection then + traverse(injection.root, depth, injection.lang, injections, tree) + end + + for child, field in node:iter_children() do + local type = child:type() + local lnum, col, end_lnum, end_col = child:range() + local named = child:named() + local text ---@type string + if named then + if field then + text = string.format('%s: (%s)', field, type) + else + text = string.format('(%s)', type) + end + else + text = string.format('"%s"', type:gsub('\n', '\\n')) + end + + table.insert(tree, { + id = child:id(), + text = text, + named = named, + depth = depth, + lnum = lnum, + col = col, + end_lnum = end_lnum, + end_col = end_col, + lang = lang, + }) + + traverse(child, depth + 1, lang, injections, tree) + end + + return tree +end + +--- Create a new treesitter view. +--- +---@param bufnr integer Source buffer number +---@param lang string|nil Language of source buffer +--- +---@return TSTreeView|nil +---@return string|nil Error message, if any +--- +---@package +function TSTreeView:new(bufnr, lang) + local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) + if not ok then + return nil, 'No parser available for the given buffer' + end + + -- For each child tree (injected language), find the root of the tree and locate the node within + -- the primary tree that contains that root. Add a mapping from the node in the primary tree to + -- the root in the child tree to the {injections} table. + local root = parser:parse()[1]:root() + local injections = {} ---@type table + parser:for_each_child(function(child, lang_) + child:for_each_tree(function(tree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = lang_, + root = r, + } + end + end) + end) + + local nodes = traverse(root, 0, parser:lang(), injections, {}) + + local named = {} ---@type TSP.Node[] + for _, v in ipairs(nodes) do + if v.named then + named[#named + 1] = v + end + end + + local t = { + ns = api.nvim_create_namespace(''), + nodes = nodes, + named = named, + opts = { + anon = false, + lang = false, + }, + } + + setmetatable(t, self) + self.__index = self + return t +end + +local decor_ns = api.nvim_create_namespace('ts.dev') + +---@private +---@param lnum integer +---@param col integer +---@param end_lnum integer +---@param end_col integer +---@return string +local function get_range_str(lnum, col, end_lnum, end_col) + if lnum == end_lnum then + return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col) + end + return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) +end + +--- Write the contents of this View into {bufnr}. +--- +---@param bufnr integer Buffer number to write into. +---@package +function TSTreeView:draw(bufnr) + vim.bo[bufnr].modifiable = true + local lines = {} ---@type string[] + local lang_hl_marks = {} ---@type table[] + + for _, item in self:iter() do + local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) + local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' + local line = + string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) + + if self.opts.lang then + lang_hl_marks[#lang_hl_marks + 1] = { + col = #line - #lang_str, + end_col = #line, + } + end + + lines[#lines + 1] = line + end + + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + + api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) + + for i, m in ipairs(lang_hl_marks) do + api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, { + hl_group = 'Title', + end_col = m.end_col, + }) + end + + vim.bo[bufnr].modifiable = false +end + +--- Get node {i} from this View. +--- +--- The node number is dependent on whether or not anonymous nodes are displayed. +--- +---@param i integer Node number to get +---@return TSP.Node +---@package +function TSTreeView:get(i) + local t = self.opts.anon and self.nodes or self.named + return t[i] +end + +--- Iterate over all of the nodes in this View. +--- +---@return (fun(): integer, TSP.Node) Iterator over all nodes in this View +---@return table +---@return integer +---@package +function TSTreeView:iter() + return ipairs(self.opts.anon and self.nodes or self.named) +end + +--- @class InspectTreeOpts +--- @field lang string? The language of the source buffer. If omitted, the +--- filetype of the source buffer is used. +--- @field bufnr integer? Buffer to draw the tree into. If omitted, a new +--- buffer is created. +--- @field winid integer? Window id to display the tree buffer in. If omitted, +--- a new window is created with {command}. +--- @field command string? Vimscript command to create the window. Default +--- value is "60vnew". Only used when {winid} is nil. +--- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a +--- function, it accepts the buffer number of the source +--- buffer as its only argument and should return a string. + +--- @private +--- +--- @param opts InspectTreeOpts +function M.inspect_tree(opts) + vim.validate({ + opts = { opts, 't', true }, + }) + + opts = opts or {} + + local buf = api.nvim_get_current_buf() + local win = api.nvim_get_current_win() + local pg = assert(TSTreeView:new(buf, opts.lang)) + + -- Close any existing dev window + if vim.b[buf].dev then + local w = vim.b[buf].dev + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end + + local w = opts.winid + if not w then + vim.cmd(opts.command or '60vnew') + w = api.nvim_get_current_win() + end + + local b = opts.bufnr + if b then + api.nvim_win_set_buf(w, b) + else + b = api.nvim_win_get_buf(w) + end + + vim.b[buf].dev = w + + vim.wo[w].scrolloff = 5 + vim.wo[w].wrap = false + vim.wo[w].foldmethod = 'manual' -- disable folding + vim.bo[b].buflisted = false + vim.bo[b].buftype = 'nofile' + vim.bo[b].bufhidden = 'wipe' + vim.b[b].disable_query_linter = true + vim.bo[b].filetype = 'query' + + local title --- @type string? + local opts_title = opts.title + if not opts_title then + local bufname = api.nvim_buf_get_name(buf) + title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) + elseif type(opts_title) == 'function' then + title = opts_title(buf) + end + + assert(type(title) == 'string', 'Window title must be a string') + api.nvim_buf_set_name(b, title) + + pg:draw(b) + + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + api.nvim_buf_set_keymap(b, 'n', '', '', { + desc = 'Jump to the node under the cursor in the source buffer', + callback = function() + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_set_current_win(win) + api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'a', '', { + desc = 'Toggle anonymous nodes', + callback = function() + local row, col = unpack(api.nvim_win_get_cursor(w)) + local curnode = pg:get(row) + while curnode and not curnode.named do + row = row - 1 + curnode = pg:get(row) + end + + pg.opts.anon = not pg.opts.anon + pg:draw(b) + + if not curnode then + return + end + + local id = curnode.id + for i, node in pg:iter() do + if node.id == id then + api.nvim_win_set_cursor(w, { i, col }) + break + end + end + end, + }) + api.nvim_buf_set_keymap(b, 'n', 'I', '', { + desc = 'Toggle language display', + callback = function() + pg.opts.lang = not pg.opts.lang + pg:draw(b) + end, + }) + + local group = api.nvim_create_augroup('treesitter/dev', {}) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + local row = api.nvim_win_get_cursor(w)[1] + local pos = pg:get(row) + api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { + end_row = pos.end_lnum, + end_col = math.max(0, pos.end_col), + hl_group = 'Visual', + }) + + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) + + -- Move the cursor if highlighted range is completely out of view + if pos.lnum < topline and pos.end_lnum < topline then + api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) + elseif pos.lnum > botline and pos.end_lnum > botline then + api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) + end + end, + }) + + api.nvim_create_autocmd('CursorMoved', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + + local cursor_node = vim.treesitter.get_node({ + bufnr = buf, + lang = opts.lang, + ignore_injections = false, + }) + if not cursor_node then + return + end + + local cursor_node_id = cursor_node:id() + for i, v in pg:iter() do + if v.id == cursor_node_id then + local start = v.depth + local end_col = start + #v.text + api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { + end_col = end_col, + hl_group = 'Visual', + }) + api.nvim_win_set_cursor(w, { i, 0 }) + break + end + end + end, + }) + + api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + pg = assert(TSTreeView:new(buf, opts.lang)) + pg:draw(b) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = b, + callback = function() + api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufLeave', { + group = group, + buffer = buf, + callback = function() + if not api.nvim_buf_is_loaded(b) then + return true + end + + api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) + end, + }) + + api.nvim_create_autocmd('BufHidden', { + group = group, + buffer = buf, + once = true, + callback = function() + if api.nvim_win_is_valid(w) then + api.nvim_win_close(w, true) + end + end, + }) +end + +return M diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua deleted file mode 100644 index 8293c1bd0a..0000000000 --- a/runtime/lua/vim/treesitter/playground.lua +++ /dev/null @@ -1,439 +0,0 @@ -local api = vim.api - ----@class TSPlaygroundModule -local M = {} - ----@class TSPlayground ----@field ns integer API namespace ----@field opts table Options table with the following keys: ---- - anon (boolean): If true, display anonymous nodes ---- - lang (boolean): If true, display the language alongside each node ----@field nodes TSP.Node[] ----@field named TSP.Node[] -local TSPlayground = {} - ----@class TSP.Node ----@field id integer Node id ----@field text string Node text ----@field named boolean True if this is a named (non-anonymous) node ----@field depth integer Depth of the node within the tree ----@field lnum integer Beginning line number of this node in the source buffer ----@field col integer Beginning column number of this node in the source buffer ----@field end_lnum integer Final line number of this node in the source buffer ----@field end_col integer Final column number of this node in the source buffer ----@field lang string Source language of this node ----@field root TSNode - ---- Traverse all child nodes starting at {node}. ---- ---- This is a recursive function. The {depth} parameter indicates the current recursion level. ---- {lang} is a string indicating the language of the tree currently being traversed. Each traversed ---- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order ---- they were visited. ---- ---- {injections} is a table mapping node ids from the primary tree to language tree injections. Each ---- injected language has a series of trees nested within the primary language's tree, and the root ---- node of each of these trees is contained within a node in the primary tree. The {injections} ---- table maps nodes in the primary tree to root nodes of injected trees. ---- ----@param node TSNode Starting node to begin traversal |tsnode| ----@param depth integer Current recursion depth ----@param lang string Language of the tree currently being traversed ----@param injections table Mapping of node ids to root nodes of injected language trees (see ---- explanation above) ----@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree ----@private -local function traverse(node, depth, lang, injections, tree) - local injection = injections[node:id()] - if injection then - traverse(injection.root, depth, injection.lang, injections, tree) - end - - for child, field in node:iter_children() do - local type = child:type() - local lnum, col, end_lnum, end_col = child:range() - local named = child:named() - local text ---@type string - if named then - if field then - text = string.format('%s: (%s)', field, type) - else - text = string.format('(%s)', type) - end - else - text = string.format('"%s"', type:gsub('\n', '\\n')) - end - - table.insert(tree, { - id = child:id(), - text = text, - named = named, - depth = depth, - lnum = lnum, - col = col, - end_lnum = end_lnum, - end_col = end_col, - lang = lang, - }) - - traverse(child, depth + 1, lang, injections, tree) - end - - return tree -end - ---- Create a new Playground object. ---- ----@param bufnr integer Source buffer number ----@param lang string|nil Language of source buffer ---- ----@return TSPlayground|nil ----@return string|nil Error message, if any ---- ----@package -function TSPlayground:new(bufnr, lang) - local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) - if not ok then - return nil, 'No parser available for the given buffer' - end - - -- For each child tree (injected language), find the root of the tree and locate the node within - -- the primary tree that contains that root. Add a mapping from the node in the primary tree to - -- the root in the child tree to the {injections} table. - local root = parser:parse()[1]:root() - local injections = {} ---@type table - parser:for_each_child(function(child, lang_) - child:for_each_tree(function(tree) - local r = tree:root() - local node = root:named_descendant_for_range(r:range()) - if node then - injections[node:id()] = { - lang = lang_, - root = r, - } - end - end) - end) - - local nodes = traverse(root, 0, parser:lang(), injections, {}) - - local named = {} ---@type TSP.Node[] - for _, v in ipairs(nodes) do - if v.named then - named[#named + 1] = v - end - end - - local t = { - ns = api.nvim_create_namespace(''), - nodes = nodes, - named = named, - opts = { - anon = false, - lang = false, - }, - } - - setmetatable(t, self) - self.__index = self - return t -end - -local decor_ns = api.nvim_create_namespace('ts.playground') - ----@private ----@param lnum integer ----@param col integer ----@param end_lnum integer ----@param end_col integer ----@return string -local function get_range_str(lnum, col, end_lnum, end_col) - if lnum == end_lnum then - return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col) - end - return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col) -end - ---- Write the contents of this Playground into {bufnr}. ---- ----@param bufnr integer Buffer number to write into. ----@package -function TSPlayground:draw(bufnr) - vim.bo[bufnr].modifiable = true - local lines = {} ---@type string[] - local lang_hl_marks = {} ---@type table[] - - for _, item in self:iter() do - local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col) - local lang_str = self.opts.lang and string.format(' %s', item.lang) or '' - local line = - string.format('%s%s ; %s%s', string.rep(' ', item.depth), item.text, range_str, lang_str) - - if self.opts.lang then - lang_hl_marks[#lang_hl_marks + 1] = { - col = #line - #lang_str, - end_col = #line, - } - end - - lines[#lines + 1] = line - end - - api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) - - api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1) - - for i, m in ipairs(lang_hl_marks) do - api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, { - hl_group = 'Title', - end_col = m.end_col, - }) - end - - vim.bo[bufnr].modifiable = false -end - ---- Get node {i} from this Playground object. ---- ---- The node number is dependent on whether or not anonymous nodes are displayed. ---- ----@param i integer Node number to get ----@return TSP.Node ----@package -function TSPlayground:get(i) - local t = self.opts.anon and self.nodes or self.named - return t[i] -end - ---- Iterate over all of the nodes in this Playground object. ---- ----@return (fun(): integer, TSP.Node) Iterator over all nodes in this Playground ----@return table ----@return integer ----@package -function TSPlayground:iter() - return ipairs(self.opts.anon and self.nodes or self.named) -end - ---- @class InspectTreeOpts ---- @field lang string? The language of the source buffer. If omitted, the ---- filetype of the source buffer is used. ---- @field bufnr integer? Buffer to draw the tree into. If omitted, a new ---- buffer is created. ---- @field winid integer? Window id to display the tree buffer in. If omitted, ---- a new window is created with {command}. ---- @field command string? Vimscript command to create the window. Default ---- value is "60vnew". Only used when {winid} is nil. ---- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a ---- function, it accepts the buffer number of the source ---- buffer as its only argument and should return a string. - ---- @param opts InspectTreeOpts -function M.inspect_tree(opts) - vim.validate({ - opts = { opts, 't', true }, - }) - - opts = opts or {} - - local buf = api.nvim_get_current_buf() - local win = api.nvim_get_current_win() - local pg = assert(TSPlayground:new(buf, opts.lang)) - - -- Close any existing playground window - if vim.b[buf].playground then - local w = vim.b[buf].playground - if api.nvim_win_is_valid(w) then - api.nvim_win_close(w, true) - end - end - - local w = opts.winid - if not w then - vim.cmd(opts.command or '60vnew') - w = api.nvim_get_current_win() - end - - local b = opts.bufnr - if b then - api.nvim_win_set_buf(w, b) - else - b = api.nvim_win_get_buf(w) - end - - vim.b[buf].playground = w - - vim.wo[w].scrolloff = 5 - vim.wo[w].wrap = false - vim.wo[w].foldmethod = 'manual' -- disable folding - vim.bo[b].buflisted = false - vim.bo[b].buftype = 'nofile' - vim.bo[b].bufhidden = 'wipe' - vim.b[b].disable_query_linter = true - vim.bo[b].filetype = 'query' - - local title --- @type string? - local opts_title = opts.title - if not opts_title then - local bufname = api.nvim_buf_get_name(buf) - title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.')) - elseif type(opts_title) == 'function' then - title = opts_title(buf) - end - - assert(type(title) == 'string', 'Window title must be a string') - api.nvim_buf_set_name(b, title) - - pg:draw(b) - - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - api.nvim_buf_set_keymap(b, 'n', '', '', { - desc = 'Jump to the node under the cursor in the source buffer', - callback = function() - local row = api.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) - api.nvim_set_current_win(win) - api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col }) - end, - }) - api.nvim_buf_set_keymap(b, 'n', 'a', '', { - desc = 'Toggle anonymous nodes', - callback = function() - local row, col = unpack(api.nvim_win_get_cursor(w)) - local curnode = pg:get(row) - while curnode and not curnode.named do - row = row - 1 - curnode = pg:get(row) - end - - pg.opts.anon = not pg.opts.anon - pg:draw(b) - - if not curnode then - return - end - - local id = curnode.id - for i, node in pg:iter() do - if node.id == id then - api.nvim_win_set_cursor(w, { i, col }) - break - end - end - end, - }) - api.nvim_buf_set_keymap(b, 'n', 'I', '', { - desc = 'Toggle language display', - callback = function() - pg.opts.lang = not pg.opts.lang - pg:draw(b) - end, - }) - - local group = api.nvim_create_augroup('treesitter/playground', {}) - - api.nvim_create_autocmd('CursorMoved', { - group = group, - buffer = b, - callback = function() - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - local row = api.nvim_win_get_cursor(w)[1] - local pos = pg:get(row) - api.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, { - end_row = pos.end_lnum, - end_col = math.max(0, pos.end_col), - hl_group = 'Visual', - }) - - local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) - - -- Move the cursor if highlighted range is completely out of view - if pos.lnum < topline and pos.end_lnum < topline then - api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 }) - elseif pos.lnum > botline and pos.end_lnum > botline then - api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 }) - end - end, - }) - - api.nvim_create_autocmd('CursorMoved', { - group = group, - buffer = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - - api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - - local cursor_node = vim.treesitter.get_node({ - bufnr = buf, - lang = opts.lang, - ignore_injections = false, - }) - if not cursor_node then - return - end - - local cursor_node_id = cursor_node:id() - for i, v in pg:iter() do - if v.id == cursor_node_id then - local start = v.depth - local end_col = start + #v.text - api.nvim_buf_set_extmark(b, pg.ns, i - 1, start, { - end_col = end_col, - hl_group = 'Visual', - }) - api.nvim_win_set_cursor(w, { i, 0 }) - break - end - end - end, - }) - - api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, { - group = group, - buffer = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - - pg = assert(TSPlayground:new(buf, opts.lang)) - pg:draw(b) - end, - }) - - api.nvim_create_autocmd('BufLeave', { - group = group, - buffer = b, - callback = function() - api.nvim_buf_clear_namespace(buf, pg.ns, 0, -1) - end, - }) - - api.nvim_create_autocmd('BufLeave', { - group = group, - buffer = buf, - callback = function() - if not api.nvim_buf_is_loaded(b) then - return true - end - - api.nvim_buf_clear_namespace(b, pg.ns, 0, -1) - end, - }) - - api.nvim_create_autocmd('BufHidden', { - group = group, - buffer = buf, - once = true, - callback = function() - if api.nvim_win_is_valid(w) then - api.nvim_win_close(w, true) - end - end, - }) -end - -return M -- cgit From 4382d2ed564b80944345785d780cf1b19fb23ba8 Mon Sep 17 00:00:00 2001 From: Alexandre Teoi Date: Tue, 6 Jun 2023 12:42:26 -0300 Subject: feat(health): fold successful healthchecks #22866 Problem: checkhealth can be noisy, but we don't want to omit info. Solution: Fold OK results by default, if 'foldenable' is enabled. Resolves #22796 --- runtime/lua/vim/health.lua | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index ff338b95ea..6e47a22d03 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -2,6 +2,40 @@ local M = {} local s_output = {} +-- 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 + -- From a path return a list [{name}, {func}, {type}] representing a healthcheck local function filepath_to_healthcheck(path) path = vim.fs.normalize(path) -- cgit From 5f4895200a49d92e636dea9c5474ab5b0882384d Mon Sep 17 00:00:00 2001 From: max397574 <81827001+max397574@users.noreply.github.com> Date: Wed, 7 Jun 2023 13:32:39 +0200 Subject: feat(scripts): add lsp_types.lua (#23750) --- runtime/lua/vim/lsp/types/protocol.lua | 4398 ++++++++++++++++++++++++++++++++ 1 file changed, 4398 insertions(+) create mode 100644 runtime/lua/vim/lsp/types/protocol.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/types/protocol.lua b/runtime/lua/vim/lsp/types/protocol.lua new file mode 100644 index 0000000000..4b6660eb51 --- /dev/null +++ b/runtime/lua/vim/lsp/types/protocol.lua @@ -0,0 +1,4398 @@ +--[[ +This file is autogenerated from scripts/lsp_types.lua +Regenerate: +nvim -l scripts/lsp_types.lua gen --runtime/lua/vim/lsp/types/protocol.lua +--]] + +---@alias lsp.null nil +---@alias uinteger integer +---@alias lsp.decimal number +---@alias lsp.DocumentUri string +---@alias lsp.URI string +---@alias lsp.LSPObject table +---@alias lsp.LSPArray lsp.LSPAny[] +---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil + +---@class lsp.ImplementationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---Represents a location inside a resource, such as a line +---inside a text file. +---@class lsp.Location +---@field uri lsp.DocumentUri +---@field range lsp.Range + +---@class lsp.ImplementationRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---@class lsp.TypeDefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---@class lsp.TypeDefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---A workspace folder inside a client. +---@class lsp.WorkspaceFolder +---The associated URI for this workspace folder. +---@field uri lsp.URI +---The name of the workspace folder. Used to refer to this +---workspace folder in the user interface. +---@field name string + +---The parameters of a `workspace/didChangeWorkspaceFolders` notification. +---@class lsp.DidChangeWorkspaceFoldersParams +---The actual workspace folder change event. +---@field event lsp.WorkspaceFoldersChangeEvent + +---The parameters of a configuration request. +---@class lsp.ConfigurationParams +---@field items lsp.ConfigurationItem[] + +---Parameters for a {@link DocumentColorRequest}. +---@class lsp.DocumentColorParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier + +---Represents a color range from a document. +---@class lsp.ColorInformation +---The range in the document where this color appears. +---@field range lsp.Range +---The actual color value for this color range. +---@field color lsp.Color + +---@class lsp.DocumentColorRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---Parameters for a {@link ColorPresentationRequest}. +---@class lsp.ColorPresentationParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The color to request presentations for. +---@field color lsp.Color +---The range where the color would be inserted. Serves as a context. +---@field range lsp.Range + +---@class lsp.ColorPresentation +---The label of this color presentation. It will be shown on the color +---picker header. By default this is also the text that is inserted when selecting +---this color presentation. +---@field label string +---An {@link TextEdit edit} which is applied to a document when selecting +---this presentation for the color. When `falsy` the {@link ColorPresentation.label label} +---is used. +---@field textEdit? lsp.TextEdit +---An optional array of additional {@link TextEdit text edits} that are applied when +---selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves. +---@field additionalTextEdits? lsp.TextEdit[] + +---@class lsp.WorkDoneProgressOptions +---@field workDoneProgress? boolean + +---General text document registration options. +---@class lsp.TextDocumentRegistrationOptions +---A document selector to identify the scope of the registration. If set to null +---the document selector provided on the client side will be used. +---@field documentSelector lsp.DocumentSelector|lsp.null + +---Parameters for a {@link FoldingRangeRequest}. +---@class lsp.FoldingRangeParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier + +---Represents a folding range. To be valid, start and end line must be bigger than zero and smaller +---than the number of lines in the document. Clients are free to ignore invalid ranges. +---@class lsp.FoldingRange +---The zero-based start line of the range to fold. The folded area starts after the line's last character. +---To be valid, the end must be zero or larger and smaller than the number of lines in the document. +---@field startLine uinteger +---The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line. +---@field startCharacter? uinteger +---The zero-based end line of the range to fold. The folded area ends with the line's last character. +---To be valid, the end must be zero or larger and smaller than the number of lines in the document. +---@field endLine uinteger +---The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line. +---@field endCharacter? uinteger +---Describes the kind of the folding range such as `comment' or 'region'. The kind +---is used to categorize folding ranges and used by commands like 'Fold all comments'. +---See {@link FoldingRangeKind} for an enumeration of standardized kinds. +---@field kind? lsp.FoldingRangeKind +---The text that the client should show when the specified range is +---collapsed. If not defined or not supported by the client, a default +---will be chosen by the client. +--- +---@since 3.17.0 +---@field collapsedText? string + +---@class lsp.FoldingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---@class lsp.DeclarationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---@class lsp.DeclarationRegistrationOptions: lsp.DeclarationOptions, lsp.StaticRegistrationOptions + +---A parameter literal used in selection range requests. +---@class lsp.SelectionRangeParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The positions inside the text document. +---@field positions lsp.Position[] + +---A selection range represents a part of a selection hierarchy. A selection range +---may have a parent selection range that contains it. +---@class lsp.SelectionRange +---The {@link Range range} of this selection range. +---@field range lsp.Range +---The parent selection range containing this range. Therefore `parent.range` must contain `this.range`. +---@field parent? lsp.SelectionRange + +---@class lsp.SelectionRangeRegistrationOptions: lsp.SelectionRangeOptions, lsp.StaticRegistrationOptions + +---@class lsp.WorkDoneProgressCreateParams +---The token to be used to report progress. +---@field token lsp.ProgressToken + +---@class lsp.WorkDoneProgressCancelParams +---The token to be used to report progress. +---@field token lsp.ProgressToken + +---The parameter of a `textDocument/prepareCallHierarchy` request. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyPrepareParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams + +---Represents programming constructs like functions or constructors in the context +---of call hierarchy. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyItem +---The name of this item. +---@field name string +---The kind of this item. +---@field kind lsp.SymbolKind +---Tags for this item. +---@field tags? lsp.SymbolTag[] +---More detail for this item, e.g. the signature of a function. +---@field detail? string +---The resource identifier of this item. +---@field uri lsp.DocumentUri +---The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code. +---@field range lsp.Range +---The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. +---Must be contained by the {@link CallHierarchyItem.range `range`}. +---@field selectionRange lsp.Range +---A data entry field that is preserved between a call hierarchy prepare and +---incoming calls or outgoing calls requests. +---@field data? lsp.LSPAny + +---Call hierarchy options used during static or dynamic registration. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---The parameter of a `callHierarchy/incomingCalls` request. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyIncomingCallsParams +---@field item lsp.CallHierarchyItem + +---Represents an incoming call, e.g. a caller of a method or constructor. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyIncomingCall +---The item that makes the call. +---@field from lsp.CallHierarchyItem +---The ranges at which the calls appear. This is relative to the caller +---denoted by {@link CallHierarchyIncomingCall.from `this.from`}. +---@field fromRanges lsp.Range[] + +---The parameter of a `callHierarchy/outgoingCalls` request. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyOutgoingCallsParams +---@field item lsp.CallHierarchyItem + +---Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyOutgoingCall +---The item that is called. +---@field to lsp.CallHierarchyItem +---The range at which this item is called. This is the range relative to the caller, e.g the item +---passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`} +---and not {@link CallHierarchyOutgoingCall.to `this.to`}. +---@field fromRanges lsp.Range[] + +---@since 3.16.0 +---@class lsp.SemanticTokensParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier + +---@since 3.16.0 +---@class lsp.SemanticTokens +---An optional result id. If provided and clients support delta updating +---the client will include the result id in the next semantic token request. +---A server can then instead of computing all semantic tokens again simply +---send a delta. +---@field resultId? string +---The actual tokens. +---@field data uinteger[] + +---@since 3.16.0 +---@class lsp.SemanticTokensPartialResult +---@field data uinteger[] + +---@since 3.16.0 +---@class lsp.SemanticTokensRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---@since 3.16.0 +---@class lsp.SemanticTokensDeltaParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The result id of a previous response. The result Id can either point to a full response +---or a delta response depending on what was received last. +---@field previousResultId string + +---@since 3.16.0 +---@class lsp.SemanticTokensDelta +---@field resultId? string +---The semantic token edits to transform a previous result into a new result. +---@field edits lsp.SemanticTokensEdit[] + +---@since 3.16.0 +---@class lsp.SemanticTokensDeltaPartialResult +---@field edits lsp.SemanticTokensEdit[] + +---@since 3.16.0 +---@class lsp.SemanticTokensRangeParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The range the semantic tokens are requested for. +---@field range lsp.Range + +---Params to show a resource in the UI. +--- +---@since 3.16.0 +---@class lsp.ShowDocumentParams +---The uri to show. +---@field uri lsp.URI +---Indicates to show the resource in an external program. +---To show, for example, `https://code.visualstudio.com/` +---in the default WEB browser set `external` to `true`. +---@field external? boolean +---An optional property to indicate whether the editor +---showing the document should take focus or not. +---Clients might ignore this property if an external +---program is started. +---@field takeFocus? boolean +---An optional selection range if the document is a text +---document. Clients might ignore the property if an +---external program is started or the file is not a text +---file. +---@field selection? lsp.Range + +---The result of a showDocument request. +--- +---@since 3.16.0 +---@class lsp.ShowDocumentResult +---A boolean indicating if the show was successful. +---@field success boolean + +---@class lsp.LinkedEditingRangeParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams + +---The result of a linked editing range request. +--- +---@since 3.16.0 +---@class lsp.LinkedEditingRanges +---A list of ranges that can be edited together. The ranges must have +---identical length and contain identical text content. The ranges cannot overlap. +---@field ranges lsp.Range[] +---An optional word pattern (regular expression) that describes valid contents for +---the given ranges. If no pattern is provided, the client configuration's word +---pattern will be used. +---@field wordPattern? string + +---@class lsp.LinkedEditingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---The parameters sent in notifications/requests for user-initiated creation of +---files. +--- +---@since 3.16.0 +---@class lsp.CreateFilesParams +---An array of all files/folders created in this operation. +---@field files lsp.FileCreate[] + +---A workspace edit represents changes to many resources managed in the workspace. The edit +---should either provide `changes` or `documentChanges`. If documentChanges are present +---they are preferred over `changes` if the client can handle versioned document edits. +--- +---Since version 3.13.0 a workspace edit can contain resource operations as well. If resource +---operations are present clients need to execute the operations in the order in which they +---are provided. So a workspace edit for example can consist of the following two changes: +---(1) a create file a.txt and (2) a text document edit which insert text into file a.txt. +--- +---An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will +---cause failure of the operation. How the client recovers from the failure is described by +---the client capability: `workspace.workspaceEdit.failureHandling` +---@class lsp.WorkspaceEdit +---Holds changes to existing resources. +---@field changes? table +---Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes +---are either an array of `TextDocumentEdit`s to express changes to n different text documents +---where each text document edit addresses a specific version of a text document. Or it can contain +---above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. +--- +---Whether a client supports versioned document edits is expressed via +---`workspace.workspaceEdit.documentChanges` client capability. +--- +---If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then +---only plain `TextEdit`s using the `changes` property are supported. +---@field documentChanges? lsp.TextDocumentEdit|lsp.CreateFile|lsp.RenameFile|lsp.DeleteFile[] +---A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and +---delete file / folder operations. +--- +---Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`. +--- +---@since 3.16.0 +---@field changeAnnotations? table + +---The options to register for file operations. +--- +---@since 3.16.0 +---@class lsp.FileOperationRegistrationOptions +---The actual filters. +---@field filters lsp.FileOperationFilter[] + +---The parameters sent in notifications/requests for user-initiated renames of +---files. +--- +---@since 3.16.0 +---@class lsp.RenameFilesParams +---An array of all files/folders renamed in this operation. When a folder is renamed, only +---the folder will be included, and not its children. +---@field files lsp.FileRename[] + +---The parameters sent in notifications/requests for user-initiated deletes of +---files. +--- +---@since 3.16.0 +---@class lsp.DeleteFilesParams +---An array of all files/folders deleted in this operation. +---@field files lsp.FileDelete[] + +---@class lsp.MonikerParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---Moniker definition to match LSIF 0.5 moniker definition. +--- +---@since 3.16.0 +---@class lsp.Moniker +---The scheme of the moniker. For example tsc or .Net +---@field scheme string +---The identifier of the moniker. The value is opaque in LSIF however +---schema owners are allowed to define the structure if they want. +---@field identifier string +---The scope in which the moniker is unique +---@field unique lsp.UniquenessLevel +---The moniker kind if known. +---@field kind? lsp.MonikerKind + +---@class lsp.MonikerRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameter of a `textDocument/prepareTypeHierarchy` request. +--- +---@since 3.17.0 +---@class lsp.TypeHierarchyPrepareParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams + +---@since 3.17.0 +---@class lsp.TypeHierarchyItem +---The name of this item. +---@field name string +---The kind of this item. +---@field kind lsp.SymbolKind +---Tags for this item. +---@field tags? lsp.SymbolTag[] +---More detail for this item, e.g. the signature of a function. +---@field detail? string +---The resource identifier of this item. +---@field uri lsp.DocumentUri +---The range enclosing this symbol not including leading/trailing whitespace +---but everything else, e.g. comments and code. +---@field range lsp.Range +---The range that should be selected and revealed when this symbol is being +---picked, e.g. the name of a function. Must be contained by the +---{@link TypeHierarchyItem.range `range`}. +---@field selectionRange lsp.Range +---A data entry field that is preserved between a type hierarchy prepare and +---supertypes or subtypes requests. It could also be used to identify the +---type hierarchy in the server, helping improve the performance on +---resolving supertypes and subtypes. +---@field data? lsp.LSPAny + +---Type hierarchy options used during static or dynamic registration. +--- +---@since 3.17.0 +---@class lsp.TypeHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---The parameter of a `typeHierarchy/supertypes` request. +--- +---@since 3.17.0 +---@class lsp.TypeHierarchySupertypesParams +---@field item lsp.TypeHierarchyItem + +---The parameter of a `typeHierarchy/subtypes` request. +--- +---@since 3.17.0 +---@class lsp.TypeHierarchySubtypesParams +---@field item lsp.TypeHierarchyItem + +---A parameter literal used in inline value requests. +--- +---@since 3.17.0 +---@class lsp.InlineValueParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The document range for which inline values should be computed. +---@field range lsp.Range +---Additional information about the context in which inline values were +---requested. +---@field context lsp.InlineValueContext + +---Inline value options used during static or dynamic registration. +--- +---@since 3.17.0 +---@class lsp.InlineValueRegistrationOptions: lsp.InlineValueOptions, lsp.StaticRegistrationOptions + +---A parameter literal used in inlay hint requests. +--- +---@since 3.17.0 +---@class lsp.InlayHintParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The document range for which inlay hints should be computed. +---@field range lsp.Range + +---Inlay hint information. +--- +---@since 3.17.0 +---@class lsp.InlayHint +---The position of this hint. +---@field position lsp.Position +---The label of this hint. A human readable string or an array of +---InlayHintLabelPart label parts. +--- +---*Note* that neither the string nor the label part can be empty. +---@field label string|lsp.InlayHintLabelPart[] +---The kind of this hint. Can be omitted in which case the client +---should fall back to a reasonable default. +---@field kind? lsp.InlayHintKind +---Optional text edits that are performed when accepting this inlay hint. +--- +---*Note* that edits are expected to change the document so that the inlay +---hint (or its nearest variant) is now part of the document and the inlay +---hint itself is now obsolete. +---@field textEdits? lsp.TextEdit[] +---The tooltip text when you hover over this item. +---@field tooltip? string|lsp.MarkupContent +---Render padding before the hint. +--- +---Note: Padding should use the editor's background color, not the +---background color of the hint itself. That means padding can be used +---to visually align/separate an inlay hint. +---@field paddingLeft? boolean +---Render padding after the hint. +--- +---Note: Padding should use the editor's background color, not the +---background color of the hint itself. That means padding can be used +---to visually align/separate an inlay hint. +---@field paddingRight? boolean +---A data entry field that is preserved on an inlay hint between +---a `textDocument/inlayHint` and a `inlayHint/resolve` request. +---@field data? lsp.LSPAny + +---Inlay hint options used during static or dynamic registration. +--- +---@since 3.17.0 +---@class lsp.InlayHintRegistrationOptions: lsp.InlayHintOptions, lsp.StaticRegistrationOptions + +---Parameters of the document diagnostic request. +--- +---@since 3.17.0 +---@class lsp.DocumentDiagnosticParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The additional identifier provided during registration. +---@field identifier? string +---The result id of a previous response if provided. +---@field previousResultId? string + +---A partial result for a document diagnostic report. +--- +---@since 3.17.0 +---@class lsp.DocumentDiagnosticReportPartialResult +---@field relatedDocuments table + +---Cancellation data returned from a diagnostic request. +--- +---@since 3.17.0 +---@class lsp.DiagnosticServerCancellationData +---@field retriggerRequest boolean + +---Diagnostic registration options. +--- +---@since 3.17.0 +---@class lsp.DiagnosticRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions + +---Parameters of the workspace diagnostic request. +--- +---@since 3.17.0 +---@class lsp.WorkspaceDiagnosticParams +---The additional identifier provided during registration. +---@field identifier? string +---The currently known diagnostic reports with their +---previous result ids. +---@field previousResultIds lsp.PreviousResultId[] + +---A workspace diagnostic report. +--- +---@since 3.17.0 +---@class lsp.WorkspaceDiagnosticReport +---@field items lsp.WorkspaceDocumentDiagnosticReport[] + +---A partial result for a workspace diagnostic report. +--- +---@since 3.17.0 +---@class lsp.WorkspaceDiagnosticReportPartialResult +---@field items lsp.WorkspaceDocumentDiagnosticReport[] + +---The params sent in an open notebook document notification. +--- +---@since 3.17.0 +---@class lsp.DidOpenNotebookDocumentParams +---The notebook document that got opened. +---@field notebookDocument lsp.NotebookDocument +---The text documents that represent the content +---of a notebook cell. +---@field cellTextDocuments lsp.TextDocumentItem[] + +---The params sent in a change notebook document notification. +--- +---@since 3.17.0 +---@class lsp.DidChangeNotebookDocumentParams +---The notebook document that did change. The version number points +---to the version after all provided changes have been applied. If +---only the text document content of a cell changes the notebook version +---doesn't necessarily have to change. +---@field notebookDocument lsp.VersionedNotebookDocumentIdentifier +---The actual changes to the notebook document. +--- +---The changes describe single state changes to the notebook document. +---So if there are two changes c1 (at array index 0) and c2 (at array +---index 1) for a notebook in state S then c1 moves the notebook from +---S to S' and c2 from S' to S''. So c1 is computed on the state S and +---c2 is computed on the state S'. +--- +---To mirror the content of a notebook using change events use the following approach: +---- start with the same initial content +---- apply the 'notebookDocument/didChange' notifications in the order you receive them. +---- apply the `NotebookChangeEvent`s in a single notification in the order +--- you receive them. +---@field change lsp.NotebookDocumentChangeEvent + +---The params sent in a save notebook document notification. +--- +---@since 3.17.0 +---@class lsp.DidSaveNotebookDocumentParams +---The notebook document that got saved. +---@field notebookDocument lsp.NotebookDocumentIdentifier + +---The params sent in a close notebook document notification. +--- +---@since 3.17.0 +---@class lsp.DidCloseNotebookDocumentParams +---The notebook document that got closed. +---@field notebookDocument lsp.NotebookDocumentIdentifier +---The text documents that represent the content +---of a notebook cell that got closed. +---@field cellTextDocuments lsp.TextDocumentIdentifier[] + +---A parameter literal used in inline completion requests. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams +---Additional information about the context in which inline completions were +---requested. +---@field context lsp.InlineCompletionContext + +---Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionList +---The inline completion items +---@field items lsp.InlineCompletionItem[] + +---An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionItem +---The text to replace the range with. Must be set. +---@field insertText string|lsp.StringValue +---A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used. +---@field filterText? string +---The range to replace. Must begin and end on the same line. +---@field range? lsp.Range +---An optional {@link Command} that is executed *after* inserting this completion. +---@field command? lsp.Command + +---Inline completion options used during static or dynamic registration. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionRegistrationOptions: lsp.InlineCompletionOptions, lsp.StaticRegistrationOptions + +---@class lsp.RegistrationParams +---@field registrations lsp.Registration[] + +---@class lsp.UnregistrationParams +---@field unregisterations lsp.Unregistration[] + +---@class lsp.InitializeParams: lsp._InitializeParams + +---The result returned from an initialize request. +---@class lsp.InitializeResult +---The capabilities the language server provides. +---@field capabilities lsp.ServerCapabilities +---Information about the server. +--- +---@since 3.15.0 +---@field serverInfo? anonym1 + +---The data type of the ResponseError if the +---initialize request fails. +---@class lsp.InitializeError +---Indicates whether the client execute the following retry logic: +---(1) show the message provided by the ResponseError to the user +---(2) user selects retry or cancel +---(3) if user selected retry the initialize method is sent again. +---@field retry boolean + +---@class lsp.InitializedParams + +---The parameters of a change configuration notification. +---@class lsp.DidChangeConfigurationParams +---The actual changed settings +---@field settings lsp.LSPAny + +---@class lsp.DidChangeConfigurationRegistrationOptions +---@field section? string|string[] + +---The parameters of a notification message. +---@class lsp.ShowMessageParams +---The message type. See {@link MessageType} +---@field type lsp.MessageType +---The actual message. +---@field message string + +---@class lsp.ShowMessageRequestParams +---The message type. See {@link MessageType} +---@field type lsp.MessageType +---The actual message. +---@field message string +---The message action items to present. +---@field actions? lsp.MessageActionItem[] + +---@class lsp.MessageActionItem +---A short title like 'Retry', 'Open Log' etc. +---@field title string + +---The log message parameters. +---@class lsp.LogMessageParams +---The message type. See {@link MessageType} +---@field type lsp.MessageType +---The actual message. +---@field message string + +---The parameters sent in an open text document notification +---@class lsp.DidOpenTextDocumentParams +---The document that was opened. +---@field textDocument lsp.TextDocumentItem + +---The change text document notification's parameters. +---@class lsp.DidChangeTextDocumentParams +---The document that did change. The version number points +---to the version after all provided content changes have +---been applied. +---@field textDocument lsp.VersionedTextDocumentIdentifier +---The actual content changes. The content changes describe single state changes +---to the document. So if there are two content changes c1 (at array index 0) and +---c2 (at array index 1) for a document in state S then c1 moves the document from +---S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed +---on the state S'. +--- +---To mirror the content of a document using change events use the following approach: +---- start with the same initial content +---- apply the 'textDocument/didChange' notifications in the order you receive them. +---- apply the `TextDocumentContentChangeEvent`s in a single notification in the order +--- you receive them. +---@field contentChanges lsp.TextDocumentContentChangeEvent[] + +---Describe options to be used when registered for text document change events. +---@class lsp.TextDocumentChangeRegistrationOptions: lsp.TextDocumentRegistrationOptions +---How documents are synced to the server. +---@field syncKind lsp.TextDocumentSyncKind + +---The parameters sent in a close text document notification +---@class lsp.DidCloseTextDocumentParams +---The document that was closed. +---@field textDocument lsp.TextDocumentIdentifier + +---The parameters sent in a save text document notification +---@class lsp.DidSaveTextDocumentParams +---The document that was saved. +---@field textDocument lsp.TextDocumentIdentifier +---Optional the content when saved. Depends on the includeText value +---when the save notification was requested. +---@field text? string + +---Save registration options. +---@class lsp.TextDocumentSaveRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters sent in a will save text document notification. +---@class lsp.WillSaveTextDocumentParams +---The document that will be saved. +---@field textDocument lsp.TextDocumentIdentifier +---The 'TextDocumentSaveReason'. +---@field reason lsp.TextDocumentSaveReason + +---A text edit applicable to a text document. +---@class lsp.TextEdit +---The range of the text document to be manipulated. To insert +---text into a document create a range where start === end. +---@field range lsp.Range +---The string to be inserted. For delete operations use an +---empty string. +---@field newText string + +---The watched files change notification's parameters. +---@class lsp.DidChangeWatchedFilesParams +---The actual file events. +---@field changes lsp.FileEvent[] + +---Describe options to be used when registered for text document change events. +---@class lsp.DidChangeWatchedFilesRegistrationOptions +---The watchers to register. +---@field watchers lsp.FileSystemWatcher[] + +---The publish diagnostic notification's parameters. +---@class lsp.PublishDiagnosticsParams +---The URI for which diagnostic information is reported. +---@field uri lsp.DocumentUri +---Optional the version number of the document the diagnostics are published for. +--- +---@since 3.15.0 +---@field version? integer +---An array of diagnostic information items. +---@field diagnostics lsp.Diagnostic[] + +---Completion parameters +---@class lsp.CompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams +---The completion context. This is only available it the client specifies +---to send this using the client capability `textDocument.completion.contextSupport === true` +---@field context? lsp.CompletionContext + +---A completion item represents a text snippet that is +---proposed to complete text that is being typed. +---@class lsp.CompletionItem +---The label of this completion item. +--- +---The label property is also by default the text that +---is inserted when selecting this completion. +--- +---If label details are provided the label itself should +---be an unqualified name of the completion item. +---@field label string +---Additional details for the label +--- +---@since 3.17.0 +---@field labelDetails? lsp.CompletionItemLabelDetails +---The kind of this completion item. Based of the kind +---an icon is chosen by the editor. +---@field kind? lsp.CompletionItemKind +---Tags for this completion item. +--- +---@since 3.15.0 +---@field tags? lsp.CompletionItemTag[] +---A human-readable string with additional information +---about this item, like type or symbol information. +---@field detail? string +---A human-readable string that represents a doc-comment. +---@field documentation? string|lsp.MarkupContent +---Indicates if this item is deprecated. +---@deprecated Use `tags` instead. +---@field deprecated? boolean +---Select this item when showing. +--- +---*Note* that only one completion item can be selected and that the +---tool / client decides which item that is. The rule is that the *first* +---item of those that match best is selected. +---@field preselect? boolean +---A string that should be used when comparing this item +---with other items. When `falsy` the {@link CompletionItem.label label} +---is used. +---@field sortText? string +---A string that should be used when filtering a set of +---completion items. When `falsy` the {@link CompletionItem.label label} +---is used. +---@field filterText? string +---A string that should be inserted into a document when selecting +---this completion. When `falsy` the {@link CompletionItem.label label} +---is used. +--- +---The `insertText` is subject to interpretation by the client side. +---Some tools might not take the string literally. For example +---VS Code when code complete is requested in this example +---`con` and a completion item with an `insertText` of +---`console` is provided it will only insert `sole`. Therefore it is +---recommended to use `textEdit` instead since it avoids additional client +---side interpretation. +---@field insertText? string +---The format of the insert text. The format applies to both the +---`insertText` property and the `newText` property of a provided +---`textEdit`. If omitted defaults to `InsertTextFormat.PlainText`. +--- +---Please note that the insertTextFormat doesn't apply to +---`additionalTextEdits`. +---@field insertTextFormat? lsp.InsertTextFormat +---How whitespace and indentation is handled during completion +---item insertion. If not provided the clients default value depends on +---the `textDocument.completion.insertTextMode` client capability. +--- +---@since 3.16.0 +---@field insertTextMode? lsp.InsertTextMode +---An {@link TextEdit edit} which is applied to a document when selecting +---this completion. When an edit is provided the value of +---{@link CompletionItem.insertText insertText} is ignored. +--- +---Most editors support two different operations when accepting a completion +---item. One is to insert a completion text and the other is to replace an +---existing text with a completion text. Since this can usually not be +---predetermined by a server it can report both ranges. Clients need to +---signal support for `InsertReplaceEdits` via the +---`textDocument.completion.insertReplaceSupport` client capability +---property. +--- +---*Note 1:* The text edit's range as well as both ranges from an insert +---replace edit must be a [single line] and they must contain the position +---at which completion has been requested. +---*Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range +---must be a prefix of the edit's replace range, that means it must be +---contained and starting at the same position. +--- +---@since 3.16.0 additional type `InsertReplaceEdit` +---@field textEdit? lsp.TextEdit|lsp.InsertReplaceEdit +---The edit text used if the completion item is part of a CompletionList and +---CompletionList defines an item default for the text edit range. +--- +---Clients will only honor this property if they opt into completion list +---item defaults using the capability `completionList.itemDefaults`. +--- +---If not provided and a list's default range is provided the label +---property is used as a text. +--- +---@since 3.17.0 +---@field textEditText? string +---An optional array of additional {@link TextEdit text edits} that are applied when +---selecting this completion. Edits must not overlap (including the same insert position) +---with the main {@link CompletionItem.textEdit edit} nor with themselves. +--- +---Additional text edits should be used to change text unrelated to the current cursor position +---(for example adding an import statement at the top of the file if the completion item will +---insert an unqualified type). +---@field additionalTextEdits? lsp.TextEdit[] +---An optional set of characters that when pressed while this completion is active will accept it first and +---then type that character. *Note* that all commit characters should have `length=1` and that superfluous +---characters will be ignored. +---@field commitCharacters? string[] +---An optional {@link Command command} that is executed *after* inserting this completion. *Note* that +---additional modifications to the current document should be described with the +---{@link CompletionItem.additionalTextEdits additionalTextEdits}-property. +---@field command? lsp.Command +---A data entry field that is preserved on a completion item between a +---{@link CompletionRequest} and a {@link CompletionResolveRequest}. +---@field data? lsp.LSPAny + +---Represents a collection of {@link CompletionItem completion items} to be presented +---in the editor. +---@class lsp.CompletionList +---This list it not complete. Further typing results in recomputing this list. +--- +---Recomputed lists have all their items replaced (not appended) in the +---incomplete completion sessions. +---@field isIncomplete boolean +---In many cases the items of an actual completion result share the same +---value for properties like `commitCharacters` or the range of a text +---edit. A completion list can therefore define item defaults which will +---be used if a completion item itself doesn't specify the value. +--- +---If a completion list specifies a default value and a completion item +---also specifies a corresponding value the one from the item is used. +--- +---Servers are only allowed to return default values if the client +---signals support for this via the `completionList.itemDefaults` +---capability. +--- +---@since 3.17.0 +---@field itemDefaults? anonym3 +---The completion items. +---@field items lsp.CompletionItem[] + +---Registration options for a {@link CompletionRequest}. +---@class lsp.CompletionRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link HoverRequest}. +---@class lsp.HoverParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams + +---The result of a hover request. +---@class lsp.Hover +---The hover's content +---@field contents lsp.MarkupContent|lsp.MarkedString|lsp.MarkedString[] +---An optional range inside the text document that is used to +---visualize the hover, e.g. by changing the background color. +---@field range? lsp.Range + +---Registration options for a {@link HoverRequest}. +---@class lsp.HoverRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link SignatureHelpRequest}. +---@class lsp.SignatureHelpParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams +---The signature help context. This is only available if the client specifies +---to send this using the client capability `textDocument.signatureHelp.contextSupport === true` +--- +---@since 3.15.0 +---@field context? lsp.SignatureHelpContext + +---Signature help represents the signature of something +---callable. There can be multiple signature but only one +---active and only one active parameter. +---@class lsp.SignatureHelp +---One or more signatures. +---@field signatures lsp.SignatureInformation[] +---The active signature. If omitted or the value lies outside the +---range of `signatures` the value defaults to zero or is ignored if +---the `SignatureHelp` has no signatures. +--- +---Whenever possible implementors should make an active decision about +---the active signature and shouldn't rely on a default value. +--- +---In future version of the protocol this property might become +---mandatory to better express this. +---@field activeSignature? uinteger +---The active parameter of the active signature. If omitted or the value +---lies outside the range of `signatures[activeSignature].parameters` +---defaults to 0 if the active signature has parameters. If +---the active signature has no parameters it is ignored. +---In future version of the protocol this property might become +---mandatory to better express the active parameter if the +---active signature does have any. +---@field activeParameter? uinteger + +---Registration options for a {@link SignatureHelpRequest}. +---@class lsp.SignatureHelpRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link DefinitionRequest}. +---@class lsp.DefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---Registration options for a {@link DefinitionRequest}. +---@class lsp.DefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link ReferencesRequest}. +---@class lsp.ReferenceParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams +---@field context lsp.ReferenceContext + +---Registration options for a {@link ReferencesRequest}. +---@class lsp.ReferenceRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link DocumentHighlightRequest}. +---@class lsp.DocumentHighlightParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams + +---A document highlight is a range inside a text document which deserves +---special attention. Usually a document highlight is visualized by changing +---the background color of its range. +---@class lsp.DocumentHighlight +---The range this highlight applies to. +---@field range lsp.Range +---The highlight kind, default is {@link DocumentHighlightKind.Text text}. +---@field kind? lsp.DocumentHighlightKind + +---Registration options for a {@link DocumentHighlightRequest}. +---@class lsp.DocumentHighlightRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---Parameters for a {@link DocumentSymbolRequest}. +---@class lsp.DocumentSymbolParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier + +---Represents information about programming constructs like variables, classes, +---interfaces etc. +---@class lsp.SymbolInformation: lsp.BaseSymbolInformation +---Indicates if this symbol is deprecated. +--- +---@deprecated Use tags instead +---@field deprecated? boolean +---The location of this symbol. The location's range is used by a tool +---to reveal the location in the editor. If the symbol is selected in the +---tool the range's start information is used to position the cursor. So +---the range usually spans more than the actual symbol's name and does +---normally include things like visibility modifiers. +--- +---The range doesn't have to denote a node range in the sense of an abstract +---syntax tree. It can therefore not be used to re-construct a hierarchy of +---the symbols. +---@field location lsp.Location + +---Represents programming constructs like variables, classes, interfaces etc. +---that appear in a document. Document symbols can be hierarchical and they +---have two ranges: one that encloses its definition and one that points to +---its most interesting range, e.g. the range of an identifier. +---@class lsp.DocumentSymbol +---The name of this symbol. Will be displayed in the user interface and therefore must not be +---an empty string or a string only consisting of white spaces. +---@field name string +---More detail for this symbol, e.g the signature of a function. +---@field detail? string +---The kind of this symbol. +---@field kind lsp.SymbolKind +---Tags for this document symbol. +--- +---@since 3.16.0 +---@field tags? lsp.SymbolTag[] +---Indicates if this symbol is deprecated. +--- +---@deprecated Use tags instead +---@field deprecated? boolean +---The range enclosing this symbol not including leading/trailing whitespace but everything else +---like comments. This information is typically used to determine if the clients cursor is +---inside the symbol to reveal in the symbol in the UI. +---@field range lsp.Range +---The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. +---Must be contained by the `range`. +---@field selectionRange lsp.Range +---Children of this symbol, e.g. properties of a class. +---@field children? lsp.DocumentSymbol[] + +---Registration options for a {@link DocumentSymbolRequest}. +---@class lsp.DocumentSymbolRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link CodeActionRequest}. +---@class lsp.CodeActionParams +---The document in which the command was invoked. +---@field textDocument lsp.TextDocumentIdentifier +---The range for which the command was invoked. +---@field range lsp.Range +---Context carrying additional information. +---@field context lsp.CodeActionContext + +---Represents a reference to a command. Provides a title which +---will be used to represent a command in the UI and, optionally, +---an array of arguments which will be passed to the command handler +---function when invoked. +---@class lsp.Command +---Title of the command, like `save`. +---@field title string +---The identifier of the actual command handler. +---@field command string +---Arguments that the command handler should be +---invoked with. +---@field arguments? lsp.LSPAny[] + +---A code action represents a change that can be performed in code, e.g. to fix a problem or +---to refactor code. +--- +---A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. +---@class lsp.CodeAction +---A short, human-readable, title for this code action. +---@field title string +---The kind of the code action. +--- +---Used to filter code actions. +---@field kind? lsp.CodeActionKind +---The diagnostics that this code action resolves. +---@field diagnostics? lsp.Diagnostic[] +---Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted +---by keybindings. +--- +---A quick fix should be marked preferred if it properly addresses the underlying error. +---A refactoring should be marked preferred if it is the most reasonable choice of actions to take. +--- +---@since 3.15.0 +---@field isPreferred? boolean +---Marks that the code action cannot currently be applied. +--- +---Clients should follow the following guidelines regarding disabled code actions: +--- +--- - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) +--- code action menus. +--- +--- - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type +--- of code action, such as refactorings. +--- +--- - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) +--- that auto applies a code action and only disabled code actions are returned, the client should show the user an +--- error message with `reason` in the editor. +--- +---@since 3.16.0 +---@field disabled? anonym4 +---The workspace edit this code action performs. +---@field edit? lsp.WorkspaceEdit +---A command this code action executes. If a code action +---provides an edit and a command, first the edit is +---executed and then the command. +---@field command? lsp.Command +---A data entry field that is preserved on a code action between +---a `textDocument/codeAction` and a `codeAction/resolve` request. +--- +---@since 3.16.0 +---@field data? lsp.LSPAny + +---Registration options for a {@link CodeActionRequest}. +---@class lsp.CodeActionRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link WorkspaceSymbolRequest}. +---@class lsp.WorkspaceSymbolParams +---A query string to filter symbols by. Clients may send an empty +---string here to request all symbols. +---@field query string + +---A special workspace symbol that supports locations without a range. +--- +---See also SymbolInformation. +--- +---@since 3.17.0 +---@class lsp.WorkspaceSymbol: lsp.BaseSymbolInformation +---The location of the symbol. Whether a server is allowed to +---return a location without a range depends on the client +---capability `workspace.symbol.resolveSupport`. +--- +---See SymbolInformation#location for more details. +---@field location lsp.Location|anonym5 +---A data entry field that is preserved on a workspace symbol between a +---workspace symbol request and a workspace symbol resolve request. +---@field data? lsp.LSPAny + +---Registration options for a {@link WorkspaceSymbolRequest}. +---@class lsp.WorkspaceSymbolRegistrationOptions: lsp.WorkspaceSymbolOptions + +---The parameters of a {@link CodeLensRequest}. +---@class lsp.CodeLensParams +---The document to request code lens for. +---@field textDocument lsp.TextDocumentIdentifier + +---A code lens represents a {@link Command command} that should be shown along with +---source text, like the number of references, a way to run tests, etc. +--- +---A code lens is _unresolved_ when no command is associated to it. For performance +---reasons the creation of a code lens and resolving should be done in two stages. +---@class lsp.CodeLens +---The range in which this code lens is valid. Should only span a single line. +---@field range lsp.Range +---The command this code lens represents. +---@field command? lsp.Command +---A data entry field that is preserved on a code lens item between +---a {@link CodeLensRequest} and a [CodeLensResolveRequest] +---(#CodeLensResolveRequest) +---@field data? lsp.LSPAny + +---Registration options for a {@link CodeLensRequest}. +---@class lsp.CodeLensRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link DocumentLinkRequest}. +---@class lsp.DocumentLinkParams +---The document to provide document links for. +---@field textDocument lsp.TextDocumentIdentifier + +---A document link is a range in a text document that links to an internal or external resource, like another +---text document or a web site. +---@class lsp.DocumentLink +---The range this link applies to. +---@field range lsp.Range +---The uri this link points to. If missing a resolve request is sent later. +---@field target? lsp.URI +---The tooltip text when you hover over this link. +--- +---If a tooltip is provided, is will be displayed in a string that includes instructions on how to +---trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, +---user settings, and localization. +--- +---@since 3.15.0 +---@field tooltip? string +---A data entry field that is preserved on a document link between a +---DocumentLinkRequest and a DocumentLinkResolveRequest. +---@field data? lsp.LSPAny + +---Registration options for a {@link DocumentLinkRequest}. +---@class lsp.DocumentLinkRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link DocumentFormattingRequest}. +---@class lsp.DocumentFormattingParams +---The document to format. +---@field textDocument lsp.TextDocumentIdentifier +---The format options. +---@field options lsp.FormattingOptions + +---Registration options for a {@link DocumentFormattingRequest}. +---@class lsp.DocumentFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link DocumentRangeFormattingRequest}. +---@class lsp.DocumentRangeFormattingParams +---The document to format. +---@field textDocument lsp.TextDocumentIdentifier +---The range to format +---@field range lsp.Range +---The format options +---@field options lsp.FormattingOptions + +---Registration options for a {@link DocumentRangeFormattingRequest}. +---@class lsp.DocumentRangeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link DocumentOnTypeFormattingRequest}. +---@class lsp.DocumentOnTypeFormattingParams +---The document to format. +---@field textDocument lsp.TextDocumentIdentifier +---The position around which the on type formatting should happen. +---This is not necessarily the exact position where the character denoted +---by the property `ch` got typed. +---@field position lsp.Position +---The character that has been typed that triggered the formatting +---on type request. That is not necessarily the last character that +---got inserted into the document since the client could auto insert +---characters as well (e.g. like automatic brace completion). +---@field ch string +---The formatting options. +---@field options lsp.FormattingOptions + +---Registration options for a {@link DocumentOnTypeFormattingRequest}. +---@class lsp.DocumentOnTypeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---The parameters of a {@link RenameRequest}. +---@class lsp.RenameParams +---The document to rename. +---@field textDocument lsp.TextDocumentIdentifier +---The position at which this request was sent. +---@field position lsp.Position +---The new name of the symbol. If the given name is not valid the +---request must return a {@link ResponseError} with an +---appropriate message set. +---@field newName string + +---Registration options for a {@link RenameRequest}. +---@class lsp.RenameRegistrationOptions: lsp.TextDocumentRegistrationOptions + +---@class lsp.PrepareRenameParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams + +---The parameters of a {@link ExecuteCommandRequest}. +---@class lsp.ExecuteCommandParams +---The identifier of the actual command handler. +---@field command string +---Arguments that the command should be invoked with. +---@field arguments? lsp.LSPAny[] + +---Registration options for a {@link ExecuteCommandRequest}. +---@class lsp.ExecuteCommandRegistrationOptions: lsp.ExecuteCommandOptions + +---The parameters passed via an apply workspace edit request. +---@class lsp.ApplyWorkspaceEditParams +---An optional label of the workspace edit. This label is +---presented in the user interface for example on an undo +---stack to undo the workspace edit. +---@field label? string +---The edits to apply. +---@field edit lsp.WorkspaceEdit + +---The result returned from the apply workspace edit request. +--- +---@since 3.17 renamed from ApplyWorkspaceEditResponse +---@class lsp.ApplyWorkspaceEditResult +---Indicates whether the edit was applied or not. +---@field applied boolean +---An optional textual description for why the edit was not applied. +---This may be used by the server for diagnostic logging or to provide +---a suitable error for a request that triggered the edit. +---@field failureReason? string +---Depending on the client's failure handling strategy `failedChange` might +---contain the index of the change that failed. This property is only available +---if the client signals a `failureHandlingStrategy` in its client capabilities. +---@field failedChange? uinteger + +---@class lsp.WorkDoneProgressBegin +---@field kind "begin" +---Mandatory title of the progress operation. Used to briefly inform about +---the kind of operation being performed. +--- +---Examples: "Indexing" or "Linking dependencies". +---@field title string +---Controls if a cancel button should show to allow the user to cancel the +---long running operation. Clients that don't support cancellation are allowed +---to ignore the setting. +---@field cancellable? boolean +---Optional, more detailed associated progress message. Contains +---complementary information to the `title`. +--- +---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". +---If unset, the previous progress message (if any) is still valid. +---@field message? string +---Optional progress percentage to display (value 100 is considered 100%). +---If not provided infinite progress is assumed and clients are allowed +---to ignore the `percentage` value in subsequent in report notifications. +--- +---The value should be steadily rising. Clients are free to ignore values +---that are not following this rule. The value range is [0, 100]. +---@field percentage? uinteger + +---@class lsp.WorkDoneProgressReport +---@field kind "report" +---Controls enablement state of a cancel button. +--- +---Clients that don't support cancellation or don't support controlling the button's +---enablement state are allowed to ignore the property. +---@field cancellable? boolean +---Optional, more detailed associated progress message. Contains +---complementary information to the `title`. +--- +---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". +---If unset, the previous progress message (if any) is still valid. +---@field message? string +---Optional progress percentage to display (value 100 is considered 100%). +---If not provided infinite progress is assumed and clients are allowed +---to ignore the `percentage` value in subsequent in report notifications. +--- +---The value should be steadily rising. Clients are free to ignore values +---that are not following this rule. The value range is [0, 100] +---@field percentage? uinteger + +---@class lsp.WorkDoneProgressEnd +---@field kind "end" +---Optional, a final message indicating to for example indicate the outcome +---of the operation. +---@field message? string + +---@class lsp.SetTraceParams +---@field value lsp.TraceValues + +---@class lsp.LogTraceParams +---@field message string +---@field verbose? string + +---@class lsp.CancelParams +---The request id to cancel. +---@field id integer|string + +---@class lsp.ProgressParams +---The progress token provided by the client or server. +---@field token lsp.ProgressToken +---The progress data. +---@field value lsp.LSPAny + +---A parameter literal used in requests to pass a text document and a position inside that +---document. +---@class lsp.TextDocumentPositionParams +---The text document. +---@field textDocument lsp.TextDocumentIdentifier +---The position inside the text document. +---@field position lsp.Position + +---@class lsp.WorkDoneProgressParams +---An optional token that a server can use to report work done progress. +---@field workDoneToken? lsp.ProgressToken + +---@class lsp.PartialResultParams +---An optional token that a server can use to report partial results (e.g. streaming) to +---the client. +---@field partialResultToken? lsp.ProgressToken + +---Represents the connection of two locations. Provides additional metadata over normal {@link Location locations}, +---including an origin range. +---@class lsp.LocationLink +---Span of the origin of this link. +--- +---Used as the underlined span for mouse interaction. Defaults to the word range at +---the definition position. +---@field originSelectionRange? lsp.Range +---The target resource identifier of this link. +---@field targetUri lsp.DocumentUri +---The full target range of this link. If the target for example is a symbol then target range is the +---range enclosing this symbol not including leading/trailing whitespace but everything else +---like comments. This information is typically used to highlight the range in the editor. +---@field targetRange lsp.Range +---The range that should be selected and revealed when this link is being followed, e.g the name of a function. +---Must be contained by the `targetRange`. See also `DocumentSymbol#range` +---@field targetSelectionRange lsp.Range + +---A range in a text document expressed as (zero-based) start and end positions. +--- +---If you want to specify a range that contains a line including the line ending +---character(s) then use an end position denoting the start of the next line. +---For example: +---```ts +---{ +--- start: { line: 5, character: 23 } +--- end : { line 6, character : 0 } +---} +---``` +---@class lsp.Range +---The range's start position. +---@field start lsp.Position +---The range's end position. +---@field end lsp.Position + +---@class lsp.ImplementationOptions + +---Static registration options to be returned in the initialize +---request. +---@class lsp.StaticRegistrationOptions +---The id used to register the request. The id can be used to deregister +---the request again. See also Registration#id. +---@field id? string + +---@class lsp.TypeDefinitionOptions + +---The workspace folder change event. +---@class lsp.WorkspaceFoldersChangeEvent +---The array of added workspace folders +---@field added lsp.WorkspaceFolder[] +---The array of the removed workspace folders +---@field removed lsp.WorkspaceFolder[] + +---@class lsp.ConfigurationItem +---The scope to get the configuration section for. +---@field scopeUri? string +---The configuration section asked for. +---@field section? string + +---A literal to identify a text document in the client. +---@class lsp.TextDocumentIdentifier +---The text document's uri. +---@field uri lsp.DocumentUri + +---Represents a color in RGBA space. +---@class lsp.Color +---The red component of this color in the range [0-1]. +---@field red decimal +---The green component of this color in the range [0-1]. +---@field green decimal +---The blue component of this color in the range [0-1]. +---@field blue decimal +---The alpha component of this color in the range [0-1]. +---@field alpha decimal + +---@class lsp.DocumentColorOptions + +---@class lsp.FoldingRangeOptions + +---@class lsp.DeclarationOptions + +---Position in a text document expressed as zero-based line and character +---offset. Prior to 3.17 the offsets were always based on a UTF-16 string +---representation. So a string of the form `a𐐀b` the character offset of the +---character `a` is 0, the character offset of `𐐀` is 1 and the character +---offset of b is 3 since `𐐀` is represented using two code units in UTF-16. +---Since 3.17 clients and servers can agree on a different string encoding +---representation (e.g. UTF-8). The client announces it's supported encoding +---via the client capability [`general.positionEncodings`](#clientCapabilities). +---The value is an array of position encodings the client supports, with +---decreasing preference (e.g. the encoding at index `0` is the most preferred +---one). To stay backwards compatible the only mandatory encoding is UTF-16 +---represented via the string `utf-16`. The server can pick one of the +---encodings offered by the client and signals that encoding back to the +---client via the initialize result's property +---[`capabilities.positionEncoding`](#serverCapabilities). If the string value +---`utf-16` is missing from the client's capability `general.positionEncodings` +---servers can safely assume that the client supports UTF-16. If the server +---omits the position encoding in its initialize result the encoding defaults +---to the string value `utf-16`. Implementation considerations: since the +---conversion from one encoding into another requires the content of the +---file / line the conversion is best done where the file is read which is +---usually on the server side. +--- +---Positions are line end character agnostic. So you can not specify a position +---that denotes `\r|\n` or `\n|` where `|` represents the character offset. +--- +---@since 3.17.0 - support for negotiated position encoding. +---@class lsp.Position +---Line position in a document (zero-based). +--- +---If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. +---If a line number is negative, it defaults to 0. +---@field line uinteger +---Character offset on a line in a document (zero-based). +--- +---The meaning of this offset is determined by the negotiated +---`PositionEncodingKind`. +--- +---If the character value is greater than the line length it defaults back to the +---line length. +---@field character uinteger + +---@class lsp.SelectionRangeOptions + +---Call hierarchy options used during static registration. +--- +---@since 3.16.0 +---@class lsp.CallHierarchyOptions + +---@since 3.16.0 +---@class lsp.SemanticTokensOptions +---The legend used by the server +---@field legend lsp.SemanticTokensLegend +---Server supports providing semantic tokens for a specific range +---of a document. +---@field range? boolean|anonym6 +---Server supports providing semantic tokens for a full document. +---@field full? boolean|anonym7 + +---@since 3.16.0 +---@class lsp.SemanticTokensEdit +---The start offset of the edit. +---@field start uinteger +---The count of elements to remove. +---@field deleteCount uinteger +---The elements to insert. +---@field data? uinteger[] + +---@class lsp.LinkedEditingRangeOptions + +---Represents information on a file/folder create. +--- +---@since 3.16.0 +---@class lsp.FileCreate +---A file:// URI for the location of the file/folder being created. +---@field uri string + +---Describes textual changes on a text document. A TextDocumentEdit describes all changes +---on a document version Si and after they are applied move the document to version Si+1. +---So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any +---kind of ordering. However the edits must be non overlapping. +---@class lsp.TextDocumentEdit +---The text document to change. +---@field textDocument lsp.OptionalVersionedTextDocumentIdentifier +---The edits to be applied. +--- +---@since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a +---client capability. +---@field edits lsp.TextEdit|lsp.AnnotatedTextEdit[] + +---Create file operation. +---@class lsp.CreateFile: lsp.ResourceOperation +---A create +---@field kind "create" +---The resource to create. +---@field uri lsp.DocumentUri +---Additional options +---@field options? lsp.CreateFileOptions + +---Rename file operation +---@class lsp.RenameFile: lsp.ResourceOperation +---A rename +---@field kind "rename" +---The old (existing) location. +---@field oldUri lsp.DocumentUri +---The new location. +---@field newUri lsp.DocumentUri +---Rename options. +---@field options? lsp.RenameFileOptions + +---Delete file operation +---@class lsp.DeleteFile: lsp.ResourceOperation +---A delete +---@field kind "delete" +---The file to delete. +---@field uri lsp.DocumentUri +---Delete options. +---@field options? lsp.DeleteFileOptions + +---Additional information that describes document changes. +--- +---@since 3.16.0 +---@class lsp.ChangeAnnotation +---A human-readable string describing the actual change. The string +---is rendered prominent in the user interface. +---@field label string +---A flag which indicates that user confirmation is needed +---before applying the change. +---@field needsConfirmation? boolean +---A human-readable string which is rendered less prominent in +---the user interface. +---@field description? string + +---A filter to describe in which file operation requests or notifications +---the server is interested in receiving. +--- +---@since 3.16.0 +---@class lsp.FileOperationFilter +---A Uri scheme like `file` or `untitled`. +---@field scheme? string +---The actual file operation pattern. +---@field pattern lsp.FileOperationPattern + +---Represents information on a file/folder rename. +--- +---@since 3.16.0 +---@class lsp.FileRename +---A file:// URI for the original location of the file/folder being renamed. +---@field oldUri string +---A file:// URI for the new location of the file/folder being renamed. +---@field newUri string + +---Represents information on a file/folder delete. +--- +---@since 3.16.0 +---@class lsp.FileDelete +---A file:// URI for the location of the file/folder being deleted. +---@field uri string + +---@class lsp.MonikerOptions + +---Type hierarchy options used during static registration. +--- +---@since 3.17.0 +---@class lsp.TypeHierarchyOptions + +---@since 3.17.0 +---@class lsp.InlineValueContext +---The stack frame (as a DAP Id) where the execution has stopped. +---@field frameId integer +---The document range where execution has stopped. +---Typically the end position of the range denotes the line where the inline values are shown. +---@field stoppedLocation lsp.Range + +---Provide inline value as text. +--- +---@since 3.17.0 +---@class lsp.InlineValueText +---The document range for which the inline value applies. +---@field range lsp.Range +---The text of the inline value. +---@field text string + +---Provide inline value through a variable lookup. +---If only a range is specified, the variable name will be extracted from the underlying document. +---An optional variable name can be used to override the extracted name. +--- +---@since 3.17.0 +---@class lsp.InlineValueVariableLookup +---The document range for which the inline value applies. +---The range is used to extract the variable name from the underlying document. +---@field range lsp.Range +---If specified the name of the variable to look up. +---@field variableName? string +---How to perform the lookup. +---@field caseSensitiveLookup boolean + +---Provide an inline value through an expression evaluation. +---If only a range is specified, the expression will be extracted from the underlying document. +---An optional expression can be used to override the extracted expression. +--- +---@since 3.17.0 +---@class lsp.InlineValueEvaluatableExpression +---The document range for which the inline value applies. +---The range is used to extract the evaluatable expression from the underlying document. +---@field range lsp.Range +---If specified the expression overrides the extracted expression. +---@field expression? string + +---Inline value options used during static registration. +--- +---@since 3.17.0 +---@class lsp.InlineValueOptions + +---An inlay hint label part allows for interactive and composite labels +---of inlay hints. +--- +---@since 3.17.0 +---@class lsp.InlayHintLabelPart +---The value of this label part. +---@field value string +---The tooltip text when you hover over this label part. Depending on +---the client capability `inlayHint.resolveSupport` clients might resolve +---this property late using the resolve request. +---@field tooltip? string|lsp.MarkupContent +---An optional source code location that represents this +---label part. +--- +---The editor will use this location for the hover and for code navigation +---features: This part will become a clickable link that resolves to the +---definition of the symbol at the given location (not necessarily the +---location itself), it shows the hover that shows at the given location, +---and it shows a context menu with further code navigation commands. +--- +---Depending on the client capability `inlayHint.resolveSupport` clients +---might resolve this property late using the resolve request. +---@field location? lsp.Location +---An optional command for this label part. +--- +---Depending on the client capability `inlayHint.resolveSupport` clients +---might resolve this property late using the resolve request. +---@field command? lsp.Command + +---A `MarkupContent` literal represents a string value which content is interpreted base on its +---kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. +--- +---If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. +---See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +--- +---Here is an example how such a string can be constructed using JavaScript / TypeScript: +---```ts +---let markdown: MarkdownContent = { +--- kind: MarkupKind.Markdown, +--- value: [ +--- '# Header', +--- 'Some text', +--- '```typescript', +--- 'someCode();', +--- '```' +--- ].join('\n') +---}; +---``` +--- +---*Please Note* that clients might sanitize the return markdown. A client could decide to +---remove HTML from the markdown to avoid script execution. +---@class lsp.MarkupContent +---The type of the Markup +---@field kind lsp.MarkupKind +---The content itself +---@field value string + +---Inlay hint options used during static registration. +--- +---@since 3.17.0 +---@class lsp.InlayHintOptions +---The server provides support to resolve additional +---information for an inlay hint item. +---@field resolveProvider? boolean + +---A full diagnostic report with a set of related documents. +--- +---@since 3.17.0 +---@class lsp.RelatedFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport +---Diagnostics of related documents. This information is useful +---in programming languages where code in a file A can generate +---diagnostics in a file B which A depends on. An example of +---such a language is C/C++ where marco definitions in a file +---a.cpp and result in errors in a header file b.hpp. +--- +---@since 3.17.0 +---@field relatedDocuments? table + +---An unchanged diagnostic report with a set of related documents. +--- +---@since 3.17.0 +---@class lsp.RelatedUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport +---Diagnostics of related documents. This information is useful +---in programming languages where code in a file A can generate +---diagnostics in a file B which A depends on. An example of +---such a language is C/C++ where marco definitions in a file +---a.cpp and result in errors in a header file b.hpp. +--- +---@since 3.17.0 +---@field relatedDocuments? table + +---A diagnostic report with a full set of problems. +--- +---@since 3.17.0 +---@class lsp.FullDocumentDiagnosticReport +---A full document diagnostic report. +---@field kind "full" +---An optional result id. If provided it will +---be sent on the next diagnostic request for the +---same document. +---@field resultId? string +---The actual items. +---@field items lsp.Diagnostic[] + +---A diagnostic report indicating that the last returned +---report is still accurate. +--- +---@since 3.17.0 +---@class lsp.UnchangedDocumentDiagnosticReport +---A document diagnostic report indicating +---no changes to the last result. A server can +---only return `unchanged` if result ids are +---provided. +---@field kind "unchanged" +---A result id which will be sent on the next +---diagnostic request for the same document. +---@field resultId string + +---Diagnostic options. +--- +---@since 3.17.0 +---@class lsp.DiagnosticOptions +---An optional identifier under which the diagnostics are +---managed by the client. +---@field identifier? string +---Whether the language has inter file dependencies meaning that +---editing code in one file can result in a different diagnostic +---set in another file. Inter file dependencies are common for +---most programming languages and typically uncommon for linters. +---@field interFileDependencies boolean +---The server provides support for workspace diagnostics as well. +---@field workspaceDiagnostics boolean + +---A previous result id in a workspace pull request. +--- +---@since 3.17.0 +---@class lsp.PreviousResultId +---The URI for which the client knowns a +---result id. +---@field uri lsp.DocumentUri +---The value of the previous result id. +---@field value string + +---A notebook document. +--- +---@since 3.17.0 +---@class lsp.NotebookDocument +---The notebook document's uri. +---@field uri lsp.URI +---The type of the notebook. +---@field notebookType string +---The version number of this document (it will increase after each +---change, including undo/redo). +---@field version integer +---Additional metadata stored with the notebook +---document. +--- +---Note: should always be an object literal (e.g. LSPObject) +---@field metadata? lsp.LSPObject +---The cells of a notebook. +---@field cells lsp.NotebookCell[] + +---An item to transfer a text document from the client to the +---server. +---@class lsp.TextDocumentItem +---The text document's uri. +---@field uri lsp.DocumentUri +---The text document's language identifier. +---@field languageId string +---The version number of this document (it will increase after each +---change, including undo/redo). +---@field version integer +---The content of the opened text document. +---@field text string + +---A versioned notebook document identifier. +--- +---@since 3.17.0 +---@class lsp.VersionedNotebookDocumentIdentifier +---The version number of this notebook document. +---@field version integer +---The notebook document's uri. +---@field uri lsp.URI + +---A change event for a notebook document. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentChangeEvent +---The changed meta data if any. +--- +---Note: should always be an object literal (e.g. LSPObject) +---@field metadata? lsp.LSPObject +---Changes to cells +---@field cells? anonym10 + +---A literal to identify a notebook document in the client. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentIdentifier +---The notebook document's uri. +---@field uri lsp.URI + +---Provides information about the context in which an inline completion was requested. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionContext +---Describes how the inline completion was triggered. +---@field triggerKind lsp.InlineCompletionTriggerKind +---Provides information about the currently selected item in the autocomplete widget if it is visible. +---@field selectedCompletionInfo? lsp.SelectedCompletionInfo + +---A string value used as a snippet is a template which allows to insert text +---and to control the editor cursor when insertion happens. +--- +---A snippet can define tab stops and placeholders with `$1`, `$2` +---and `${3:foo}`. `$0` defines the final tab stop, it defaults to +---the end of the snippet. Variables are defined with `$name` and +---`${name:default value}`. +--- +---@since 3.18.0 +---@proposed +---@class lsp.StringValue +---The kind of string value. +---@field kind "snippet" +---The snippet string. +---@field value string + +---Inline completion options used during static registration. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionOptions + +---General parameters to register for a notification or to register a provider. +---@class lsp.Registration +---The id used to register the request. The id can be used to deregister +---the request again. +---@field id string +---The method / capability to register for. +---@field method string +---Options necessary for the registration. +---@field registerOptions? lsp.LSPAny + +---General parameters to unregister a request or notification. +---@class lsp.Unregistration +---The id used to unregister the request or notification. Usually an id +---provided during the register request. +---@field id string +---The method to unregister for. +---@field method string + +---The initialize parameters +---@class lsp._InitializeParams +---The process Id of the parent process that started +---the server. +--- +---Is `null` if the process has not been started by another process. +---If the parent process is not alive then the server should exit. +---@field processId integer|lsp.null +---Information about the client +--- +---@since 3.15.0 +---@field clientInfo? anonym11 +---The locale the client is currently showing the user interface +---in. This must not necessarily be the locale of the operating +---system. +--- +---Uses IETF language tags as the value's syntax +---(See https://en.wikipedia.org/wiki/IETF_language_tag) +--- +---@since 3.16.0 +---@field locale? string +---The rootPath of the workspace. Is null +---if no folder is open. +--- +---@deprecated in favour of rootUri. +---@field rootPath? string|lsp.null +---The rootUri of the workspace. Is null if no +---folder is open. If both `rootPath` and `rootUri` are set +---`rootUri` wins. +--- +---@deprecated in favour of workspaceFolders. +---@field rootUri lsp.DocumentUri|lsp.null +---The capabilities provided by the client (editor or tool) +---@field capabilities lsp.ClientCapabilities +---User provided initialization options. +---@field initializationOptions? lsp.LSPAny +---The initial trace setting. If omitted trace is disabled ('off'). +---@field trace? lsp.TraceValues + +---@class lsp.WorkspaceFoldersInitializeParams +---The workspace folders configured in the client when the server starts. +--- +---This property is only available if the client supports workspace folders. +---It can be `null` if the client supports workspace folders but none are +---configured. +--- +---@since 3.6.0 +---@field workspaceFolders? lsp.WorkspaceFolder[]|lsp.null + +---Defines the capabilities provided by a language +---server. +---@class lsp.ServerCapabilities +---The position encoding the server picked from the encodings offered +---by the client via the client capability `general.positionEncodings`. +--- +---If the client didn't provide any position encodings the only valid +---value that a server can return is 'utf-16'. +--- +---If omitted it defaults to 'utf-16'. +--- +---@since 3.17.0 +---@field positionEncoding? lsp.PositionEncodingKind +---Defines how text documents are synced. Is either a detailed structure +---defining each notification or for backwards compatibility the +---TextDocumentSyncKind number. +---@field textDocumentSync? lsp.TextDocumentSyncOptions|lsp.TextDocumentSyncKind +---Defines how notebook documents are synced. +--- +---@since 3.17.0 +---@field notebookDocumentSync? lsp.NotebookDocumentSyncOptions|lsp.NotebookDocumentSyncRegistrationOptions +---The server provides completion support. +---@field completionProvider? lsp.CompletionOptions +---The server provides hover support. +---@field hoverProvider? boolean|lsp.HoverOptions +---The server provides signature help support. +---@field signatureHelpProvider? lsp.SignatureHelpOptions +---The server provides Goto Declaration support. +---@field declarationProvider? boolean|lsp.DeclarationOptions|lsp.DeclarationRegistrationOptions +---The server provides goto definition support. +---@field definitionProvider? boolean|lsp.DefinitionOptions +---The server provides Goto Type Definition support. +---@field typeDefinitionProvider? boolean|lsp.TypeDefinitionOptions|lsp.TypeDefinitionRegistrationOptions +---The server provides Goto Implementation support. +---@field implementationProvider? boolean|lsp.ImplementationOptions|lsp.ImplementationRegistrationOptions +---The server provides find references support. +---@field referencesProvider? boolean|lsp.ReferenceOptions +---The server provides document highlight support. +---@field documentHighlightProvider? boolean|lsp.DocumentHighlightOptions +---The server provides document symbol support. +---@field documentSymbolProvider? boolean|lsp.DocumentSymbolOptions +---The server provides code actions. CodeActionOptions may only be +---specified if the client states that it supports +---`codeActionLiteralSupport` in its initial `initialize` request. +---@field codeActionProvider? boolean|lsp.CodeActionOptions +---The server provides code lens. +---@field codeLensProvider? lsp.CodeLensOptions +---The server provides document link support. +---@field documentLinkProvider? lsp.DocumentLinkOptions +---The server provides color provider support. +---@field colorProvider? boolean|lsp.DocumentColorOptions|lsp.DocumentColorRegistrationOptions +---The server provides workspace symbol support. +---@field workspaceSymbolProvider? boolean|lsp.WorkspaceSymbolOptions +---The server provides document formatting. +---@field documentFormattingProvider? boolean|lsp.DocumentFormattingOptions +---The server provides document range formatting. +---@field documentRangeFormattingProvider? boolean|lsp.DocumentRangeFormattingOptions +---The server provides document formatting on typing. +---@field documentOnTypeFormattingProvider? lsp.DocumentOnTypeFormattingOptions +---The server provides rename support. RenameOptions may only be +---specified if the client states that it supports +---`prepareSupport` in its initial `initialize` request. +---@field renameProvider? boolean|lsp.RenameOptions +---The server provides folding provider support. +---@field foldingRangeProvider? boolean|lsp.FoldingRangeOptions|lsp.FoldingRangeRegistrationOptions +---The server provides selection range support. +---@field selectionRangeProvider? boolean|lsp.SelectionRangeOptions|lsp.SelectionRangeRegistrationOptions +---The server provides execute command support. +---@field executeCommandProvider? lsp.ExecuteCommandOptions +---The server provides call hierarchy support. +--- +---@since 3.16.0 +---@field callHierarchyProvider? boolean|lsp.CallHierarchyOptions|lsp.CallHierarchyRegistrationOptions +---The server provides linked editing range support. +--- +---@since 3.16.0 +---@field linkedEditingRangeProvider? boolean|lsp.LinkedEditingRangeOptions|lsp.LinkedEditingRangeRegistrationOptions +---The server provides semantic tokens support. +--- +---@since 3.16.0 +---@field semanticTokensProvider? lsp.SemanticTokensOptions|lsp.SemanticTokensRegistrationOptions +---The server provides moniker support. +--- +---@since 3.16.0 +---@field monikerProvider? boolean|lsp.MonikerOptions|lsp.MonikerRegistrationOptions +---The server provides type hierarchy support. +--- +---@since 3.17.0 +---@field typeHierarchyProvider? boolean|lsp.TypeHierarchyOptions|lsp.TypeHierarchyRegistrationOptions +---The server provides inline values. +--- +---@since 3.17.0 +---@field inlineValueProvider? boolean|lsp.InlineValueOptions|lsp.InlineValueRegistrationOptions +---The server provides inlay hints. +--- +---@since 3.17.0 +---@field inlayHintProvider? boolean|lsp.InlayHintOptions|lsp.InlayHintRegistrationOptions +---The server has support for pull model diagnostics. +--- +---@since 3.17.0 +---@field diagnosticProvider? lsp.DiagnosticOptions|lsp.DiagnosticRegistrationOptions +---Inline completion options used during static registration. +--- +---@since 3.18.0 +---@proposed +---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions +---Workspace specific server capabilities. +---@field workspace? anonym12 +---Experimental server capabilities. +---@field experimental? lsp.LSPAny + +---A text document identifier to denote a specific version of a text document. +---@class lsp.VersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier +---The version number of this document. +---@field version integer + +---Save options. +---@class lsp.SaveOptions +---The client is supposed to include the content on save. +---@field includeText? boolean + +---An event describing a file change. +---@class lsp.FileEvent +---The file's uri. +---@field uri lsp.DocumentUri +---The change type. +---@field type lsp.FileChangeType + +---@class lsp.FileSystemWatcher +---The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail. +--- +---@since 3.17.0 support for relative patterns. +---@field globPattern lsp.GlobPattern +---The kind of events of interest. If omitted it defaults +---to WatchKind.Create | WatchKind.Change | WatchKind.Delete +---which is 7. +---@field kind? lsp.WatchKind + +---Represents a diagnostic, such as a compiler error or warning. Diagnostic objects +---are only valid in the scope of a resource. +---@class lsp.Diagnostic +---The range at which the message applies +---@field range lsp.Range +---The diagnostic's severity. Can be omitted. If omitted it is up to the +---client to interpret diagnostics as error, warning, info or hint. +---@field severity? lsp.DiagnosticSeverity +---The diagnostic's code, which usually appear in the user interface. +---@field code? integer|string +---An optional property to describe the error code. +---Requires the code field (above) to be present/not null. +--- +---@since 3.16.0 +---@field codeDescription? lsp.CodeDescription +---A human-readable string describing the source of this +---diagnostic, e.g. 'typescript' or 'super lint'. It usually +---appears in the user interface. +---@field source? string +---The diagnostic's message. It usually appears in the user interface +---@field message string +---Additional metadata about the diagnostic. +--- +---@since 3.15.0 +---@field tags? lsp.DiagnosticTag[] +---An array of related diagnostic information, e.g. when symbol-names within +---a scope collide all definitions can be marked via this property. +---@field relatedInformation? lsp.DiagnosticRelatedInformation[] +---A data entry field that is preserved between a `textDocument/publishDiagnostics` +---notification and `textDocument/codeAction` request. +--- +---@since 3.16.0 +---@field data? lsp.LSPAny + +---Contains additional information about the context in which a completion request is triggered. +---@class lsp.CompletionContext +---How the completion was triggered. +---@field triggerKind lsp.CompletionTriggerKind +---The trigger character (a single character) that has trigger code complete. +---Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter` +---@field triggerCharacter? string + +---Additional details for a completion item label. +--- +---@since 3.17.0 +---@class lsp.CompletionItemLabelDetails +---An optional string which is rendered less prominently directly after {@link CompletionItem.label label}, +---without any spacing. Should be used for function signatures and type annotations. +---@field detail? string +---An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used +---for fully qualified names and file paths. +---@field description? string + +---A special text edit to provide an insert and a replace operation. +--- +---@since 3.16.0 +---@class lsp.InsertReplaceEdit +---The string to be inserted. +---@field newText string +---The range if the insert is requested +---@field insert lsp.Range +---The range if the replace is requested. +---@field replace lsp.Range + +---Completion options. +---@class lsp.CompletionOptions +---Most tools trigger completion request automatically without explicitly requesting +---it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user +---starts to type an identifier. For example if the user types `c` in a JavaScript file +---code complete will automatically pop up present `console` besides others as a +---completion item. Characters that make up identifiers don't need to be listed here. +--- +---If code complete should automatically be trigger on characters not being valid inside +---an identifier (for example `.` in JavaScript) list them in `triggerCharacters`. +---@field triggerCharacters? string[] +---The list of all possible characters that commit a completion. This field can be used +---if clients don't support individual commit characters per completion item. See +---`ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` +--- +---If a server provides both `allCommitCharacters` and commit characters on an individual +---completion item the ones on the completion item win. +--- +---@since 3.2.0 +---@field allCommitCharacters? string[] +---The server provides support to resolve additional +---information for a completion item. +---@field resolveProvider? boolean +---The server supports the following `CompletionItem` specific +---capabilities. +--- +---@since 3.17.0 +---@field completionItem? anonym13 + +---Hover options. +---@class lsp.HoverOptions + +---Additional information about the context in which a signature help request was triggered. +--- +---@since 3.15.0 +---@class lsp.SignatureHelpContext +---Action that caused signature help to be triggered. +---@field triggerKind lsp.SignatureHelpTriggerKind +---Character that caused signature help to be triggered. +--- +---This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter` +---@field triggerCharacter? string +---`true` if signature help was already showing when it was triggered. +--- +---Retriggers occurs when the signature help is already active and can be caused by actions such as +---typing a trigger character, a cursor move, or document content changes. +---@field isRetrigger boolean +---The currently active `SignatureHelp`. +--- +---The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on +---the user navigating through available signatures. +---@field activeSignatureHelp? lsp.SignatureHelp + +---Represents the signature of something callable. A signature +---can have a label, like a function-name, a doc-comment, and +---a set of parameters. +---@class lsp.SignatureInformation +---The label of this signature. Will be shown in +---the UI. +---@field label string +---The human-readable doc-comment of this signature. Will be shown +---in the UI but can be omitted. +---@field documentation? string|lsp.MarkupContent +---The parameters of this signature. +---@field parameters? lsp.ParameterInformation[] +---The index of the active parameter. +--- +---If provided, this is used in place of `SignatureHelp.activeParameter`. +--- +---@since 3.16.0 +---@field activeParameter? uinteger + +---Server Capabilities for a {@link SignatureHelpRequest}. +---@class lsp.SignatureHelpOptions +---List of characters that trigger signature help automatically. +---@field triggerCharacters? string[] +---List of characters that re-trigger signature help. +--- +---These trigger characters are only active when signature help is already showing. All trigger characters +---are also counted as re-trigger characters. +--- +---@since 3.15.0 +---@field retriggerCharacters? string[] + +---Server Capabilities for a {@link DefinitionRequest}. +---@class lsp.DefinitionOptions + +---Value-object that contains additional information when +---requesting references. +---@class lsp.ReferenceContext +---Include the declaration of the current symbol. +---@field includeDeclaration boolean + +---Reference options. +---@class lsp.ReferenceOptions + +---Provider options for a {@link DocumentHighlightRequest}. +---@class lsp.DocumentHighlightOptions + +---A base for all symbol information. +---@class lsp.BaseSymbolInformation +---The name of this symbol. +---@field name string +---The kind of this symbol. +---@field kind lsp.SymbolKind +---Tags for this symbol. +--- +---@since 3.16.0 +---@field tags? lsp.SymbolTag[] +---The name of the symbol containing this symbol. This information is for +---user interface purposes (e.g. to render a qualifier in the user interface +---if necessary). It can't be used to re-infer a hierarchy for the document +---symbols. +---@field containerName? string + +---Provider options for a {@link DocumentSymbolRequest}. +---@class lsp.DocumentSymbolOptions +---A human-readable string that is shown when multiple outlines trees +---are shown for the same document. +--- +---@since 3.16.0 +---@field label? string + +---Contains additional diagnostic information about the context in which +---a {@link CodeActionProvider.provideCodeActions code action} is run. +---@class lsp.CodeActionContext +---An array of diagnostics known on the client side overlapping the range provided to the +---`textDocument/codeAction` request. They are provided so that the server knows which +---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. +---@field diagnostics lsp.Diagnostic[] +---Requested kind of actions to return. +--- +---Actions not of this kind are filtered out by the client before being shown. So servers +---can omit computing them. +---@field only? lsp.CodeActionKind[] +---The reason why code actions were requested. +--- +---@since 3.17.0 +---@field triggerKind? lsp.CodeActionTriggerKind + +---Provider options for a {@link CodeActionRequest}. +---@class lsp.CodeActionOptions +---CodeActionKinds that this server may return. +--- +---The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server +---may list out every specific kind they provide. +---@field codeActionKinds? lsp.CodeActionKind[] +---The server provides support to resolve additional +---information for a code action. +--- +---@since 3.16.0 +---@field resolveProvider? boolean + +---Server capabilities for a {@link WorkspaceSymbolRequest}. +---@class lsp.WorkspaceSymbolOptions +---The server provides support to resolve additional +---information for a workspace symbol. +--- +---@since 3.17.0 +---@field resolveProvider? boolean + +---Code Lens provider options of a {@link CodeLensRequest}. +---@class lsp.CodeLensOptions +---Code lens has a resolve provider as well. +---@field resolveProvider? boolean + +---Provider options for a {@link DocumentLinkRequest}. +---@class lsp.DocumentLinkOptions +---Document links have a resolve provider as well. +---@field resolveProvider? boolean + +---Value-object describing what options formatting should use. +---@class lsp.FormattingOptions +---Size of a tab in spaces. +---@field tabSize uinteger +---Prefer spaces over tabs. +---@field insertSpaces boolean +---Trim trailing whitespace on a line. +--- +---@since 3.15.0 +---@field trimTrailingWhitespace? boolean +---Insert a newline character at the end of the file if one does not exist. +--- +---@since 3.15.0 +---@field insertFinalNewline? boolean +---Trim all newlines after the final newline at the end of the file. +--- +---@since 3.15.0 +---@field trimFinalNewlines? boolean + +---Provider options for a {@link DocumentFormattingRequest}. +---@class lsp.DocumentFormattingOptions + +---Provider options for a {@link DocumentRangeFormattingRequest}. +---@class lsp.DocumentRangeFormattingOptions + +---Provider options for a {@link DocumentOnTypeFormattingRequest}. +---@class lsp.DocumentOnTypeFormattingOptions +---A character on which formatting should be triggered, like `{`. +---@field firstTriggerCharacter string +---More trigger characters. +---@field moreTriggerCharacter? string[] + +---Provider options for a {@link RenameRequest}. +---@class lsp.RenameOptions +---Renames should be checked and tested before being executed. +--- +---@since version 3.12.0 +---@field prepareProvider? boolean + +---The server capabilities of a {@link ExecuteCommandRequest}. +---@class lsp.ExecuteCommandOptions +---The commands to be executed on the server +---@field commands string[] + +---@since 3.16.0 +---@class lsp.SemanticTokensLegend +---The token types a server uses. +---@field tokenTypes string[] +---The token modifiers a server uses. +---@field tokenModifiers string[] + +---A text document identifier to optionally denote a specific version of a text document. +---@class lsp.OptionalVersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier +---The version number of this document. If a versioned text document identifier +---is sent from the server to the client and the file is not open in the editor +---(the server has not received an open notification before) the server can send +---`null` to indicate that the version is unknown and the content on disk is the +---truth (as specified with document content ownership). +---@field version integer|lsp.null + +---A special text edit with an additional change annotation. +--- +---@since 3.16.0. +---@class lsp.AnnotatedTextEdit: lsp.TextEdit +---The actual identifier of the change annotation +---@field annotationId lsp.ChangeAnnotationIdentifier + +---A generic resource operation. +---@class lsp.ResourceOperation +---The resource operation kind. +---@field kind string +---An optional annotation identifier describing the operation. +--- +---@since 3.16.0 +---@field annotationId? lsp.ChangeAnnotationIdentifier + +---Options to create a file. +---@class lsp.CreateFileOptions +---Overwrite existing file. Overwrite wins over `ignoreIfExists` +---@field overwrite? boolean +---Ignore if exists. +---@field ignoreIfExists? boolean + +---Rename file options +---@class lsp.RenameFileOptions +---Overwrite target if existing. Overwrite wins over `ignoreIfExists` +---@field overwrite? boolean +---Ignores if target exists. +---@field ignoreIfExists? boolean + +---Delete file options +---@class lsp.DeleteFileOptions +---Delete the content recursively if a folder is denoted. +---@field recursive? boolean +---Ignore the operation if the file doesn't exist. +---@field ignoreIfNotExists? boolean + +---A pattern to describe in which file operation requests or notifications +---the server is interested in receiving. +--- +---@since 3.16.0 +---@class lsp.FileOperationPattern +---The glob pattern to match. Glob patterns can have the following syntax: +---- `*` to match one or more characters in a path segment +---- `?` to match on one character in a path segment +---- `**` to match any number of path segments, including none +---- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +---@field glob string +---Whether to match files or folders with this pattern. +--- +---Matches both if undefined. +---@field matches? lsp.FileOperationPatternKind +---Additional options used during matching. +---@field options? lsp.FileOperationPatternOptions + +---A full document diagnostic report for a workspace diagnostic result. +--- +---@since 3.17.0 +---@class lsp.WorkspaceFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport +---The URI for which diagnostic information is reported. +---@field uri lsp.DocumentUri +---The version number for which the diagnostics are reported. +---If the document is not marked as open `null` can be provided. +---@field version integer|lsp.null + +---An unchanged document diagnostic report for a workspace diagnostic result. +--- +---@since 3.17.0 +---@class lsp.WorkspaceUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport +---The URI for which diagnostic information is reported. +---@field uri lsp.DocumentUri +---The version number for which the diagnostics are reported. +---If the document is not marked as open `null` can be provided. +---@field version integer|lsp.null + +---A notebook cell. +--- +---A cell's document URI must be unique across ALL notebook +---cells and can therefore be used to uniquely identify a +---notebook cell or the cell's text document. +--- +---@since 3.17.0 +---@class lsp.NotebookCell +---The cell's kind +---@field kind lsp.NotebookCellKind +---The URI of the cell's text document +---content. +---@field document lsp.DocumentUri +---Additional metadata stored with the cell. +--- +---Note: should always be an object literal (e.g. LSPObject) +---@field metadata? lsp.LSPObject +---Additional execution summary information +---if supported by the client. +---@field executionSummary? lsp.ExecutionSummary + +---A change describing how to move a `NotebookCell` +---array from state S to S'. +--- +---@since 3.17.0 +---@class lsp.NotebookCellArrayChange +---The start oftest of the cell that changed. +---@field start uinteger +---The deleted cells +---@field deleteCount uinteger +---The new cells, if any +---@field cells? lsp.NotebookCell[] + +---Describes the currently selected completion item. +--- +---@since 3.18.0 +---@proposed +---@class lsp.SelectedCompletionInfo +---The range that will be replaced if this completion item is accepted. +---@field range lsp.Range +---The text the range will be replaced with if this completion is accepted. +---@field text string + +---Defines the capabilities provided by the client. +---@class lsp.ClientCapabilities +---Workspace specific client capabilities. +---@field workspace? lsp.WorkspaceClientCapabilities +---Text document specific client capabilities. +---@field textDocument? lsp.TextDocumentClientCapabilities +---Capabilities specific to the notebook document support. +--- +---@since 3.17.0 +---@field notebookDocument? lsp.NotebookDocumentClientCapabilities +---Window specific client capabilities. +---@field window? lsp.WindowClientCapabilities +---General client capabilities. +--- +---@since 3.16.0 +---@field general? lsp.GeneralClientCapabilities +---Experimental client capabilities. +---@field experimental? lsp.LSPAny + +---@class lsp.TextDocumentSyncOptions +---Open and close notifications are sent to the server. If omitted open close notification should not +---be sent. +---@field openClose? boolean +---Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full +---and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None. +---@field change? lsp.TextDocumentSyncKind +---If present will save notifications are sent to the server. If omitted the notification should not be +---sent. +---@field willSave? boolean +---If present will save wait until requests are sent to the server. If omitted the request should not be +---sent. +---@field willSaveWaitUntil? boolean +---If present save notifications are sent to the server. If omitted the notification should not be +---sent. +---@field save? boolean|lsp.SaveOptions + +---Options specific to a notebook plus its cells +---to be synced to the server. +--- +---If a selector provides a notebook document +---filter but no cell selector all cells of a +---matching notebook document will be synced. +--- +---If a selector provides no notebook document +---filter but only a cell selector all notebook +---document that contain at least one matching +---cell will be synced. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentSyncOptions +---The notebooks to be synced +---@field notebookSelector anonym15|anonym17[] +---Whether save notification should be forwarded to +---the server. Will only be honored if mode === `notebook`. +---@field save? boolean + +---Registration options specific to a notebook. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentSyncRegistrationOptions: lsp.NotebookDocumentSyncOptions, lsp.StaticRegistrationOptions + +---@class lsp.WorkspaceFoldersServerCapabilities +---The server has support for workspace folders +---@field supported? boolean +---Whether the server wants to receive workspace folder +---change notifications. +--- +---If a string is provided the string is treated as an ID +---under which the notification is registered on the client +---side. The ID can be used to unregister for these events +---using the `client/unregisterCapability` request. +---@field changeNotifications? string|boolean + +---Options for notifications/requests for user operations on files. +--- +---@since 3.16.0 +---@class lsp.FileOperationOptions +---The server is interested in receiving didCreateFiles notifications. +---@field didCreate? lsp.FileOperationRegistrationOptions +---The server is interested in receiving willCreateFiles requests. +---@field willCreate? lsp.FileOperationRegistrationOptions +---The server is interested in receiving didRenameFiles notifications. +---@field didRename? lsp.FileOperationRegistrationOptions +---The server is interested in receiving willRenameFiles requests. +---@field willRename? lsp.FileOperationRegistrationOptions +---The server is interested in receiving didDeleteFiles file notifications. +---@field didDelete? lsp.FileOperationRegistrationOptions +---The server is interested in receiving willDeleteFiles file requests. +---@field willDelete? lsp.FileOperationRegistrationOptions + +---Structure to capture a description for an error code. +--- +---@since 3.16.0 +---@class lsp.CodeDescription +---An URI to open with more information about the diagnostic error. +---@field href lsp.URI + +---Represents a related message and source code location for a diagnostic. This should be +---used to point to code locations that cause or related to a diagnostics, e.g when duplicating +---a symbol in a scope. +---@class lsp.DiagnosticRelatedInformation +---The location of this related diagnostic information. +---@field location lsp.Location +---The message of this related diagnostic information. +---@field message string + +---Represents a parameter of a callable-signature. A parameter can +---have a label and a doc-comment. +---@class lsp.ParameterInformation +---The label of this parameter information. +--- +---Either a string or an inclusive start and exclusive end offsets within its containing +---signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 +---string representation as `Position` and `Range` does. +--- +---*Note*: a label of type string should be a substring of its containing signature label. +---Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. +---@field label string|{ [1]: uinteger, [2]: uinteger } +---The human-readable doc-comment of this parameter. Will be shown +---in the UI but can be omitted. +---@field documentation? string|lsp.MarkupContent + +---A notebook cell text document filter denotes a cell text +---document by different properties. +--- +---@since 3.17.0 +---@class lsp.NotebookCellTextDocumentFilter +---A filter that matches against the notebook +---containing the notebook cell. If a string +---value is provided it matches against the +---notebook type. '*' matches every notebook. +---@field notebook string|lsp.NotebookDocumentFilter +---A language id like `python`. +--- +---Will be matched against the language id of the +---notebook cell document. '*' matches every language. +---@field language? string + +---Matching options for the file operation pattern. +--- +---@since 3.16.0 +---@class lsp.FileOperationPatternOptions +---The pattern should be matched ignoring casing. +---@field ignoreCase? boolean + +---@class lsp.ExecutionSummary +---A strict monotonically increasing value +---indicating the execution order of a cell +---inside a notebook. +---@field executionOrder uinteger +---Whether the execution was successful or +---not if known by the client. +---@field success? boolean + +---Workspace specific client capabilities. +---@class lsp.WorkspaceClientCapabilities +---The client supports applying batch edits +---to the workspace by supporting the request +---'workspace/applyEdit' +---@field applyEdit? boolean +---Capabilities specific to `WorkspaceEdit`s. +---@field workspaceEdit? lsp.WorkspaceEditClientCapabilities +---Capabilities specific to the `workspace/didChangeConfiguration` notification. +---@field didChangeConfiguration? lsp.DidChangeConfigurationClientCapabilities +---Capabilities specific to the `workspace/didChangeWatchedFiles` notification. +---@field didChangeWatchedFiles? lsp.DidChangeWatchedFilesClientCapabilities +---Capabilities specific to the `workspace/symbol` request. +---@field symbol? lsp.WorkspaceSymbolClientCapabilities +---Capabilities specific to the `workspace/executeCommand` request. +---@field executeCommand? lsp.ExecuteCommandClientCapabilities +---The client has support for workspace folders. +--- +---@since 3.6.0 +---@field workspaceFolders? boolean +---The client supports `workspace/configuration` requests. +--- +---@since 3.6.0 +---@field configuration? boolean +---Capabilities specific to the semantic token requests scoped to the +---workspace. +--- +---@since 3.16.0. +---@field semanticTokens? lsp.SemanticTokensWorkspaceClientCapabilities +---Capabilities specific to the code lens requests scoped to the +---workspace. +--- +---@since 3.16.0. +---@field codeLens? lsp.CodeLensWorkspaceClientCapabilities +---The client has support for file notifications/requests for user operations on files. +--- +---Since 3.16.0 +---@field fileOperations? lsp.FileOperationClientCapabilities +---Capabilities specific to the inline values requests scoped to the +---workspace. +--- +---@since 3.17.0. +---@field inlineValue? lsp.InlineValueWorkspaceClientCapabilities +---Capabilities specific to the inlay hint requests scoped to the +---workspace. +--- +---@since 3.17.0. +---@field inlayHint? lsp.InlayHintWorkspaceClientCapabilities +---Capabilities specific to the diagnostic requests scoped to the +---workspace. +--- +---@since 3.17.0. +---@field diagnostics? lsp.DiagnosticWorkspaceClientCapabilities + +---Text document specific client capabilities. +---@class lsp.TextDocumentClientCapabilities +---Defines which synchronization capabilities the client supports. +---@field synchronization? lsp.TextDocumentSyncClientCapabilities +---Capabilities specific to the `textDocument/completion` request. +---@field completion? lsp.CompletionClientCapabilities +---Capabilities specific to the `textDocument/hover` request. +---@field hover? lsp.HoverClientCapabilities +---Capabilities specific to the `textDocument/signatureHelp` request. +---@field signatureHelp? lsp.SignatureHelpClientCapabilities +---Capabilities specific to the `textDocument/declaration` request. +--- +---@since 3.14.0 +---@field declaration? lsp.DeclarationClientCapabilities +---Capabilities specific to the `textDocument/definition` request. +---@field definition? lsp.DefinitionClientCapabilities +---Capabilities specific to the `textDocument/typeDefinition` request. +--- +---@since 3.6.0 +---@field typeDefinition? lsp.TypeDefinitionClientCapabilities +---Capabilities specific to the `textDocument/implementation` request. +--- +---@since 3.6.0 +---@field implementation? lsp.ImplementationClientCapabilities +---Capabilities specific to the `textDocument/references` request. +---@field references? lsp.ReferenceClientCapabilities +---Capabilities specific to the `textDocument/documentHighlight` request. +---@field documentHighlight? lsp.DocumentHighlightClientCapabilities +---Capabilities specific to the `textDocument/documentSymbol` request. +---@field documentSymbol? lsp.DocumentSymbolClientCapabilities +---Capabilities specific to the `textDocument/codeAction` request. +---@field codeAction? lsp.CodeActionClientCapabilities +---Capabilities specific to the `textDocument/codeLens` request. +---@field codeLens? lsp.CodeLensClientCapabilities +---Capabilities specific to the `textDocument/documentLink` request. +---@field documentLink? lsp.DocumentLinkClientCapabilities +---Capabilities specific to the `textDocument/documentColor` and the +---`textDocument/colorPresentation` request. +--- +---@since 3.6.0 +---@field colorProvider? lsp.DocumentColorClientCapabilities +---Capabilities specific to the `textDocument/formatting` request. +---@field formatting? lsp.DocumentFormattingClientCapabilities +---Capabilities specific to the `textDocument/rangeFormatting` request. +---@field rangeFormatting? lsp.DocumentRangeFormattingClientCapabilities +---Capabilities specific to the `textDocument/onTypeFormatting` request. +---@field onTypeFormatting? lsp.DocumentOnTypeFormattingClientCapabilities +---Capabilities specific to the `textDocument/rename` request. +---@field rename? lsp.RenameClientCapabilities +---Capabilities specific to the `textDocument/foldingRange` request. +--- +---@since 3.10.0 +---@field foldingRange? lsp.FoldingRangeClientCapabilities +---Capabilities specific to the `textDocument/selectionRange` request. +--- +---@since 3.15.0 +---@field selectionRange? lsp.SelectionRangeClientCapabilities +---Capabilities specific to the `textDocument/publishDiagnostics` notification. +---@field publishDiagnostics? lsp.PublishDiagnosticsClientCapabilities +---Capabilities specific to the various call hierarchy requests. +--- +---@since 3.16.0 +---@field callHierarchy? lsp.CallHierarchyClientCapabilities +---Capabilities specific to the various semantic token request. +--- +---@since 3.16.0 +---@field semanticTokens? lsp.SemanticTokensClientCapabilities +---Capabilities specific to the `textDocument/linkedEditingRange` request. +--- +---@since 3.16.0 +---@field linkedEditingRange? lsp.LinkedEditingRangeClientCapabilities +---Client capabilities specific to the `textDocument/moniker` request. +--- +---@since 3.16.0 +---@field moniker? lsp.MonikerClientCapabilities +---Capabilities specific to the various type hierarchy requests. +--- +---@since 3.17.0 +---@field typeHierarchy? lsp.TypeHierarchyClientCapabilities +---Capabilities specific to the `textDocument/inlineValue` request. +--- +---@since 3.17.0 +---@field inlineValue? lsp.InlineValueClientCapabilities +---Capabilities specific to the `textDocument/inlayHint` request. +--- +---@since 3.17.0 +---@field inlayHint? lsp.InlayHintClientCapabilities +---Capabilities specific to the diagnostic pull model. +--- +---@since 3.17.0 +---@field diagnostic? lsp.DiagnosticClientCapabilities +---Client capabilities specific to inline completions. +--- +---@since 3.18.0 +---@proposed +---@field inlineCompletion? lsp.InlineCompletionClientCapabilities + +---Capabilities specific to the notebook document support. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentClientCapabilities +---Capabilities specific to notebook document synchronization +--- +---@since 3.17.0 +---@field synchronization lsp.NotebookDocumentSyncClientCapabilities + +---@class lsp.WindowClientCapabilities +---It indicates whether the client supports server initiated +---progress using the `window/workDoneProgress/create` request. +--- +---The capability also controls Whether client supports handling +---of progress notifications. If set servers are allowed to report a +---`workDoneProgress` property in the request specific server +---capabilities. +--- +---@since 3.15.0 +---@field workDoneProgress? boolean +---Capabilities specific to the showMessage request. +--- +---@since 3.16.0 +---@field showMessage? lsp.ShowMessageRequestClientCapabilities +---Capabilities specific to the showDocument request. +--- +---@since 3.16.0 +---@field showDocument? lsp.ShowDocumentClientCapabilities + +---General client capabilities. +--- +---@since 3.16.0 +---@class lsp.GeneralClientCapabilities +---Client capability that signals how the client +---handles stale requests (e.g. a request +---for which the client will not process the response +---anymore since the information is outdated). +--- +---@since 3.17.0 +---@field staleRequestSupport? anonym18 +---Client capabilities specific to regular expressions. +--- +---@since 3.16.0 +---@field regularExpressions? lsp.RegularExpressionsClientCapabilities +---Client capabilities specific to the client's markdown parser. +--- +---@since 3.16.0 +---@field markdown? lsp.MarkdownClientCapabilities +---The position encodings supported by the client. Client and server +---have to agree on the same position encoding to ensure that offsets +---(e.g. character position in a line) are interpreted the same on both +---sides. +--- +---To keep the protocol backwards compatible the following applies: if +---the value 'utf-16' is missing from the array of position encodings +---servers can assume that the client supports UTF-16. UTF-16 is +---therefore a mandatory encoding. +--- +---If omitted it defaults to ['utf-16']. +--- +---Implementation considerations: since the conversion from one encoding +---into another requires the content of the file / line the conversion +---is best done where the file is read which is usually on the server +---side. +--- +---@since 3.17.0 +---@field positionEncodings? lsp.PositionEncodingKind[] + +---A relative pattern is a helper to construct glob patterns that are matched +---relatively to a base URI. The common value for a `baseUri` is a workspace +---folder root, but it can be another absolute URI as well. +--- +---@since 3.17.0 +---@class lsp.RelativePattern +---A workspace folder or a base URI to which this pattern will be matched +---against relatively. +---@field baseUri lsp.WorkspaceFolder|lsp.URI +---The actual glob pattern; +---@field pattern lsp.Pattern + +---@class lsp.WorkspaceEditClientCapabilities +---The client supports versioned document changes in `WorkspaceEdit`s +---@field documentChanges? boolean +---The resource operations the client supports. Clients should at least +---support 'create', 'rename' and 'delete' files and folders. +--- +---@since 3.13.0 +---@field resourceOperations? lsp.ResourceOperationKind[] +---The failure handling strategy of a client if applying the workspace edit +---fails. +--- +---@since 3.13.0 +---@field failureHandling? lsp.FailureHandlingKind +---Whether the client normalizes line endings to the client specific +---setting. +---If set to `true` the client will normalize line ending characters +---in a workspace edit to the client-specified new line +---character. +--- +---@since 3.16.0 +---@field normalizesLineEndings? boolean +---Whether the client in general supports change annotations on text edits, +---create file, rename file and delete file changes. +--- +---@since 3.16.0 +---@field changeAnnotationSupport? anonym19 + +---@class lsp.DidChangeConfigurationClientCapabilities +---Did change configuration notification supports dynamic registration. +---@field dynamicRegistration? boolean + +---@class lsp.DidChangeWatchedFilesClientCapabilities +---Did change watched files notification supports dynamic registration. Please note +---that the current protocol doesn't support static configuration for file changes +---from the server side. +---@field dynamicRegistration? boolean +---Whether the client has support for {@link RelativePattern relative pattern} +---or not. +--- +---@since 3.17.0 +---@field relativePatternSupport? boolean + +---Client capabilities for a {@link WorkspaceSymbolRequest}. +---@class lsp.WorkspaceSymbolClientCapabilities +---Symbol request supports dynamic registration. +---@field dynamicRegistration? boolean +---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. +---@field symbolKind? anonym20 +---The client supports tags on `SymbolInformation`. +---Clients supporting tags have to handle unknown tags gracefully. +--- +---@since 3.16.0 +---@field tagSupport? anonym21 +---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? anonym22 + +---The client capabilities of a {@link ExecuteCommandRequest}. +---@class lsp.ExecuteCommandClientCapabilities +---Execute command supports dynamic registration. +---@field dynamicRegistration? boolean + +---@since 3.16.0 +---@class lsp.SemanticTokensWorkspaceClientCapabilities +---Whether the client implementation supports a refresh request sent from +---the server to the client. +--- +---Note that this event is global and will force the client to refresh all +---semantic tokens currently shown. It should be used with absolute care +---and is useful for situation where a server for example detects a project +---wide change that requires such a calculation. +---@field refreshSupport? boolean + +---@since 3.16.0 +---@class lsp.CodeLensWorkspaceClientCapabilities +---Whether the client implementation supports a refresh request sent from the +---server to the client. +--- +---Note that this event is global and will force the client to refresh all +---code lenses currently shown. It should be used with absolute care and is +---useful for situation where a server for example detect a project wide +---change that requires such a calculation. +---@field refreshSupport? boolean + +---Capabilities relating to events from file operations by the user in the client. +--- +---These events do not come from the file system, they come from user operations +---like renaming a file in the UI. +--- +---@since 3.16.0 +---@class lsp.FileOperationClientCapabilities +---Whether the client supports dynamic registration for file requests/notifications. +---@field dynamicRegistration? boolean +---The client has support for sending didCreateFiles notifications. +---@field didCreate? boolean +---The client has support for sending willCreateFiles requests. +---@field willCreate? boolean +---The client has support for sending didRenameFiles notifications. +---@field didRename? boolean +---The client has support for sending willRenameFiles requests. +---@field willRename? boolean +---The client has support for sending didDeleteFiles notifications. +---@field didDelete? boolean +---The client has support for sending willDeleteFiles requests. +---@field willDelete? boolean + +---Client workspace capabilities specific to inline values. +--- +---@since 3.17.0 +---@class lsp.InlineValueWorkspaceClientCapabilities +---Whether the client implementation supports a refresh request sent from the +---server to the client. +--- +---Note that this event is global and will force the client to refresh all +---inline values currently shown. It should be used with absolute care and is +---useful for situation where a server for example detects a project wide +---change that requires such a calculation. +---@field refreshSupport? boolean + +---Client workspace capabilities specific to inlay hints. +--- +---@since 3.17.0 +---@class lsp.InlayHintWorkspaceClientCapabilities +---Whether the client implementation supports a refresh request sent from +---the server to the client. +--- +---Note that this event is global and will force the client to refresh all +---inlay hints currently shown. It should be used with absolute care and +---is useful for situation where a server for example detects a project wide +---change that requires such a calculation. +---@field refreshSupport? boolean + +---Workspace client capabilities specific to diagnostic pull requests. +--- +---@since 3.17.0 +---@class lsp.DiagnosticWorkspaceClientCapabilities +---Whether the client implementation supports a refresh request sent from +---the server to the client. +--- +---Note that this event is global and will force the client to refresh all +---pulled diagnostics currently shown. It should be used with absolute care and +---is useful for situation where a server for example detects a project wide +---change that requires such a calculation. +---@field refreshSupport? boolean + +---@class lsp.TextDocumentSyncClientCapabilities +---Whether text document synchronization supports dynamic registration. +---@field dynamicRegistration? boolean +---The client supports sending will save notifications. +---@field willSave? boolean +---The client supports sending a will save request and +---waits for a response providing text edits which will +---be applied to the document before it is saved. +---@field willSaveWaitUntil? boolean +---The client supports did save notifications. +---@field didSave? boolean + +---Completion client capabilities +---@class lsp.CompletionClientCapabilities +---Whether completion supports dynamic registration. +---@field dynamicRegistration? boolean +---The client supports the following `CompletionItem` specific +---capabilities. +---@field completionItem? anonym26 +---@field completionItemKind? anonym27 +---Defines how the client handles whitespace and indentation +---when accepting a completion item that uses multi line +---text in either `insertText` or `textEdit`. +--- +---@since 3.17.0 +---@field insertTextMode? lsp.InsertTextMode +---The client supports to send additional context information for a +---`textDocument/completion` request. +---@field contextSupport? boolean +---The client supports the following `CompletionList` specific +---capabilities. +--- +---@since 3.17.0 +---@field completionList? anonym28 + +---@class lsp.HoverClientCapabilities +---Whether hover supports dynamic registration. +---@field dynamicRegistration? boolean +---Client supports the following content formats for the content +---property. The order describes the preferred format of the client. +---@field contentFormat? lsp.MarkupKind[] + +---Client Capabilities for a {@link SignatureHelpRequest}. +---@class lsp.SignatureHelpClientCapabilities +---Whether signature help supports dynamic registration. +---@field dynamicRegistration? boolean +---The client supports the following `SignatureInformation` +---specific properties. +---@field signatureInformation? anonym30 +---The client supports to send additional context information for a +---`textDocument/signatureHelp` request. A client that opts into +---contextSupport will also support the `retriggerCharacters` on +---`SignatureHelpOptions`. +--- +---@since 3.15.0 +---@field contextSupport? boolean + +---@since 3.14.0 +---@class lsp.DeclarationClientCapabilities +---Whether declaration supports dynamic registration. If this is set to `true` +---the client supports the new `DeclarationRegistrationOptions` return value +---for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---The client supports additional metadata in the form of declaration links. +---@field linkSupport? boolean + +---Client Capabilities for a {@link DefinitionRequest}. +---@class lsp.DefinitionClientCapabilities +---Whether definition supports dynamic registration. +---@field dynamicRegistration? boolean +---The client supports additional metadata in the form of definition links. +--- +---@since 3.14.0 +---@field linkSupport? boolean + +---Since 3.6.0 +---@class lsp.TypeDefinitionClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `TypeDefinitionRegistrationOptions` return value +---for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---The client supports additional metadata in the form of definition links. +--- +---Since 3.14.0 +---@field linkSupport? boolean + +---@since 3.6.0 +---@class lsp.ImplementationClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `ImplementationRegistrationOptions` return value +---for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---The client supports additional metadata in the form of definition links. +--- +---@since 3.14.0 +---@field linkSupport? boolean + +---Client Capabilities for a {@link ReferencesRequest}. +---@class lsp.ReferenceClientCapabilities +---Whether references supports dynamic registration. +---@field dynamicRegistration? boolean + +---Client Capabilities for a {@link DocumentHighlightRequest}. +---@class lsp.DocumentHighlightClientCapabilities +---Whether document highlight supports dynamic registration. +---@field dynamicRegistration? boolean + +---Client Capabilities for a {@link DocumentSymbolRequest}. +---@class lsp.DocumentSymbolClientCapabilities +---Whether document symbol supports dynamic registration. +---@field dynamicRegistration? boolean +---Specific capabilities for the `SymbolKind` in the +---`textDocument/documentSymbol` request. +---@field symbolKind? anonym31 +---The client supports hierarchical document symbols. +---@field hierarchicalDocumentSymbolSupport? boolean +---The client supports tags on `SymbolInformation`. Tags are supported on +---`DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true. +---Clients supporting tags have to handle unknown tags gracefully. +--- +---@since 3.16.0 +---@field tagSupport? anonym32 +---The client supports an additional label presented in the UI when +---registering a document symbol provider. +--- +---@since 3.16.0 +---@field labelSupport? boolean + +---The Client Capabilities of a {@link CodeActionRequest}. +---@class lsp.CodeActionClientCapabilities +---Whether code action supports dynamic registration. +---@field dynamicRegistration? boolean +---The client support code action literals of type `CodeAction` as a valid +---response of the `textDocument/codeAction` request. If the property is not +---set the request can only return `Command` literals. +--- +---@since 3.8.0 +---@field codeActionLiteralSupport? anonym34 +---Whether code action supports the `isPreferred` property. +--- +---@since 3.15.0 +---@field isPreferredSupport? boolean +---Whether code action supports the `disabled` property. +--- +---@since 3.16.0 +---@field disabledSupport? boolean +---Whether code action supports the `data` property which is +---preserved between a `textDocument/codeAction` and a +---`codeAction/resolve` request. +--- +---@since 3.16.0 +---@field dataSupport? boolean +---Whether the client supports resolving additional code action +---properties via a separate `codeAction/resolve` request. +--- +---@since 3.16.0 +---@field resolveSupport? anonym35 +---Whether the client honors the change annotations in +---text edits and resource operations returned via the +---`CodeAction#edit` property by for example presenting +---the workspace edit in the user interface and asking +---for confirmation. +--- +---@since 3.16.0 +---@field honorsChangeAnnotations? boolean + +---The client capabilities of a {@link CodeLensRequest}. +---@class lsp.CodeLensClientCapabilities +---Whether code lens supports dynamic registration. +---@field dynamicRegistration? boolean + +---The client capabilities of a {@link DocumentLinkRequest}. +---@class lsp.DocumentLinkClientCapabilities +---Whether document link supports dynamic registration. +---@field dynamicRegistration? boolean +---Whether the client supports the `tooltip` property on `DocumentLink`. +--- +---@since 3.15.0 +---@field tooltipSupport? boolean + +---@class lsp.DocumentColorClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `DocumentColorRegistrationOptions` return value +---for the corresponding server capability as well. +---@field dynamicRegistration? boolean + +---Client capabilities of a {@link DocumentFormattingRequest}. +---@class lsp.DocumentFormattingClientCapabilities +---Whether formatting supports dynamic registration. +---@field dynamicRegistration? boolean + +---Client capabilities of a {@link DocumentRangeFormattingRequest}. +---@class lsp.DocumentRangeFormattingClientCapabilities +---Whether range formatting supports dynamic registration. +---@field dynamicRegistration? boolean + +---Client capabilities of a {@link DocumentOnTypeFormattingRequest}. +---@class lsp.DocumentOnTypeFormattingClientCapabilities +---Whether on type formatting supports dynamic registration. +---@field dynamicRegistration? boolean + +---@class lsp.RenameClientCapabilities +---Whether rename supports dynamic registration. +---@field dynamicRegistration? boolean +---Client supports testing for validity of rename operations +---before execution. +--- +---@since 3.12.0 +---@field prepareSupport? boolean +---Client supports the default behavior result. +--- +---The value indicates the default behavior used by the +---client. +--- +---@since 3.16.0 +---@field prepareSupportDefaultBehavior? lsp.PrepareSupportDefaultBehavior +---Whether the client honors the change annotations in +---text edits and resource operations returned via the +---rename request's workspace edit by for example presenting +---the workspace edit in the user interface and asking +---for confirmation. +--- +---@since 3.16.0 +---@field honorsChangeAnnotations? boolean + +---@class lsp.FoldingRangeClientCapabilities +---Whether implementation supports dynamic registration for folding range +---providers. If this is set to `true` the client supports the new +---`FoldingRangeRegistrationOptions` return value for the corresponding +---server capability as well. +---@field dynamicRegistration? boolean +---The maximum number of folding ranges that the client prefers to receive +---per document. The value serves as a hint, servers are free to follow the +---limit. +---@field rangeLimit? uinteger +---If set, the client signals that it only supports folding complete lines. +---If set, client will ignore specified `startCharacter` and `endCharacter` +---properties in a FoldingRange. +---@field lineFoldingOnly? boolean +---Specific options for the folding range kind. +--- +---@since 3.17.0 +---@field foldingRangeKind? anonym36 +---Specific options for the folding range. +--- +---@since 3.17.0 +---@field foldingRange? anonym37 + +---@class lsp.SelectionRangeClientCapabilities +---Whether implementation supports dynamic registration for selection range providers. If this is set to `true` +---the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server +---capability as well. +---@field dynamicRegistration? boolean + +---The publish diagnostic client capabilities. +---@class lsp.PublishDiagnosticsClientCapabilities +---Whether the clients accepts diagnostics with related information. +---@field relatedInformation? boolean +---Client supports the tag property to provide meta data about a diagnostic. +---Clients supporting tags have to handle unknown tags gracefully. +--- +---@since 3.15.0 +---@field tagSupport? anonym38 +---Whether the client interprets the version property of the +---`textDocument/publishDiagnostics` notification's parameter. +--- +---@since 3.15.0 +---@field versionSupport? boolean +---Client supports a codeDescription property +--- +---@since 3.16.0 +---@field codeDescriptionSupport? boolean +---Whether code action supports the `data` property which is +---preserved between a `textDocument/publishDiagnostics` and +---`textDocument/codeAction` request. +--- +---@since 3.16.0 +---@field dataSupport? boolean + +---@since 3.16.0 +---@class lsp.CallHierarchyClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean + +---@since 3.16.0 +---@class lsp.SemanticTokensClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---Which requests the client supports and might send to the server +---depending on the server's capability. Please note that clients might not +---show semantic tokens or degrade some of the user experience if a range +---or full request is advertised by the client but not provided by the +---server. If for example the client capability `requests.full` and +---`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 anonym41 +---The token types that the client supports. +---@field tokenTypes string[] +---The token modifiers that the client supports. +---@field tokenModifiers string[] +---The token formats the clients supports. +---@field formats lsp.TokenFormat[] +---Whether the client supports tokens that can overlap each other. +---@field overlappingTokenSupport? boolean +---Whether the client supports tokens that can span multiple lines. +---@field multilineTokenSupport? boolean +---Whether the client allows the server to actively cancel a +---semantic token request, e.g. supports returning +---LSPErrorCodes.ServerCancelled. If a server does the client +---needs to retrigger the request. +--- +---@since 3.17.0 +---@field serverCancelSupport? boolean +---Whether the client uses semantic tokens to augment existing +---syntax tokens. If set to `true` client side created syntax +---tokens and semantic tokens are both used for colorization. If +---set to `false` the client only uses the returned semantic tokens +---for colorization. +--- +---If the value is `undefined` then the client behavior is not +---specified. +--- +---@since 3.17.0 +---@field augmentsSyntaxTokens? boolean + +---Client capabilities for the linked editing range request. +--- +---@since 3.16.0 +---@class lsp.LinkedEditingRangeClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean + +---Client capabilities specific to the moniker request. +--- +---@since 3.16.0 +---@class lsp.MonikerClientCapabilities +---Whether moniker supports dynamic registration. If this is set to `true` +---the client supports the new `MonikerRegistrationOptions` return value +---for the corresponding server capability as well. +---@field dynamicRegistration? boolean + +---@since 3.17.0 +---@class lsp.TypeHierarchyClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean + +---Client capabilities specific to inline values. +--- +---@since 3.17.0 +---@class lsp.InlineValueClientCapabilities +---Whether implementation supports dynamic registration for inline value providers. +---@field dynamicRegistration? boolean + +---Inlay hint client capabilities. +--- +---@since 3.17.0 +---@class lsp.InlayHintClientCapabilities +---Whether inlay hints support dynamic registration. +---@field dynamicRegistration? boolean +---Indicates which properties a client can resolve lazily on an inlay +---hint. +---@field resolveSupport? anonym42 + +---Client capabilities specific to diagnostic pull requests. +--- +---@since 3.17.0 +---@class lsp.DiagnosticClientCapabilities +---Whether implementation supports dynamic registration. If this is set to `true` +---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---Whether the clients supports related documents for document diagnostic pulls. +---@field relatedDocumentSupport? boolean + +---Client capabilities specific to inline completions. +--- +---@since 3.18.0 +---@proposed +---@class lsp.InlineCompletionClientCapabilities +---Whether implementation supports dynamic registration for inline completion providers. +---@field dynamicRegistration? boolean + +---Notebook specific client capabilities. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentSyncClientCapabilities +---Whether implementation supports dynamic registration. If this is +---set to `true` the client supports the new +---`(TextDocumentRegistrationOptions & StaticRegistrationOptions)` +---return value for the corresponding server capability as well. +---@field dynamicRegistration? boolean +---The client supports sending execution summary data per cell. +---@field executionSummarySupport? boolean + +---Show message request client capabilities +---@class lsp.ShowMessageRequestClientCapabilities +---Capabilities specific to the `MessageActionItem` type. +---@field messageActionItem? anonym43 + +---Client capabilities for the showDocument request. +--- +---@since 3.16.0 +---@class lsp.ShowDocumentClientCapabilities +---The client has support for the showDocument +---request. +---@field support boolean + +---Client capabilities specific to regular expressions. +--- +---@since 3.16.0 +---@class lsp.RegularExpressionsClientCapabilities +---The engine's name. +---@field engine string +---The engine's version. +---@field version? string + +---Client capabilities specific to the used markdown parser. +--- +---@since 3.16.0 +---@class lsp.MarkdownClientCapabilities +---The name of the parser. +---@field parser string +---The version of the parser. +---@field version? string +---A list of HTML tags that the client allows / supports in +---Markdown. +--- +---@since 3.17.0 +---@field allowedTags? string[] + +---A set of predefined token types. This set is not fixed +---an clients can specify additional token types via the +---corresponding client capabilities. +--- +---@since 3.16.0 +---@alias lsp.SemanticTokenTypes +---| "namespace" # namespace +---| "type" # type +---| "class" # class +---| "enum" # enum +---| "interface" # interface +---| "struct" # struct +---| "typeParameter" # typeParameter +---| "parameter" # parameter +---| "variable" # variable +---| "property" # property +---| "enumMember" # enumMember +---| "event" # event +---| "function" # function +---| "method" # method +---| "macro" # macro +---| "keyword" # keyword +---| "modifier" # modifier +---| "comment" # comment +---| "string" # string +---| "number" # number +---| "regexp" # regexp +---| "operator" # operator +---| "decorator" # decorator + +---A set of predefined token modifiers. This set is not fixed +---an clients can specify additional token types via the +---corresponding client capabilities. +--- +---@since 3.16.0 +---@alias lsp.SemanticTokenModifiers +---| "declaration" # declaration +---| "definition" # definition +---| "readonly" # readonly +---| "static" # static +---| "deprecated" # deprecated +---| "abstract" # abstract +---| "async" # async +---| "modification" # modification +---| "documentation" # documentation +---| "defaultLibrary" # defaultLibrary + +---The document diagnostic report kinds. +--- +---@since 3.17.0 +---@alias lsp.DocumentDiagnosticReportKind +---| "full" # Full +---| "unchanged" # Unchanged + +---Predefined error codes. +---@alias lsp.ErrorCodes +---| -32700 # ParseError +---| -32600 # InvalidRequest +---| -32601 # MethodNotFound +---| -32602 # InvalidParams +---| -32603 # InternalError +---| -32002 # ServerNotInitialized +---| -32001 # UnknownErrorCode + +---@alias lsp.LSPErrorCodes +---| -32803 # RequestFailed +---| -32802 # ServerCancelled +---| -32801 # ContentModified +---| -32800 # RequestCancelled + +---A set of predefined range kinds. +---@alias lsp.FoldingRangeKind +---| "comment" # Comment +---| "imports" # Imports +---| "region" # Region + +---A symbol kind. +---@alias lsp.SymbolKind +---| 1 # File +---| 2 # Module +---| 3 # Namespace +---| 4 # Package +---| 5 # Class +---| 6 # Method +---| 7 # Property +---| 8 # Field +---| 9 # Constructor +---| 10 # Enum +---| 11 # Interface +---| 12 # Function +---| 13 # Variable +---| 14 # Constant +---| 15 # String +---| 16 # Number +---| 17 # Boolean +---| 18 # Array +---| 19 # Object +---| 20 # Key +---| 21 # Null +---| 22 # EnumMember +---| 23 # Struct +---| 24 # Event +---| 25 # Operator +---| 26 # TypeParameter + +---Symbol tags are extra annotations that tweak the rendering of a symbol. +--- +---@since 3.16 +---@alias lsp.SymbolTag +---| 1 # Deprecated + +---Moniker uniqueness level to define scope of the moniker. +--- +---@since 3.16.0 +---@alias lsp.UniquenessLevel +---| "document" # document +---| "project" # project +---| "group" # group +---| "scheme" # scheme +---| "global" # global + +---The moniker kind. +--- +---@since 3.16.0 +---@alias lsp.MonikerKind +---| "import" # import +---| "export" # export +---| "local" # local + +---Inlay hint kinds. +--- +---@since 3.17.0 +---@alias lsp.InlayHintKind +---| 1 # Type +---| 2 # Parameter + +---The message type +---@alias lsp.MessageType +---| 1 # Error +---| 2 # Warning +---| 3 # Info +---| 4 # Log + +---Defines how the host (editor) should sync +---document changes to the language server. +---@alias lsp.TextDocumentSyncKind +---| 0 # None +---| 1 # Full +---| 2 # Incremental + +---Represents reasons why a text document is saved. +---@alias lsp.TextDocumentSaveReason +---| 1 # Manual +---| 2 # AfterDelay +---| 3 # FocusOut + +---The kind of a completion entry. +---@alias lsp.CompletionItemKind +---| 1 # Text +---| 2 # Method +---| 3 # Function +---| 4 # Constructor +---| 5 # Field +---| 6 # Variable +---| 7 # Class +---| 8 # Interface +---| 9 # Module +---| 10 # Property +---| 11 # Unit +---| 12 # Value +---| 13 # Enum +---| 14 # Keyword +---| 15 # Snippet +---| 16 # Color +---| 17 # File +---| 18 # Reference +---| 19 # Folder +---| 20 # EnumMember +---| 21 # Constant +---| 22 # Struct +---| 23 # Event +---| 24 # Operator +---| 25 # TypeParameter + +---Completion item tags are extra annotations that tweak the rendering of a completion +---item. +--- +---@since 3.15.0 +---@alias lsp.CompletionItemTag +---| 1 # Deprecated + +---Defines whether the insert text in a completion item should be interpreted as +---plain text or a snippet. +---@alias lsp.InsertTextFormat +---| 1 # PlainText +---| 2 # Snippet + +---How whitespace and indentation is handled during completion +---item insertion. +--- +---@since 3.16.0 +---@alias lsp.InsertTextMode +---| 1 # asIs +---| 2 # adjustIndentation + +---A document highlight kind. +---@alias lsp.DocumentHighlightKind +---| 1 # Text +---| 2 # Read +---| 3 # Write + +---A set of predefined code action kinds +---@alias lsp.CodeActionKind +---| "" # Empty +---| "quickfix" # QuickFix +---| "refactor" # Refactor +---| "refactor.extract" # RefactorExtract +---| "refactor.inline" # RefactorInline +---| "refactor.rewrite" # RefactorRewrite +---| "source" # Source +---| "source.organizeImports" # SourceOrganizeImports +---| "source.fixAll" # SourceFixAll + +---@alias lsp.TraceValues +---| "off" # Off +---| "messages" # Messages +---| "verbose" # Verbose + +---Describes the content type that a client supports in various +---result literals like `Hover`, `ParameterInfo` or `CompletionItem`. +--- +---Please note that `MarkupKinds` must not start with a `$`. This kinds +---are reserved for internal usage. +---@alias lsp.MarkupKind +---| "plaintext" # PlainText +---| "markdown" # Markdown + +---Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. +--- +---@since 3.18.0 +---@proposed +---@alias lsp.InlineCompletionTriggerKind +---| 0 # Invoked +---| 1 # Automatic + +---A set of predefined position encoding kinds. +--- +---@since 3.17.0 +---@alias lsp.PositionEncodingKind +---| "utf-8" # UTF8 +---| "utf-16" # UTF16 +---| "utf-32" # UTF32 + +---The file event type +---@alias lsp.FileChangeType +---| 1 # Created +---| 2 # Changed +---| 3 # Deleted + +---@alias lsp.WatchKind +---| 1 # Create +---| 2 # Change +---| 4 # Delete + +---The diagnostic's severity. +---@alias lsp.DiagnosticSeverity +---| 1 # Error +---| 2 # Warning +---| 3 # Information +---| 4 # Hint + +---The diagnostic tags. +--- +---@since 3.15.0 +---@alias lsp.DiagnosticTag +---| 1 # Unnecessary +---| 2 # Deprecated + +---How a completion was triggered +---@alias lsp.CompletionTriggerKind +---| 1 # Invoked +---| 2 # TriggerCharacter +---| 3 # TriggerForIncompleteCompletions + +---How a signature help was triggered. +--- +---@since 3.15.0 +---@alias lsp.SignatureHelpTriggerKind +---| 1 # Invoked +---| 2 # TriggerCharacter +---| 3 # ContentChange + +---The reason why code actions were requested. +--- +---@since 3.17.0 +---@alias lsp.CodeActionTriggerKind +---| 1 # Invoked +---| 2 # Automatic + +---A pattern kind describing if a glob pattern matches a file a folder or +---both. +--- +---@since 3.16.0 +---@alias lsp.FileOperationPatternKind +---| "file" # file +---| "folder" # folder + +---A notebook cell kind. +--- +---@since 3.17.0 +---@alias lsp.NotebookCellKind +---| 1 # Markup +---| 2 # Code + +---@alias lsp.ResourceOperationKind +---| "create" # Create +---| "rename" # Rename +---| "delete" # Delete + +---@alias lsp.FailureHandlingKind +---| "abort" # Abort +---| "transactional" # Transactional +---| "textOnlyTransactional" # TextOnlyTransactional +---| "undo" # Undo + +---@alias lsp.PrepareSupportDefaultBehavior +---| 1 # Identifier + +---@alias lsp.TokenFormat +---| "relative" # Relative + +---The definition of a symbol represented as one or many {@link Location locations}. +---For most programming languages there is only one location at which a symbol is +---defined. +--- +---Servers should prefer returning `DefinitionLink` over `Definition` if supported +---by the client. +---@alias lsp.Definition lsp.Location|lsp.Location[] + +---Information about where a symbol is defined. +--- +---Provides additional metadata over normal {@link Location location} definitions, including the range of +---the defining symbol +---@alias lsp.DefinitionLink lsp.LocationLink + +---LSP arrays. +---@since 3.17.0 +---@alias lsp.LSPArray lsp.LSPAny[] + +---The LSP any type. +---Please note that strictly speaking a property with the value `undefined` +---can't be converted into JSON preserving the property name. However for +---convenience it is allowed and assumed that all these properties are +---optional as well. +---@since 3.17.0 +---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|integer|uinteger|decimal|boolean|lsp.null + +---The declaration of a symbol representation as one or many {@link Location locations}. +---@alias lsp.Declaration lsp.Location|lsp.Location[] + +---Information about where a symbol is declared. +--- +---Provides additional metadata over normal {@link Location location} declarations, including the range of +---the declaring symbol. +--- +---Servers should prefer returning `DeclarationLink` over `Declaration` if supported +---by the client. +---@alias lsp.DeclarationLink lsp.LocationLink + +---Inline value information can be provided by different means: +---- directly as a text value (class InlineValueText). +---- as a name to use for a variable lookup (class InlineValueVariableLookup) +---- as an evaluatable expression (class InlineValueEvaluatableExpression) +---The InlineValue types combines all inline value types into one type. +--- +---@since 3.17.0 +---@alias lsp.InlineValue lsp.InlineValueText|lsp.InlineValueVariableLookup|lsp.InlineValueEvaluatableExpression + +---The result of a document diagnostic pull request. A report can +---either be a full report containing all diagnostics for the +---requested document or an unchanged report indicating that nothing +---has changed in terms of diagnostics in comparison to the last +---pull request. +--- +---@since 3.17.0 +---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport + +---@alias lsp.PrepareRenameResult lsp.Range|anonym44|anonym45 + +---A document selector is the combination of one or many document filters. +--- +---@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`; +--- +---The use of a string as a document filter is deprecated @since 3.16.0. +---@alias lsp.DocumentSelector lsp.DocumentFilter[] + +---@alias lsp.ProgressToken integer|string + +---An identifier to refer to a change annotation stored with a workspace edit. +---@alias lsp.ChangeAnnotationIdentifier string + +---A workspace diagnostic document report. +--- +---@since 3.17.0 +---@alias lsp.WorkspaceDocumentDiagnosticReport lsp.WorkspaceFullDocumentDiagnosticReport|lsp.WorkspaceUnchangedDocumentDiagnosticReport + +---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 anonym46|anonym47 + +---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 +---is semantically equal to the optional language identifier in fenced code blocks in GitHub +---issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting +--- +---The pair of a language and a value is an equivalent to markdown: +---```${language} +---${value} +---``` +--- +---Note that markdown strings will be sanitized - that means html will be escaped. +---@deprecated use MarkupContent instead. +---@alias lsp.MarkedString string|anonym48 + +---A document filter describes a top level text document or +---a notebook cell document. +--- +---@since 3.17.0 - proposed support for NotebookCellTextDocumentFilter. +---@alias lsp.DocumentFilter lsp.TextDocumentFilter|lsp.NotebookCellTextDocumentFilter + +---LSP object definition. +---@since 3.17.0 +---@alias lsp.LSPObject table + +---The glob pattern. Either a string pattern or a relative pattern. +--- +---@since 3.17.0 +---@alias lsp.GlobPattern lsp.Pattern|lsp.RelativePattern + +---A document filter denotes a document by different properties like +---the {@link TextDocument.languageId language}, the {@link Uri.scheme scheme} of +---its resource, or a glob-pattern that is applied to the {@link TextDocument.fileName path}. +--- +---Glob patterns can have the following syntax: +---- `*` to match one or more characters in a path segment +---- `?` to match on one character in a path segment +---- `**` to match any number of path segments, including none +---- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +--- +---@sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }` +---@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` +--- +---@since 3.17.0 +---@alias lsp.TextDocumentFilter anonym49|anonym50|anonym51 + +---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 anonym52|anonym53|anonym54 + +---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 +---- `?` to match on one character in a path segment +---- `**` to match any number of path segments, including none +---- `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files) +---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) +--- +---@since 3.17.0 +---@alias lsp.Pattern string + +---@class anonym1 +---The name of the server as defined by the server. +---@field name string +---The server's version as defined by the server. +---@field version? string + +---@class anonym3 +---@field insert lsp.Range +---@field replace lsp.Range + +---@class anonym2 +---A default commit character set. +--- +---@since 3.17.0 +---@field commitCharacters? string[] +---A default edit range. +--- +---@since 3.17.0 +---@field editRange? lsp.Range|anonym3 +---A default insert text format. +--- +---@since 3.17.0 +---@field insertTextFormat? lsp.InsertTextFormat +---A default insert text mode. +--- +---@since 3.17.0 +---@field insertTextMode? lsp.InsertTextMode +---A default data value. +--- +---@since 3.17.0 +---@field data? lsp.LSPAny + +---@class anonym4 +---Human readable description of why the code action is currently disabled. +--- +---This is displayed in the code actions UI. +---@field reason string + +---@class anonym5 +---@field uri lsp.DocumentUri + +---@class anonym6 + +---@class anonym7 +---The server supports deltas for full documents. +---@field delta? boolean + +---@class anonym9 +---The change to the cell array. +---@field array lsp.NotebookCellArrayChange +---Additional opened cell text documents. +---@field didOpen? lsp.TextDocumentItem[] +---Additional closed cell text documents. +---@field didClose? lsp.TextDocumentIdentifier[] + +---@class anonym10 +---@field document lsp.VersionedTextDocumentIdentifier +---@field changes lsp.TextDocumentContentChangeEvent[] + +---@class anonym8 +---Changes to the cell structure to add or +---remove cells. +---@field structure? anonym9 +---Changes to notebook cells properties like its +---kind, execution summary or metadata. +---@field data? lsp.NotebookCell[] +---Changes to the text content of notebook cells. +---@field textContent? anonym10[] + +---@class anonym11 +---The name of the client as defined by the client. +---@field name string +---The client's version as defined by the client. +---@field version? string + +---@class anonym12 +---The server supports workspace folder. +--- +---@since 3.6.0 +---@field workspaceFolders? lsp.WorkspaceFoldersServerCapabilities +---The server is interested in notifications/requests for operations on files. +--- +---@since 3.16.0 +---@field fileOperations? lsp.FileOperationOptions + +---@class anonym13 +---The server has support for completion item label +---details (see also `CompletionItemLabelDetails`) when +---receiving a completion item in a resolve call. +--- +---@since 3.17.0 +---@field labelDetailsSupport? boolean + +---@class anonym15 +---@field language string + +---@class anonym14 +---The notebook to be synced If a string +---value is provided it matches against the +---notebook type. '*' matches every notebook. +---@field notebook string|lsp.NotebookDocumentFilter +---The cells of the matching notebook to be synced. +---@field cells? anonym15[] + +---@class anonym17 +---@field language string + +---@class anonym16 +---The notebook to be synced If a string +---value is provided it matches against the +---notebook type. '*' matches every notebook. +---@field notebook? string|lsp.NotebookDocumentFilter +---The cells of the matching notebook to be synced. +---@field cells anonym17[] + +---@class anonym18 +---The client will actively cancel the request. +---@field cancel boolean +---The list of requests for which the client +---will retry the request if it receives a +---response with error code `ContentModified` +---@field retryOnContentModified string[] + +---@class anonym19 +---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 anonym20 +---The symbol kind values the client supports. When this +---property exists the client also guarantees that it will +---handle values outside its set gracefully and falls back +---to a default value when unknown. +--- +---If this property is not present the client only supports +---the symbol kinds from `File` to `Array` as defined in +---the initial version of the protocol. +---@field valueSet? lsp.SymbolKind[] + +---@class anonym21 +---The tags supported by the client. +---@field valueSet lsp.SymbolTag[] + +---@class anonym22 +---The properties that a client can resolve lazily. Usually +---`location.range` +---@field properties string[] + +---@class anonym24 +---The tags supported by the client. +---@field valueSet lsp.CompletionItemTag[] + +---@class anonym25 +---The properties that a client can resolve lazily. +---@field properties string[] + +---@class anonym26 +---@field valueSet lsp.InsertTextMode[] + +---@class anonym23 +---Client supports snippets as insert text. +--- +---A snippet can define tab stops and placeholders with `$1`, `$2` +---and `${3:foo}`. `$0` defines the final tab stop, it defaults to +---the end of the snippet. Placeholders with equal identifiers are linked, +---that is typing in one will update others too. +---@field snippetSupport? boolean +---Client supports commit characters on a completion item. +---@field commitCharactersSupport? boolean +---Client supports the following content formats for the documentation +---property. The order describes the preferred format of the client. +---@field documentationFormat? lsp.MarkupKind[] +---Client supports the deprecated property on a completion item. +---@field deprecatedSupport? boolean +---Client supports the preselect property on a completion item. +---@field preselectSupport? boolean +---Client supports the tag property on a completion item. Clients supporting +---tags have to handle unknown tags gracefully. Clients especially need to +---preserve unknown tags when sending a completion item back to the server in +---a resolve call. +--- +---@since 3.15.0 +---@field tagSupport? anonym24 +---Client support insert replace edit to control different behavior if a +---completion item is inserted in the text or should replace text. +--- +---@since 3.16.0 +---@field insertReplaceSupport? boolean +---Indicates which properties a client can resolve lazily on a completion +---item. Before version 3.16.0 only the predefined properties `documentation` +---and `details` could be resolved lazily. +--- +---@since 3.16.0 +---@field resolveSupport? anonym25 +---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? anonym26 +---The client has support for completion item label +---details (see also `CompletionItemLabelDetails`). +--- +---@since 3.17.0 +---@field labelDetailsSupport? boolean + +---@class anonym27 +---The completion item kind values the client supports. When this +---property exists the client also guarantees that it will +---handle values outside its set gracefully and falls back +---to a default value when unknown. +--- +---If this property is not present the client only supports +---the completion items kinds from `Text` to `Reference` as defined in +---the initial version of the protocol. +---@field valueSet? lsp.CompletionItemKind[] + +---@class anonym28 +---The client supports the following itemDefaults on +---a completion list. +--- +---The value lists the supported property names of the +---`CompletionList.itemDefaults` object. If omitted +---no properties are supported. +--- +---@since 3.17.0 +---@field itemDefaults? string[] + +---@class anonym30 +---The client supports processing label offsets instead of a +---simple label string. +--- +---@since 3.14.0 +---@field labelOffsetSupport? boolean + +---@class anonym29 +---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? anonym30 +---The client supports the `activeParameter` property on `SignatureInformation` +---literal. +--- +---@since 3.16.0 +---@field activeParameterSupport? boolean + +---@class anonym31 +---The symbol kind values the client supports. When this +---property exists the client also guarantees that it will +---handle values outside its set gracefully and falls back +---to a default value when unknown. +--- +---If this property is not present the client only supports +---the symbol kinds from `File` to `Array` as defined in +---the initial version of the protocol. +---@field valueSet? lsp.SymbolKind[] + +---@class anonym32 +---The tags supported by the client. +---@field valueSet lsp.SymbolTag[] + +---@class anonym34 +---The code action kind values the client supports. When this +---property exists the client also guarantees that it will +---handle values outside its set gracefully and falls back +---to a default value when unknown. +---@field valueSet lsp.CodeActionKind[] + +---@class anonym33 +---The code action kind is support with the following value +---set. +---@field codeActionKind anonym34 + +---@class anonym35 +---The properties that a client can resolve lazily. +---@field properties string[] + +---@class anonym36 +---The folding range kind values the client supports. When this +---property exists the client also guarantees that it will +---handle values outside its set gracefully and falls back +---to a default value when unknown. +---@field valueSet? lsp.FoldingRangeKind[] + +---@class anonym37 +---If set, the client signals that it supports setting collapsedText on +---folding ranges to display custom labels instead of the default text. +--- +---@since 3.17.0 +---@field collapsedText? boolean + +---@class anonym38 +---The tags supported by the client. +---@field valueSet lsp.DiagnosticTag[] + +---@class anonym40 + +---@class anonym41 +---The client will send the `textDocument/semanticTokens/full/delta` request if +---the server provides a corresponding handler. +---@field delta? boolean + +---@class anonym39 +---The client will send the `textDocument/semanticTokens/range` request if +---the server provides a corresponding handler. +---@field range? boolean|anonym40 +---The client will send the `textDocument/semanticTokens/full` request if +---the server provides a corresponding handler. +---@field full? boolean|anonym41 + +---@class anonym42 +---The properties that a client can resolve lazily. +---@field properties string[] + +---@class anonym43 +---Whether the client supports additional attributes which +---are preserved and send back to the server in the +---request's response. +---@field additionalPropertiesSupport? boolean + +---@class anonym44 +---@field range lsp.Range +---@field placeholder string + +---@class anonym45 +---@field defaultBehavior boolean + +---@class anonym46 +---The range of the document that changed. +---@field range lsp.Range +---The optional length of the range that got replaced. +--- +---@deprecated use range instead. +---@field rangeLength? uinteger +---The new text for the provided range. +---@field text string + +---@class anonym47 +---The new text of the whole document. +---@field text string + +---@class anonym48 +---@field language string +---@field value string + +---@class anonym49 +---A language id, like `typescript`. +---@field language string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme? string +---A glob pattern, like `*.{ts,js}`. +---@field pattern? string + +---@class anonym50 +---A language id, like `typescript`. +---@field language? string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme string +---A glob pattern, like `*.{ts,js}`. +---@field pattern? string + +---@class anonym51 +---A language id, like `typescript`. +---@field language? string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme? string +---A glob pattern, like `*.{ts,js}`. +---@field pattern string + +---@class anonym52 +---The type of the enclosing notebook. +---@field notebookType string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme? string +---A glob pattern. +---@field pattern? string + +---@class anonym53 +---The type of the enclosing notebook. +---@field notebookType? string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme string +---A glob pattern. +---@field pattern? string + +---@class anonym54 +---The type of the enclosing notebook. +---@field notebookType? string +---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`. +---@field scheme? string +---A glob pattern. +---@field pattern string -- cgit From 4ecc71f6fc7377403ed91ae5bc32992a5d08f678 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 7 Jun 2023 13:39:41 +0100 Subject: fix(lsp): reduce diagnostics and add more types (#23948) --- runtime/lua/vim/lsp.lua | 144 +++++++++++++++++++++++++-------------- runtime/lua/vim/lsp/protocol.lua | 1 + 2 files changed, 92 insertions(+), 53 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5f7a95ae14..532504a7db 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -147,9 +147,9 @@ local function next_client_id() return client_index end -- Tracks all clients created via lsp.start_client -local active_clients = {} -local all_buffer_active_clients = {} -local uninitialized_clients = {} +local active_clients = {} --- @type table +local all_buffer_active_clients = {} --- @type table> +local uninitialized_clients = {} --- @type table ---@private ---@param bufnr? integer @@ -166,7 +166,7 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids) end if restrict_client_ids and #restrict_client_ids > 0 then - local filtered_client_ids = {} + 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 @@ -257,9 +257,10 @@ end ---@private --- Validates a client configuration as given to |vim.lsp.start_client()|. --- ----@param config (table) ----@return table config Cleaned config, containing the command, its ----arguments, and a valid encoding. +---@param config (lsp.ClientConfig) +---@return (string|fun(dispatchers:table):table) Command +---@return string[] Arguments +---@return string Encoding. local function validate_client_config(config) validate({ config = { config, 't' }, @@ -290,22 +291,19 @@ local function validate_client_config(config) 'flags.debounce_text_changes must be a number with the debounce time in milliseconds' ) - local cmd, cmd_args - if type(config.cmd) == 'function' then - cmd = config.cmd + local cmd, cmd_args --- @type (string|fun(dispatchers:table):table), string[] + local config_cmd = config.cmd + if type(config_cmd) == 'function' then + cmd = config_cmd else - cmd, cmd_args = lsp._cmd_parts(config.cmd) + cmd, cmd_args = lsp._cmd_parts(config_cmd) end local offset_encoding = valid_encodings.UTF16 if config.offset_encoding then offset_encoding = validate_encoding(config.offset_encoding) end - return { - cmd = cmd, - cmd_args = cmd_args, - offset_encoding = offset_encoding, - } + return cmd, cmd_args, offset_encoding end ---@private @@ -328,10 +326,11 @@ end --- only the first returned value will be memoized and returned. The function will only be run once, --- even if it has side effects. --- ----@param fn (function) Function to run ----@return function fn Memoized function +---@generic T: function +---@param fn (T) Function to run +---@return T local function once(fn) - local value + local value --- @type any local ran = false return function(...) if not ran then @@ -371,7 +370,7 @@ do --- @field lines string[] snapshot of buffer lines from last didChange --- @field lines_tmp string[] --- @field pending_changes table[] List of debounced changes in incremental sync mode - --- @field timer nil|uv.uv_timer_t uv_timer + --- @field timer nil|uv_timer_t uv_timer --- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification --- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet --- @field refs integer how many clients are using this group @@ -610,7 +609,7 @@ do ---@private function changetracking.send_changes(bufnr, firstline, lastline, new_lastline) - local groups = {} + local groups = {} ---@type table for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do local group = get_group(client) groups[group_key(group)] = group @@ -812,6 +811,10 @@ function lsp.client() error() end +--- @class lsp.StartOpts +--- @field reuse_client fun(client: lsp.Client, config: table): boolean +--- @field bufnr integer + --- 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`. --- Attaches the current buffer to the client. @@ -849,7 +852,7 @@ end --- `ftplugin/.lua` (See |ftplugin-name|) --- ---@param config table Same configuration as documented in |vim.lsp.start_client()| ----@param opts nil|table Optional keyword arguments: +---@param opts (nil|lsp.StartOpts) Optional keyword arguments: --- - reuse_client (fun(client: client, config: table): boolean) --- Predicate used to decide if a client should be re-used. --- Used on all running clients. @@ -858,14 +861,13 @@ end --- - bufnr (number) --- Buffer handle to attach to if starting or re-using a --- client (0 for current). ----@return number|nil client_id +---@return integer|nil client_id function lsp.start(config, opts) opts = opts or {} local reuse_client = opts.reuse_client or function(client, conf) return client.config.root_dir == conf.root_dir and client.name == conf.name end - config.name = config.name if not config.name and type(config.cmd) == 'table' then config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil end @@ -930,6 +932,29 @@ function lsp._set_defaults(client, bufnr) end end +--- @class lsp.ClientConfig +--- @field cmd (string[]|fun(dispatchers: table):table) +--- @field cmd_cwd string +--- @field cmd_env (table) +--- @field detached boolean +--- @field workspace_folders (table) +--- @field capabilities lsp.ClientCapabilities +--- @field handlers table +--- @field settings table +--- @field commands table +--- @field init_options table +--- @field name string +--- @field get_language_id fun(bufnr: integer, filetype: string): string +--- @field offset_encoding string +--- @field on_error fun(code: integer) +--- @field before_init function +--- @field on_init function +--- @field on_exit fun(code: integer, signal: integer, client_id: integer) +--- @field on_attach fun(client: lsp.Client, bufnr: integer) +--- @field trace 'off'|'messages'|'verbose'|nil +--- @field flags table +--- @field root_dir string + -- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are -- documented twice: Here, and on the methods themselves (e.g. -- `client.request()`). This is a workaround for the vimdoc generator script @@ -940,7 +965,7 @@ end --- --- Field `cmd` in {config} is required. --- ----@param config (table) Configuration for the server: +---@param config (lsp.ClientConfig) Configuration for the server: --- - cmd: (string[]|fun(dispatchers: table):table) command a list of --- strings treated like |jobstart()|. The command must launch the language server --- process. `cmd` can also be a function that creates an RPC client. @@ -970,7 +995,7 @@ end --- the LSP spec. --- --- - capabilities: Map overriding the default capabilities defined by ---- |vim.lsp.protocol.make_client_capabilities()|, passed to the language +--- \|vim.lsp.protocol.make_client_capabilities()|, passed to the language --- server on initialization. Hint: use make_client_capabilities() and modify --- its result. --- - Note: To send an empty dictionary use @@ -1051,9 +1076,7 @@ end --- fully initialized. Use `on_init` to do any actions once --- the client has been initialized. function lsp.start_client(config) - local cleaned_config = validate_client_config(config) - local cmd, cmd_args, offset_encoding = - cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding + local cmd, cmd_args, offset_encoding = validate_client_config(config) config.flags = config.flags or {} config.settings = config.settings or {} @@ -1090,7 +1113,9 @@ function lsp.start_client(config) ---@param method (string) LSP method name ---@param params (table) The parameters for that method. function dispatch.notification(method, params) - local _ = log.trace() and log.trace('notification', method, params) + if log.trace() then + log.trace('notification', method, params) + end local handler = resolve_handler(method) if handler then -- Method name is provided here for convenience. @@ -1104,13 +1129,19 @@ function lsp.start_client(config) ---@param method (string) LSP method name ---@param params (table) The parameters for that method function dispatch.server_request(method, params) - local _ = log.trace() and log.trace('server_request', method, params) + if log.trace() then + log.trace('server_request', method, params) + end local handler = resolve_handler(method) if handler then - local _ = log.trace() and log.trace('server_request: found handler for', method) + if log.trace() then + log.trace('server_request: found handler for', method) + end return handler(nil, params, { method = method, client_id = client_id }) end - local _ = log.warn() and log.warn('server_request: no handler found for', method) + if log.warn() then + log.warn('server_request: no handler found for', method) + end return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end @@ -1122,8 +1153,9 @@ function lsp.start_client(config) ---@see `vim.lsp.rpc.client_errors` for possible errors. Use ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. function dispatch.on_error(code, err) - local _ = log.error() - and log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err }) + if log.error() then + log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err }) + end err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err)) if config.on_error then local status, usererr = pcall(config.on_error, code, err) @@ -1232,11 +1264,13 @@ function lsp.start_client(config) handlers = handlers, commands = config.commands or {}, + --- @type table requests = {}, -- for $/progress report messages = { name = name, messages = {}, progress = {}, status = {} }, dynamic_capabilities = require('vim.lsp._dynamic').new(client_id), } + --- @type lsp.ClientCapabilities client.config.capabilities = config.capabilities or protocol.make_client_capabilities() -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. @@ -1251,9 +1285,9 @@ function lsp.start_client(config) } local version = vim.version() - local workspace_folders - local root_uri - local root_path + local workspace_folders --- @type table[]? + local root_uri --- @type string? + local root_path --- @type string? if config.workspace_folders or config.root_dir then if config.root_dir and not config.workspace_folders then workspace_folders = { @@ -1278,7 +1312,7 @@ function lsp.start_client(config) -- the process has not been started by another process. If the parent -- process is not alive then the server should exit (see exit notification) -- its process. - processId = uv.getpid(), + processId = uv.os_getpid(), -- Information about the client -- since 3.15.0 clientInfo = { @@ -1405,8 +1439,9 @@ function lsp.start_client(config) -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(client, bufnr) bufnr = resolve_bufnr(bufnr) - local _ = log.debug() - and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr) + if log.debug() then + log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr) + end local success, request_id = rpc.request(method, params, function(err, result) handler( err, @@ -1879,13 +1914,13 @@ end --- - id (number): Only return clients with the given id --- - bufnr (number): Only return clients attached to this buffer --- - name (string): Only return clients with the given name ----@returns (table) List of |vim.lsp.client| objects +---@return lsp.Client[]: List of |vim.lsp.client| objects function lsp.get_active_clients(filter) validate({ filter = { filter, 't', true } }) filter = filter or {} - local clients = {} + local clients = {} --- @type lsp.Client[] local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {}) or active_clients @@ -2143,20 +2178,20 @@ end --- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=1: list of matches (actually just calls |complete()|) function lsp.omnifunc(findstart, base) - local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base }) + if log.debug() then + log.debug('omnifunc.findstart', { findstart = findstart, base = base }) + end local bufnr = resolve_bufnr() local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {}) if not has_buffer_clients then - if findstart == 1 then - return -1 - else - return {} - end + return findstart == 1 and -1 or {} end -- Then, perform standard completion request - local _ = log.info() and log.info('base ', base) + if log.info() then + log.info('base ', base) + end local pos = api.nvim_win_get_cursor(0) local line = api.nvim_get_current_line() @@ -2270,8 +2305,8 @@ end ---@param flags string See |tag-function| --- ---@return table[] tags A list of matching tags -function lsp.tagfunc(...) - return require('vim.lsp.tagfunc')(...) +function lsp.tagfunc(pattern, flags) + return require('vim.lsp.tagfunc')(pattern, flags) end ---Checks whether a client is stopped. @@ -2359,10 +2394,13 @@ end --- are valid keys and make sense to include for this handler. --- --- Will error on invalid keys (i.e. keys that do not exist in the options) +--- @param name string +--- @param options table +--- @param user_config table function lsp._with_extend(name, options, user_config) user_config = user_config or {} - local resulting_config = {} + local resulting_config = {} --- @type table for k, v in pairs(user_config) do if options[k] == nil then error( diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 7558b5c6ba..172d43e483 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -632,6 +632,7 @@ export interface WorkspaceClientCapabilities { --- Gets a new ClientCapabilities object describing the LSP client --- capabilities. +--- @return lsp.ClientCapabilities function protocol.make_client_capabilities() return { general = { -- cgit From c0952e62fd0ee16a3275bb69e0de04c836b39015 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 7 Jun 2023 13:52:23 +0100 Subject: feat(lua): add `vim.system()` feat(lua): add vim.system() Problem: Handling system commands in Lua is tedious and error-prone: - vim.fn.jobstart() is vimscript and comes with all limitations attached to typval. - vim.loop.spawn is too low level Solution: Add vim.system(). Partly inspired by Python's subprocess module Does not expose any libuv objects. --- runtime/lua/vim/_editor.lua | 99 ++++++++++--- runtime/lua/vim/_system.lua | 342 ++++++++++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/rpc.lua | 124 ++++++---------- 3 files changed, 469 insertions(+), 96 deletions(-) create mode 100644 runtime/lua/vim/_system.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 7b946a55e4..d46b0fbf32 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -42,10 +42,6 @@ for k, v in pairs({ vim._submodules[k] = v end --- Remove at Nvim 1.0 ----@deprecated -vim.loop = vim.uv - -- There are things which have special rules in vim._init_packages -- for legacy reasons (uri) or for performance (_inspector). -- most new things should go into a submodule namespace ( vim.foobar.do_thing() ) @@ -69,13 +65,73 @@ vim.log = { }, } --- Internal-only until comments in #8107 are addressed. --- Returns: --- {errcode}, {output} -function vim._system(cmd) - local out = vim.fn.system(cmd) - local err = vim.v.shell_error - return err, out +-- TODO(lewis6991): document that the signature is system({cmd}, [{opts},] {on_exit}) +--- Run a system command +--- +--- Examples: +---
lua
+---
+---   local on_exit = function(obj)
+---     print(obj.code)
+---     print(obj.signal)
+---     print(obj.stdout)
+---     print(obj.stderr)
+---   end
+---
+---   -- Run asynchronously
+---   vim.system({'echo', 'hello'}, { text = true }, on_exit)
+---
+---   -- Run synchronously
+---   local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
+---   -- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
+---
+--- 
+--- +--- See |uv.spawn()| for more details. +--- +--- @param cmd (string[]) Command to execute +--- @param opts (SystemOpts|nil) Options: +--- - cwd: (string) Set the current working directory for the sub-process. +--- - env: table Set environment variables for the new process. Inherits the +--- current environment with `NVIM` set to |v:servername|. +--- - clear_env: (boolean) `env` defines the job environment exactly, instead of merging current +--- environment. +--- - stdin: (string|string[]|boolean) If `true`, then a pipe to stdin is opened and can be written +--- to via the `write()` method to SystemObj. If string or string[] then will be written to stdin +--- and closed. Defaults to `false`. +--- - stdout: (boolean|function) +--- Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`. +--- Defaults to `true` +--- - stderr: (boolean|function) +--- Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`. +--- Defaults to `true`. +--- - text: (boolean) Handle stdout and stderr as text. Replaces `\r\n` with `\n`. +--- - timeout: (integer) +--- - detach: (boolean) If true, spawn the child process in a detached state - this will make it +--- a process group leader, and will effectively enable the child to keep running after the +--- parent exits. Note that the child process will still keep the parent's event loop alive +--- unless the parent process calls |uv.unref()| on the child's process handle. +--- +--- @param on_exit (function|nil) Called when subprocess exits. When provided, the command runs +--- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait(). +--- +--- @return SystemObj Object with the fields: +--- - pid (integer) Process ID +--- - wait (fun(timeout: integer|nil): SystemCompleted) +--- - SystemCompleted is an object with the fields: +--- - code: (integer) +--- - signal: (integer) +--- - stdout: (string), nil if stdout argument is passed +--- - stderr: (string), nil if stderr argument is passed +--- - kill (fun(signal: integer)) +--- - write (fun(data: string|nil)) Requires `stdin=true`. Pass `nil` to close the stream. +--- - is_closing (fun(): boolean) +function vim.system(cmd, opts, on_exit) + if type(opts) == 'function' then + on_exit = opts + opts = nil + end + return require('vim._system').run(cmd, opts, on_exit) end -- Gets process info from the `ps` command. @@ -85,13 +141,14 @@ function vim._os_proc_info(pid) error('invalid pid') end local cmd = { 'ps', '-p', pid, '-o', 'comm=' } - local err, name = vim._system(cmd) - if 1 == err and vim.trim(name) == '' then + local r = vim.system(cmd):wait() + local name = assert(r.stdout) + if r.code == 1 and vim.trim(name) == '' then return {} -- Process not found. - elseif 0 ~= err then + elseif r.code ~= 0 then error('command failed: ' .. vim.fn.string(cmd)) end - local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=' }) + local ppid = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout) -- Remove trailing whitespace. name = vim.trim(name):gsub('^.*/', '') ppid = tonumber(ppid) or -1 @@ -109,14 +166,14 @@ function vim._os_proc_children(ppid) error('invalid ppid') end local cmd = { 'pgrep', '-P', ppid } - local err, rv = vim._system(cmd) - if 1 == err and vim.trim(rv) == '' then + local r = vim.system(cmd):wait() + if r.code == 1 and vim.trim(r.stdout) == '' then return {} -- Process not found. - elseif 0 ~= err then + elseif r.code ~= 0 then error('command failed: ' .. vim.fn.string(cmd)) end local children = {} - for s in rv:gmatch('%S+') do + for s in r.stdout:gmatch('%S+') do local i = tonumber(s) if i ~= nil then table.insert(children, i) @@ -1006,4 +1063,8 @@ end require('vim._meta') +-- Remove at Nvim 1.0 +---@deprecated +vim.loop = vim.uv + return vim diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua new file mode 100644 index 0000000000..eadf801a31 --- /dev/null +++ b/runtime/lua/vim/_system.lua @@ -0,0 +1,342 @@ +local uv = vim.uv + +--- @class SystemOpts +--- @field cmd string[] +--- @field stdin string|string[]|true +--- @field stdout fun(err:string, data: string)|false +--- @field stderr fun(err:string, data: string)|false +--- @field cwd? string +--- @field env? table +--- @field clear_env? boolean +--- @field text boolean? +--- @field timeout? integer Timeout in ms +--- @field detach? boolean + +--- @class SystemCompleted +--- @field code integer +--- @field signal integer +--- @field stdout? string +--- @field stderr? string + +--- @class SystemState +--- @field handle uv_process_t +--- @field timer uv_timer_t +--- @field pid integer +--- @field timeout? integer +--- @field done boolean +--- @field stdin uv_stream_t? +--- @field stdout uv_stream_t? +--- @field stderr uv_stream_t? +--- @field cmd string[] +--- @field result? SystemCompleted + +---@private +---@param state SystemState +local function close_handles(state) + for _, handle in pairs({ state.handle, state.stdin, state.stdout, state.stderr }) do + if not handle:is_closing() then + handle:close() + end + end +end + +--- @param cmd string[] +--- @return SystemCompleted +local function timeout_result(cmd) + local cmd_str = table.concat(cmd, ' ') + local err = string.format("Command timed out: '%s'", cmd_str) + return { code = 0, signal = 2, stdout = '', stderr = err } +end + +--- @class SystemObj +--- @field pid integer +--- @field private _state SystemState +--- @field wait fun(self: SystemObj, timeout?: integer): SystemCompleted +--- @field kill fun(self: SystemObj, signal: integer) +--- @field write fun(self: SystemObj, data?: string|string[]) +--- @field is_closing fun(self: SystemObj): boolean? +local SystemObj = {} + +--- @param state SystemState +--- @return SystemObj +local function new_systemobj(state) + return setmetatable({ + pid = state.pid, + _state = state, + }, { __index = SystemObj }) +end + +--- @param signal integer +function SystemObj:kill(signal) + local state = self._state + state.handle:kill(signal) + close_handles(state) +end + +local MAX_TIMEOUT = 2 ^ 31 + +--- @param timeout? integer +--- @return SystemCompleted +function SystemObj:wait(timeout) + local state = self._state + + vim.wait(timeout or state.timeout or MAX_TIMEOUT, function() + return state.done + end) + + if not state.done then + self:kill(6) -- 'sigint' + state.result = timeout_result(state.cmd) + end + + return state.result +end + +--- @param data string[]|string|nil +function SystemObj:write(data) + local stdin = self._state.stdin + + if not stdin then + error('stdin has not been opened on this object') + end + + if type(data) == 'table' then + for _, v in ipairs(data) do + stdin:write(v) + stdin:write('\n') + end + elseif type(data) == 'string' then + stdin:write(data) + elseif data == nil then + -- Shutdown the write side of the duplex stream and then close the pipe. + -- Note shutdown will wait for all the pending write requests to complete + -- TODO(lewis6991): apparently shutdown doesn't behave this way. + -- (https://github.com/neovim/neovim/pull/17620#discussion_r820775616) + stdin:write('', function() + stdin:shutdown(function() + if stdin then + stdin:close() + end + end) + end) + end +end + +--- @return boolean +function SystemObj:is_closing() + local handle = self._state.handle + return handle == nil or handle:is_closing() +end + +---@private +---@param output function|'false' +---@return uv_stream_t? +---@return function? Handler +local function setup_output(output) + if output == nil then + return assert(uv.new_pipe(false)), nil + end + + if type(output) == 'function' then + return assert(uv.new_pipe(false)), output + end + + assert(output == false) + return nil, nil +end + +---@private +---@param input string|string[]|true|nil +---@return uv_stream_t? +---@return string|string[]? +local function setup_input(input) + if not input then + return + end + + local towrite --- @type string|string[]? + if type(input) == 'string' or type(input) == 'table' then + towrite = input + end + + return assert(uv.new_pipe(false)), towrite +end + +--- @return table +local function base_env() + local env = vim.fn.environ() + env['NVIM'] = vim.v.servername + env['NVIM_LISTEN_ADDRESS'] = nil + return env +end + +--- uv.spawn will completely overwrite the environment +--- when we just want to modify the existing one, so +--- make sure to prepopulate it with the current env. +--- @param env? table +--- @param clear_env? boolean +--- @return string[]? +local function setup_env(env, clear_env) + if clear_env then + return env + end + + --- @type table + env = vim.tbl_extend('force', base_env(), env or {}) + + local renv = {} --- @type string[] + for k, v in pairs(env) do + renv[#renv + 1] = string.format('%s=%s', k, tostring(v)) + end + + return renv +end + +--- @param stream uv_stream_t +--- @param text? boolean +--- @param bucket string[] +--- @return fun(err: string?, data: string?) +local function default_handler(stream, text, bucket) + return function(err, data) + if err then + error(err) + end + if data ~= nil then + if text then + bucket[#bucket + 1] = data:gsub('\r\n', '\n') + else + bucket[#bucket + 1] = data + end + else + stream:read_stop() + stream:close() + end + end +end + +local M = {} + +--- @param cmd string +--- @param opts uv.aliases.spawn_options +--- @param on_exit fun(code: integer, signal: integer) +--- @param on_error fun() +--- @return uv_process_t, integer +local function spawn(cmd, opts, on_exit, on_error) + local handle, pid_or_err = uv.spawn(cmd, opts, on_exit) + if not handle then + on_error() + error(pid_or_err) + end + return handle, pid_or_err --[[@as integer]] +end + +--- Run a system command +--- +--- @param cmd string[] +--- @param opts? SystemOpts +--- @param on_exit? fun(out: SystemCompleted) +--- @return SystemObj +function M.run(cmd, opts, on_exit) + vim.validate({ + cmd = { cmd, 'table' }, + opts = { opts, 'table', true }, + on_exit = { on_exit, 'function', true }, + }) + + opts = opts or {} + + local stdout, stdout_handler = setup_output(opts.stdout) + local stderr, stderr_handler = setup_output(opts.stderr) + local stdin, towrite = setup_input(opts.stdin) + + --- @type SystemState + local state = { + done = false, + cmd = cmd, + timeout = opts.timeout, + stdin = stdin, + stdout = stdout, + stderr = stderr, + } + + -- Define data buckets as tables and concatenate the elements at the end as + -- one operation. + --- @type string[], string[] + local stdout_data, stderr_data + + state.handle, state.pid = spawn(cmd[1], { + args = vim.list_slice(cmd, 2), + stdio = { stdin, stdout, stderr }, + cwd = opts.cwd, + env = setup_env(opts.env, opts.clear_env), + detached = opts.detach, + hide = true, + }, function(code, signal) + close_handles(state) + if state.timer then + state.timer:stop() + state.timer:close() + end + + local check = assert(uv.new_check()) + + check:start(function() + for _, pipe in pairs({ state.stdin, state.stdout, state.stderr }) do + if not pipe:is_closing() then + return + end + end + check:stop() + + state.done = true + state.result = { + code = code, + signal = signal, + stdout = stdout_data and table.concat(stdout_data) or nil, + stderr = stderr_data and table.concat(stderr_data) or nil, + } + + if on_exit then + on_exit(state.result) + end + end) + end, function() + close_handles(state) + end) + + if stdout then + stdout_data = {} + stdout:read_start(stdout_handler or default_handler(stdout, opts.text, stdout_data)) + end + + if stderr then + stderr_data = {} + stderr:read_start(stderr_handler or default_handler(stderr, opts.text, stderr_data)) + end + + local obj = new_systemobj(state) + + if towrite then + obj:write(towrite) + obj:write(nil) -- close the stream + end + + if opts.timeout then + state.timer = assert(uv.new_timer()) + state.timer:start(opts.timeout, 0, function() + state.timer:stop() + state.timer:close() + if state.handle and state.handle:is_active() then + obj:kill(6) --- 'sigint' + state.result = timeout_result(state.cmd) + if on_exit then + on_exit(state.result) + end + end + end) + end + + return obj +end + +return M diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 5f48effebf..64bc732bdf 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -14,32 +14,6 @@ local function is_dir(filename) return stat and stat.type == 'directory' or false end ----@private ---- Merges current process env with the given env and returns the result as ---- a list of "k=v" strings. ---- ----
---- Example:
----
----  in:    { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
----  out:   { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
---- 
----@param env (table) table of environment variable assignments ----@returns (table) list of `"k=v"` strings -local function env_merge(env) - if env == nil then - return env - end - -- Merge. - env = vim.tbl_extend('force', vim.fn.environ(), env) - local final_env = {} - for k, v in pairs(env) do - assert(type(k) == 'string', 'env must be a dict') - table.insert(final_env, k .. '=' .. tostring(v)) - end - return final_env -end - ---@private --- Embeds the given string into a table and correctly computes `Content-Length`. --- @@ -658,89 +632,85 @@ end --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. local function start(cmd, cmd_args, dispatchers, extra_spawn_params) - local _ = log.info() - and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) + if log.info() then + log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) + end + validate({ cmd = { cmd, 's' }, cmd_args = { cmd_args, 't' }, dispatchers = { dispatchers, 't', true }, }) - if extra_spawn_params and extra_spawn_params.cwd then + extra_spawn_params = extra_spawn_params or {} + + if extra_spawn_params.cwd then assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory') end dispatchers = merge_dispatchers(dispatchers) - local stdin = uv.new_pipe(false) - local stdout = uv.new_pipe(false) - local stderr = uv.new_pipe(false) - local handle, pid + + local sysobj ---@type SystemObj local client = new_client(dispatchers, { write = function(msg) - stdin:write(msg) + sysobj:write(msg) end, is_closing = function() - return handle == nil or handle:is_closing() + return sysobj == nil or sysobj:is_closing() end, terminate = function() - if handle then - handle:kill(15) - end + sysobj:kill(15) end, }) - ---@private - --- Callback for |vim.uv.spawn()| Closes all streams and runs the `on_exit` dispatcher. - ---@param code (integer) Exit code - ---@param signal (integer) Signal that was used to terminate (if any) - local function onexit(code, signal) - stdin:close() - stdout:close() - stderr:close() - handle:close() - dispatchers.on_exit(code, signal) + local handle_body = function(body) + client:handle_body(body) end - local spawn_params = { - args = cmd_args, - stdio = { stdin, stdout, stderr }, - detached = not is_win, - } - if extra_spawn_params then - spawn_params.cwd = extra_spawn_params.cwd - spawn_params.env = env_merge(extra_spawn_params.env) - if extra_spawn_params.detached ~= nil then - spawn_params.detached = extra_spawn_params.detached + + local stdout_handler = create_read_loop(handle_body, nil, function(err) + client:on_error(client_errors.READ_ERROR, err) + end) + + local stderr_handler = function(_, chunk) + if chunk and log.error() then + log.error('rpc', cmd, 'stderr', chunk) end end - handle, pid = uv.spawn(cmd, spawn_params, onexit) - if handle == nil then - stdin:close() - stdout:close() - stderr:close() + + local detached = not is_win + if extra_spawn_params.detached ~= nil then + detached = extra_spawn_params.detached + end + + local cmd1 = { cmd } + vim.list_extend(cmd1, cmd_args) + + local ok, sysobj_or_err = pcall(vim.system, cmd1, { + stdin = true, + stdout = stdout_handler, + stderr = stderr_handler, + cwd = extra_spawn_params.cwd, + env = extra_spawn_params.env, + detach = detached, + }, function(obj) + dispatchers.on_exit(obj.code, obj.signal) + end) + + if not ok then + local err = sysobj_or_err --[[@as string]] local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) - if string.match(pid, 'ENOENT') then + if string.match(err, 'ENOENT') then msg = msg .. '. The language server is either not installed, missing from PATH, or not executable.' else - msg = msg .. string.format(' with error message: %s', pid) + msg = msg .. string.format(' with error message: %s', err) end vim.notify(msg, vim.log.levels.WARN) return end - stderr:read_start(function(_, chunk) - if chunk then - local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk) - end - end) - - local handle_body = function(body) - client:handle_body(body) - end - stdout:read_start(create_read_loop(handle_body, nil, function(err) - client:on_error(client_errors.READ_ERROR, err) - end)) + sysobj = sysobj_or_err --[[@as SystemObj]] return public_client(client) end -- cgit From 0329f5c2f4f300b9ffe66b0b4b2db1d51c6293d1 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 7 Jun 2023 20:53:02 +0200 Subject: vim-patch:9.0.1615: URL shortcut files are not recognized (#23950) Problem: URL shortcut files are not recognized. Solution: Add a pattern for URL shortcut files. (closes vim/vim#12474) https://github.com/vim/vim/commit/cdb7b4c50826df254d2e5ba8abd211e49b7a9784 Co-authored-by: ObserverOfTime --- 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 6990de3391..13d3d3e53a 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1082,6 +1082,7 @@ local extension = { uit = 'uil', uil = 'uil', ungram = 'ungrammar', + url = 'urlshortcut', usd = 'usd', usda = 'usd', sba = 'vb', -- cgit From 7c661207cc4357553ed2b057b6c8f28400024361 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 8 Jun 2023 12:11:24 +0200 Subject: feat(lua): add ringbuffer (#22894) https://en.wikipedia.org/wiki/Circular_buffer --- runtime/lua/vim/shared.lua | 94 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 4f230c4412..f899157ab7 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -881,4 +881,98 @@ function vim.defaulttable(create) }) end +do + ---@class vim.Ringbuf + ---@field private _items table[] + ---@field private _idx_read integer + ---@field private _idx_write integer + ---@field private _size integer + local Ringbuf = {} + + --- Clear all items + function Ringbuf.clear(self) + self._items = {} + self._idx_read = 0 + self._idx_write = 0 + end + + --- Adds an item, overriding the oldest item if the buffer is full. + ---@generic T + ---@param item T + function Ringbuf.push(self, item) + self._items[self._idx_write] = item + self._idx_write = (self._idx_write + 1) % self._size + if self._idx_write == self._idx_read then + self._idx_read = (self._idx_read + 1) % self._size + end + end + + --- Removes and returns the first unread item + ---@generic T + ---@return T? + function Ringbuf.pop(self) + local idx_read = self._idx_read + if idx_read == self._idx_write then + return nil + end + local item = self._items[idx_read] + self._items[idx_read] = nil + self._idx_read = (idx_read + 1) % self._size + return item + end + + --- Returns the first unread item without removing it + ---@generic T + ---@return T? + function Ringbuf.peek(self) + if self._idx_read == self._idx_write then + return nil + end + return self._items[self._idx_read] + end + + --- Create a ring buffer limited to a maximal number of items. + --- Once the buffer is full, adding a new entry overrides the oldest entry. + --- + ---
+  ---   local ringbuf = vim.ringbuf(4)
+  ---   ringbuf:push("a")
+  ---   ringbuf:push("b")
+  ---   ringbuf:push("c")
+  ---   ringbuf:push("d")
+  ---   ringbuf:push("e")    -- overrides "a"
+  ---   print(ringbuf:pop()) -- returns "b"
+  ---   print(ringbuf:pop()) -- returns "c"
+  ---
+  ---   -- Can be used as iterator. Pops remaining items:
+  ---   for val in ringbuf do
+  ---     print(val)
+  ---   end
+  --- 
+ --- + --- Returns a Ringbuf instance with the following methods: + --- + --- - |Ringbuf:push()| + --- - |Ringbuf:pop()| + --- - |Ringbuf:peek()| + --- - |Ringbuf:clear()| + --- + ---@param size integer + ---@return vim.Ringbuf ringbuf (table) + function vim.ringbuf(size) + local ringbuf = { + _items = {}, + _size = size + 1, + _idx_read = 0, + _idx_write = 0, + } + return setmetatable(ringbuf, { + __index = Ringbuf, + __call = function(self) + return self:pop() + end, + }) + end +end + return vim -- cgit From be539f5fa8e2d7ad73f7f69941bd6ff13354e89c Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 8 Jun 2023 21:53:16 +0200 Subject: vim-patch:9.0.1618: Trace32 files are not recognized (#23960) Problem: Trace32 files are not recognized. Solution: Add patterns for the t32 filetype. (Christoph Sax, closes vim/vim#12505) https://github.com/vim/vim/commit/7fbcee6f928356f1c205a4dfa67c1d542f743b92 Co-authored-by: Christoph Sax --- 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 13d3d3e53a..18c47ea2f4 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1033,6 +1033,8 @@ local extension = { swift = 'swift', svh = 'systemverilog', sv = 'systemverilog', + cmm = 't32', + t32 = 't32', td = 'tablegen', tak = 'tak', tal = 'tal', -- cgit From f31dba93f921891159eb707b185517648df00d6b Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 9 Jun 2023 08:59:28 +0200 Subject: vim-patch:9.0.1620: Nix files are not recognized from the hashbang line (#23961) Problem: Nix files are not recognized from the hashbang line. Solution: Add a hashbang check. (issue vim/vim#12507) https://github.com/vim/vim/commit/19548c6a742d954ecd0b50b0680c37cc6ced7473 Co-authored-by: Bram Moolenaar --- runtime/lua/vim/filetype/detect.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index b347215895..4e18ed190a 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1459,6 +1459,7 @@ local patterns_hashbang = { ['gforth\\>'] = { 'forth', { vim_regex = true } }, ['icon\\>'] = { 'icon', { vim_regex = true } }, guile = 'scheme', + ['nix%-shell'] = 'nix', } ---@private @@ -1491,7 +1492,7 @@ local function match_from_hashbang(contents, path, dispatch_extension) elseif matchregex(first_line, [[^#!\s*[^/\\ ]*\>\([^/\\]\|$\)]]) then name = vim.fn.substitute(first_line, [[^#!\s*\([^/\\ ]*\>\).*]], '\\1', '') else - name = vim.fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\i\+\).*]], '\\1', '') + name = vim.fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\f\+\).*]], '\\1', '') end -- tcl scripts may have #!/bin/sh in the first line and "exec wish" in the -- cgit From e5e0bda41b640d324350c5147b956e37e9f8b32c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 9 Jun 2023 11:32:43 +0200 Subject: feat(lsp)!: add vim.lsp.status, client.progress and promote LspProgressUpdate (#23958) `client.messages` could grow unbounded because the default handler only added new messages, never removing them. A user either had to consume the messages by calling `vim.lsp.util.get_progress_messages` or by manually removing them from `client.messages.progress`. If they didn't do that, using LSP effectively leaked memory. To fix this, this deprecates the `messages` property and instead adds a `progress` ring buffer that only keeps at most 50 messages. In addition it deprecates `vim.lsp.util.get_progress_messages` in favour of a new `vim.lsp.status()` and also promotes the `LspProgressUpdate` user autocmd to a regular autocmd to allow users to pattern match on the progress kind. Also closes https://github.com/neovim/neovim/pull/20327 --- runtime/lua/vim/lsp.lua | 62 ++++++++++++++++++++++++++++++++- runtime/lua/vim/lsp/handlers.lua | 75 +++++++++++++++++----------------------- runtime/lua/vim/lsp/types.lua | 8 ++++- runtime/lua/vim/lsp/util.lua | 29 ++++++++++++++++ 4 files changed, 129 insertions(+), 45 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 532504a7db..6ddbfc6df7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -807,6 +807,9 @@ end --- --- - {server_capabilities} (table): Response from the server sent on --- `initialize` describing the server's capabilities. +--- +--- - {progress} A ring buffer (|vim.ringbuf()|) containing progress messages +--- sent by the server. function lsp.client() error() end @@ -891,6 +894,50 @@ function lsp.start(config, opts) return client_id end +--- Consumes the latest progress messages from all clients and formats them as a string. +--- Empty if there are no clients or if no new messages +--- +---@return string +function lsp.status() + local percentage = nil + local groups = {} + for _, client in ipairs(vim.lsp.get_active_clients()) do + 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 = {} + groups[progress.token] = group + end + group.title = value.title or group.title + group.message = value.message or group.message + if value.percentage then + percentage = math.max(percentage or 0, value.percentage) + end + end + -- else: Doesn't look like work done progress and can be in any format + -- Just ignore it as there is no sensible way to display it + end + end + local messages = {} + for _, group in pairs(groups) do + if group.title then + table.insert( + messages, + group.message and (group.title .. ': ' .. group.message) or group.title + ) + elseif group.message then + table.insert(messages, group.message) + end + end + local message = table.concat(messages, ', ') + if percentage then + return string.format('%03d: %s', percentage, message) + end + return message +end + ---@private -- Determines whether the given option can be set by `set_defaults`. local function is_empty_or_default(bufnr, option) @@ -1266,10 +1313,23 @@ function lsp.start_client(config) --- @type table requests = {}, - -- for $/progress report + + --- Contains $/progress report messages. + --- They have the format {token: integer|string, value: any} + --- For "work done progress", value will be one of: + --- - lsp.WorkDoneProgressBegin, + --- - lsp.WorkDoneProgressReport (extended with title from Begin) + --- - lsp.WorkDoneProgressEnd (extended with title from Begin) + progress = vim.ringbuf(50), + + ---@deprecated use client.progress instead messages = { name = name, messages = {}, progress = {}, status = {} }, dynamic_capabilities = require('vim.lsp._dynamic').new(client_id), } + + ---@type table title of unfinished progress sequences by token + client.progress.pending = {} + --- @type lsp.ClientCapabilities client.config.capabilities = config.capabilities or protocol.make_client_capabilities() diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 5346160871..19338ae8f0 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -9,7 +9,7 @@ local M = {} ---@private --- Writes to error buffer. ----@param ... (table of strings) Will be concatenated before being written +---@param ... string Will be concatenated before being written local function err_message(...) vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR) api.nvim_command('redraw') @@ -20,63 +20,52 @@ M['workspace/executeCommand'] = function(_, _, _, _) -- Error handling is done implicitly by wrapping all handlers; see end of this file end ----@private -local function progress_handler(_, result, ctx, _) - local client_id = ctx.client_id - local client = vim.lsp.get_client_by_id(client_id) - local client_name = client and client.name or string.format('id=%d', client_id) +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress +---@param result lsp.ProgressParams +---@param ctx lsp.HandlerContext +M['$/progress'] = function(_, result, ctx) + local client = vim.lsp.get_client_by_id(ctx.client_id) if not client then - err_message('LSP[', client_name, '] client has shut down during progress update') + err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update') return vim.NIL end - local val = result.value -- unspecified yet - local token = result.token -- string or number + local kind = nil + local value = result.value - if type(val) ~= 'table' then - val = { content = val } - end - if val.kind then - if val.kind == 'begin' then - client.messages.progress[token] = { - title = val.title, - cancellable = val.cancellable, - message = val.message, - percentage = val.percentage, - } - elseif val.kind == 'report' then - client.messages.progress[token].cancellable = val.cancellable - client.messages.progress[token].message = val.message - client.messages.progress[token].percentage = val.percentage - elseif val.kind == 'end' then - if client.messages.progress[token] == nil then - err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`') - else - client.messages.progress[token].message = val.message - client.messages.progress[token].done = true + if type(value) == 'table' then + kind = value.kind + -- Carry over title of `begin` messages to `report` and `end` messages + -- 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 + else + value.title = client.progress.pending[result.token] + if kind == 'end' then + client.progress.pending[result.token] = nil end end - else - client.messages.progress[token] = val - client.messages.progress[token].done = true end - api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false }) -end + client.progress:push(result) ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress -M['$/progress'] = progress_handler + api.nvim_exec_autocmds('LspProgress', { + pattern = kind, + modeline = false, + data = { client_id = ctx.client_id, result = result }, + }) +end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create +---@param result lsp.WorkDoneProgressCreateParams +---@param ctx lsp.HandlerContext M['window/workDoneProgress/create'] = function(_, result, ctx) - local client_id = ctx.client_id - local client = vim.lsp.get_client_by_id(client_id) - local token = result.token -- string or number - local client_name = client and client.name or string.format('id=%d', client_id) + local client = vim.lsp.get_client_by_id(ctx.client_id) if not client then - err_message('LSP[', client_name, '] client has shut down while creating progress report') + err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update') return vim.NIL end - client.messages.progress[token] = {} + client.progress:push(result) return vim.NIL end diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua index e77e1fb63a..ef85a0d10f 100644 --- a/runtime/lua/vim/lsp/types.lua +++ b/runtime/lua/vim/lsp/types.lua @@ -1,6 +1,12 @@ ---@meta ----@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: table, config: table|nil) +---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil) + +---@class lsp.HandlerContext +---@field method string +---@field client_id integer +---@field bufnr integer +---@field params any ---@class lsp.ResponseError ---@field code integer diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index e36014d07d..538e48c805 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -353,11 +353,40 @@ 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.0') local new_messages = {} local progress_remove = {} for _, client in ipairs(vim.lsp.get_active_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 -- cgit From 5959b3c9222629c0e6020672ad5b13d0ed707c99 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 9 Jun 2023 23:48:31 +0200 Subject: vim-patch:9.0.1622: filetype name t32 is a bit obscure (#23967) Problem: Filetype name t32 is a bit obscure. Solution: Rename t32 to trace32. (Christoph Sax, closes vim/vim#12512) https://github.com/vim/vim/commit/740df76c90ee2c421ab7852b7ff2835aa0af782a Co-authored-by: Christoph Sax --- runtime/lua/vim/filetype.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 18c47ea2f4..fc8871f593 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1033,8 +1033,8 @@ local extension = { swift = 'swift', svh = 'systemverilog', sv = 'systemverilog', - cmm = 't32', - t32 = 't32', + cmm = 'trace32', + t32 = 'trace32', td = 'tablegen', tak = 'tak', tal = 'tal', -- cgit From 7154f0c98619d0789618f370a6c7f81ee1775469 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Sat, 10 Jun 2023 03:37:05 +0200 Subject: docs: fix typos (#23917) --- runtime/lua/vim/fs.lua | 2 +- runtime/lua/vim/keymap.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 5e63fb6237..c89147dbd2 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -72,7 +72,7 @@ function M.basename(file) return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/')) end ---- Concatenate directories and/or file into a single path with normalization +--- Concatenate directories and/or file paths into a single path with normalization --- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`) --- ---@param ... string diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 911b584c14..0d7fc3b1ec 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -6,7 +6,7 @@ local keymap = {} --- -- Map to a Lua function: --- vim.keymap.set('n', 'lhs', function() print("real lua function") end) --- -- Map to multiple modes: ---- vim.keymap.set({'n', 'v'}, 'lr', vim.lsp.buf.references, { buffer=true }) +--- vim.keymap.set({'n', 'v'}, 'lr', vim.lsp.buf.references, { buffer = true }) --- -- Buffer-local mapping: --- vim.keymap.set('n', 'w', "w", { silent = true, buffer = 5 }) --- -- Expr mapping: -- cgit From b302da9ad220a7699d4b0ebf642529d142a0b9cf Mon Sep 17 00:00:00 2001 From: Raphael Date: Sun, 11 Jun 2023 02:32:41 +0800 Subject: fix(lsp): use percentage format on lsp.status (#23971) --- 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 6ddbfc6df7..1d9a91801a 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -933,7 +933,7 @@ function lsp.status() end local message = table.concat(messages, ', ') if percentage then - return string.format('%03d: %s', percentage, message) + return string.format('%3d%%: %s', percentage, message) end return message end -- cgit From 302d3cfb96d7f0c856262e1a4252d058e3300c8b Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 10 Jun 2023 20:33:23 +0200 Subject: feat(lua): use callable table as iterator in vim.iter (#23957) A table passed to `vim.iter` can be a class instance with a `__call` implementation for the iterator protocol. --- runtime/lua/vim/iter.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 0e98d0437e..204d22b9be 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -14,6 +14,8 @@ --- - Non-list tables pass both the key and value of each element --- - Function iterators pass all of the values returned by their respective --- function +--- - Tables with a metatable implementing __call are treated as function +--- iterators --- --- Examples: ---
lua
@@ -46,6 +48,12 @@
 ---     return k == 'z'
 ---   end)
 ---   -- true
+---
+---   local rb = vim.ringbuf(3)
+---   rb:push("a")
+---   rb:push("b")
+---   vim.iter(rb):totable()
+---   -- { "a", "b" }
 --- 
--- --- In addition to the |vim.iter()| function, the |vim.iter| module provides @@ -889,6 +897,17 @@ end function Iter.new(src, ...) local it = {} if type(src) == 'table' then + local mt = getmetatable(src) + if mt and type(mt.__call) == 'function' then + ---@private + function it.next() + return src() + end + + setmetatable(it, Iter) + return it + end + local t = {} -- Check if source table can be treated like a list (indices are consecutive integers -- cgit From cce9460524aa17bcd4daa095f4706220b81f8845 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 11 Jun 2023 15:29:51 +0800 Subject: fix(remote): make --remote-expr print to stdout (#23980) --- runtime/lua/vim/_editor.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index d46b0fbf32..dc396b8f65 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -932,7 +932,7 @@ function vim._cs_remote(rcid, server_addr, connect_error, args) or subcmd == 'tab-wait' or subcmd == 'tab-wait-silent' then - return { errmsg = 'E5600: Wait commands not yet implemented in nvim' } + return { errmsg = 'E5600: Wait commands not yet implemented in Nvim' } elseif subcmd == 'tab-silent' then f_tab = true f_silent = true @@ -946,8 +946,9 @@ function vim._cs_remote(rcid, server_addr, connect_error, args) if rcid == 0 then return { errmsg = connection_failure_errmsg('Send expression failed.') } end - print(vim.fn.rpcrequest(rcid, 'nvim_eval', args[2])) - return { should_exit = true, tabbed = false } + local expr = 'string(' .. args[2] .. ')' + local res = vim.fn.rpcrequest(rcid, 'nvim_eval', expr) + return { result = res, should_exit = true, tabbed = false } elseif subcmd ~= '' then return { errmsg = 'Unknown option argument: ' .. args[1] } end -- cgit From 643546b82b4bc0c29ca869f81af868a019723d83 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Sun, 11 Jun 2023 15:23:37 +0530 Subject: feat(lsp): add handlers for inlay hints (#23736) initial support; public API left for a follow-up PR --- runtime/lua/vim/lsp.lua | 17 ++- runtime/lua/vim/lsp/_inlay_hint.lua | 217 ++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/handlers.lua | 26 +++++ runtime/lua/vim/lsp/protocol.lua | 9 ++ runtime/lua/vim/lsp/types.lua | 25 +++++ 5 files changed, 289 insertions(+), 5 deletions(-) create mode 100644 runtime/lua/vim/lsp/_inlay_hint.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1d9a91801a..6f9a6c460b 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -17,6 +17,7 @@ local if_nil = vim.F.if_nil local lsp = { protocol = protocol, + _inlay_hint = require('vim.lsp._inlay_hint'), handlers = default_handlers, @@ -60,6 +61,8 @@ lsp._request_name_to_capability = { ['textDocument/documentHighlight'] = { 'documentHighlightProvider' }, ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' }, ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' }, + ['textDocument/inlayHint'] = { 'inlayHintProvider' }, + ['inlayHint/resolve'] = { 'inlayHintProvider', 'resolveProvider' }, } -- TODO improve handling of scratch buffers with LSP attached. @@ -1498,16 +1501,20 @@ function lsp.start_client(config) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(client, bufnr) + local version = util.buf_versions[bufnr] bufnr = resolve_bufnr(bufnr) if log.debug() then log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr) end local success, request_id = rpc.request(method, params, function(err, result) - handler( - err, - result, - { method = method, client_id = client_id, bufnr = bufnr, params = params } - ) + local context = { + method = method, + client_id = client_id, + bufnr = bufnr, + params = params, + version = version, + } + handler(err, result, context) end, function(request_id) local request = client.requests[request_id] request.type = 'complete' diff --git a/runtime/lua/vim/lsp/_inlay_hint.lua b/runtime/lua/vim/lsp/_inlay_hint.lua new file mode 100644 index 0000000000..aa6ec9aca8 --- /dev/null +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -0,0 +1,217 @@ +local util = require('vim.lsp.util') +local log = require('vim.lsp.log') +local api = vim.api +local M = {} + +---@class lsp._inlay_hint.bufstate +---@field version integer +---@field client_hint table> client_id -> (lnum -> hints) + +---@type table +local hint_cache_by_buf = setmetatable({}, { + __index = function(t, b) + local key = b > 0 and b or api.nvim_get_current_buf() + return rawget(t, key) + end, +}) + +local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') + +M.__explicit_buffers = {} + +--- |lsp-handler| for the method `textDocument/inlayHint` +--- Store hints for a specific buffer and client +--- Resolves unresolved hints +---@private +function M.on_inlayhint(err, result, ctx, _) + if err then + local _ = log.error() and log.error('inlayhint', err) + return + end + local bufnr = ctx.bufnr + if util.buf_versions[bufnr] ~= ctx.version then + return + end + local client_id = ctx.client_id + if not result then + return + end + local bufstate = hint_cache_by_buf[bufnr] + if not bufstate then + bufstate = { + client_hint = vim.defaulttable(), + version = ctx.version, + } + hint_cache_by_buf[bufnr] = bufstate + api.nvim_buf_attach(bufnr, false, { + on_detach = function(_, b) + api.nvim_buf_clear_namespace(b, namespace, 0, -1) + hint_cache_by_buf[b] = nil + end, + on_reload = function(_, b) + api.nvim_buf_clear_namespace(b, namespace, 0, -1) + hint_cache_by_buf[b] = nil + end, + }) + end + local hints_by_client = bufstate.client_hint + local client = vim.lsp.get_client_by_id(client_id) + + local new_hints_by_lnum = vim.defaulttable() + local num_unprocessed = #result + if num_unprocessed == 0 then + hints_by_client[client_id] = {} + bufstate.version = ctx.version + api.nvim__buf_redraw_range(bufnr, 0, -1) + return + end + + local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + ---@private + local function pos_to_byte(position) + local col = position.character + if col > 0 then + local line = lines[position.line + 1] or '' + local ok, convert_result + ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding) + if ok then + return convert_result + end + return math.min(#line, col) + end + return col + end + + 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) + end + + hints_by_client[client_id] = new_hints_by_lnum + bufstate.version = ctx.version + api.nvim__buf_redraw_range(bufnr, 0, -1) +end + +---@private +local function resolve_bufnr(bufnr) + return bufnr == 0 and api.nvim_get_current_buf() or bufnr +end + +--- Refresh inlay hints for a buffer +--- +--- It is recommended to trigger this using an autocmd or via keymap. +---@param opts (nil|table) Optional arguments +--- - bufnr (integer, default: 0): Buffer whose hints to refresh +--- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer +--- +--- Example: +---
vim
+---   autocmd BufEnter,InsertLeave,BufWritePost  lua vim.lsp._inlay_hint.refresh()
+--- 
+--- +---@private +function M.refresh(opts) + opts = opts or {} + local bufnr = opts.bufnr or 0 + local only_visible = opts.only_visible or false + bufnr = resolve_bufnr(bufnr) + M.__explicit_buffers[bufnr] = true + local buffer_windows = {} + for _, winid in ipairs(api.nvim_list_wins()) do + if api.nvim_win_get_buf(winid) == bufnr then + table.insert(buffer_windows, winid) + end + end + for _, window in ipairs(buffer_windows) do + local first = vim.fn.line('w0', window) + local last = vim.fn.line('w$', window) + local params = { + textDocument = util.make_text_document_params(bufnr), + range = { + start = { line = first - 1, character = 0 }, + ['end'] = { line = last, character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) + end + if not only_visible then + local params = { + textDocument = util.make_text_document_params(bufnr), + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) + end +end + +--- Clear inlay hints +--- +---@param client_id integer|nil filter by client_id. All clients if nil +---@param bufnr integer|nil filter by buffer. All buffers if nil +---@private +function M.clear(client_id, bufnr) + local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(hint_cache_by_buf) + for _, iter_bufnr in ipairs(buffers) do + M.__explicit_buffers[iter_bufnr] = false + local bufstate = hint_cache_by_buf[iter_bufnr] + local client_lens = (bufstate or {}).client_hint or {} + local client_ids = client_id and { client_id } or vim.tbl_keys(client_lens) + for _, iter_client_id in ipairs(client_ids) do + if bufstate then + bufstate.client_hint[iter_client_id] = {} + end + end + api.nvim_buf_clear_namespace(iter_bufnr, namespace, 0, -1) + end + vim.cmd('redraw!') +end + +api.nvim_set_decoration_provider(namespace, { + on_win = function(_, _, bufnr, topline, botline) + local bufstate = hint_cache_by_buf[bufnr] + if not bufstate then + return + end + + if bufstate.version ~= util.buf_versions[bufnr] then + return + end + local hints_by_client = bufstate.client_hint + api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) + + for lnum = topline, botline do + 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 + local text = '' + if type(hint.label) == 'string' then + text = hint.label + else + for _, part in ipairs(hint.label) do + text = text .. part.value + end + end + if hint.paddingLeft then + text = ' ' .. text + end + if hint.paddingRight then + text = text .. ' ' + end + api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { + virt_text_pos = 'inline', + ephemeral = false, + virt_text = { + { text, 'LspInlayHint' }, + }, + hl_mode = 'combine', + }) + end + end + end + end, +}) + +return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 19338ae8f0..44a9a58aca 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -219,6 +219,10 @@ M['textDocument/codeLens'] = function(...) return require('vim.lsp.codelens').on_codelens(...) end +M['textDocument/inlayHint'] = function(...) + return require('vim.lsp._inlay_hint').on_inlayhint(...) +end + --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references M['textDocument/references'] = function(_, result, ctx, config) if not result or vim.tbl_isempty(result) then @@ -612,6 +616,28 @@ M['window/showDocument'] = function(_, result, ctx, _) return { success = success or false } end +---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh +M['workspace/inlayHint/refresh'] = function(err, _, ctx) + local inlay_hint = require('vim.lsp._inlay_hint') + if not inlay_hint.__explicit_buffers[ctx.bufnr] then + return vim.NIL + end + if err then + return vim.NIL + end + + 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 + inlay_hint.refresh({ bufnr = bufnr }) + break + end + end + end + + return vim.NIL +end + -- Add boilerplate error validation and logging for all of these. for k, fn in pairs(M) do M[k] = function(err, result, ctx, config) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 172d43e483..b3a7903420 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -641,6 +641,12 @@ function protocol.make_client_capabilities() }, }, textDocument = { + inlayHint = { + dynamicRegistration = false, + resolveSupport = { + properties = {}, + }, + }, semanticTokens = { dynamicRegistration = false, tokenTypes = { @@ -853,6 +859,9 @@ function protocol.make_client_capabilities() dynamicRegistration = true, relativePatternSupport = true, }, + inlayHint = { + refreshSupport = true, + }, }, experimental = nil, window = { diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua index ef85a0d10f..108aeeb922 100644 --- a/runtime/lua/vim/lsp/types.lua +++ b/runtime/lua/vim/lsp/types.lua @@ -69,3 +69,28 @@ --- @field method string --- @alias lsp.UnregistrationParams {unregisterations: lsp.Unregistration[]} + +---@class lsp.Location +---@field uri string +---@field range lsp.Range + +---@class lsp.MarkupContent +---@field kind string +---@field value string + +---@class lsp.InlayHintLabelPart +---@field value string +---@field tooltip? string | lsp.MarkupContent +---@field location? lsp.Location + +---@class lsp.TextEdit +---@field range lsp.Range +---@field newText string + +---@class lsp.InlayHint +---@field position lsp.Position +---@field label string | lsp.InlayHintLabelPart[] +---@field kind? integer +---@field textEdits? lsp.TextEdit[] +---@field paddingLeft? boolean +---@field paddingRight? boolean -- cgit From bde59e81473f29944ef80ff98f6b2c88010b2df6 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Sun, 11 Jun 2023 22:12:32 +0800 Subject: fix(remote): restore previous --remote-expr output formatting (#23988) - Use tostring() as that's what print() uses internally. - Do not append trailing new line. --- runtime/lua/vim/_editor.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index dc396b8f65..1c7eb30b03 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -940,14 +940,13 @@ function vim._cs_remote(rcid, server_addr, connect_error, args) if rcid == 0 then return { errmsg = connection_failure_errmsg('Send failed.') } end - vim.fn.rpcrequest(rcid, 'nvim_input', args[2]) + vim.rpcrequest(rcid, 'nvim_input', args[2]) return { should_exit = true, tabbed = false } elseif subcmd == 'expr' then if rcid == 0 then return { errmsg = connection_failure_errmsg('Send expression failed.') } end - local expr = 'string(' .. args[2] .. ')' - local res = vim.fn.rpcrequest(rcid, 'nvim_eval', expr) + local res = tostring(vim.rpcrequest(rcid, 'nvim_eval', args[2])) return { result = res, should_exit = true, tabbed = false } elseif subcmd ~= '' then return { errmsg = 'Unknown option argument: ' .. args[1] } -- cgit From ceb37ff1d83c6dc341dc43d3dc8c81f657bc9484 Mon Sep 17 00:00:00 2001 From: Stephan Seitz Date: Sun, 11 Jun 2023 23:46:22 +0200 Subject: refactor(treesitter): use npcall in _query_linter.lua #23985 --- runtime/lua/vim/treesitter/_query_linter.lua | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index ecdee5fc95..3dd0177a81 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -53,10 +53,7 @@ end local function guess_query_lang(buf) local filename = api.nvim_buf_get_name(buf) if filename ~= '' then - local ok, query_lang = pcall(vim.fn.fnamemodify, filename, ':p:h:t') - if ok then - return query_lang - end + return vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') end end @@ -270,11 +267,8 @@ function M.lint(buf, opts) for i = 1, math.max(1, #opts.langs) do local lang = opts.langs[i] - --- @type boolean, (table|nil) - local ok, parser_info = pcall(vim.treesitter.language.inspect, lang) - if not ok then - parser_info = nil - end + --- @type (table|nil) + local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang) local parser = vim.treesitter.get_parser(buf) parser:parse() -- cgit From ecdb6465e272119f80f3b68a6695e0f74b02ca49 Mon Sep 17 00:00:00 2001 From: Gianmaria Bajo Date: Wed, 7 Jun 2023 14:28:44 +0200 Subject: feat: vim.version() returns a Version object - vim.version() returns a Version object. Makes it printable and removes the need of workarounds when passing it to other vim.version methods. --- runtime/lua/vim/version.lua | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index d4a3752e37..5578423453 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -226,13 +226,11 @@ function Range:has(version) if type(version) == 'string' then ---@diagnostic disable-next-line: cast-local-type version = M.parse(version) - else + elseif getmetatable(version) ~= Version then -- Need metatable to compare versions. version = setmetatable(vim.deepcopy(version), Version) end if version then - -- Workaround: vim.version() reports "prerelease" as a boolean. - version.prerelease = version.prerelease or nil if version.prerelease ~= self.from.prerelease then return false end @@ -423,8 +421,12 @@ function M.parse(version, opts) end setmetatable(M, { + --- Returns the current Nvim version. __call = function() - return vim.fn.api_info().version + local version = vim.fn.api_info().version + -- Workaround: vim.fn.api_info().version reports "prerelease" as a boolean. + version.prerelease = version.prerelease or nil + return setmetatable(version, Version) end, }) -- cgit From d3b9feccb39124cefbe4b0c492fb0bc3f777d0b4 Mon Sep 17 00:00:00 2001 From: Stanislav Asunkin <1353637+stasjok@users.noreply.github.com> Date: Mon, 12 Jun 2023 01:48:13 +0300 Subject: docs: fix vim.tbl_get type annotations #23992 --- 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 f899157ab7..dd05299295 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -456,7 +456,7 @@ end ---
--- ---@param o table Table to index ----@param ... string Optional strings (0 or more, variadic) via which to index the table +---@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 function vim.tbl_get(o, ...) -- cgit From e6887932539315e02621edb77d5e77c7c2a0b033 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 12 Jun 2023 01:14:33 +0200 Subject: feat: tostring(vim.version()) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: tostring(vim.version()) returns "table: 0x…". Solution: Modify vim.version() to return a string prerelease instead of a boolean. Fix #23863 --- runtime/lua/vim/version.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 5578423453..92250ff1f8 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -425,7 +425,7 @@ setmetatable(M, { __call = function() local version = vim.fn.api_info().version -- Workaround: vim.fn.api_info().version reports "prerelease" as a boolean. - version.prerelease = version.prerelease or nil + version.prerelease = version.prerelease and 'dev' or nil return setmetatable(version, Version) end, }) -- cgit From 0eb02ea90a5a7c2e35bfcf99b701a28ff2901b4b Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 12 Jun 2023 20:08:08 +0800 Subject: docs: various clarifications (#23999) Close #18907 Close #20314 Close #23749 --- runtime/lua/vim/_editor.lua | 4 ++-- runtime/lua/vim/keymap.lua | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 1c7eb30b03..1de2ade1a1 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -534,9 +534,9 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) return region end ---- Defers calling `fn` until `timeout` ms passes. +--- Defers calling {fn} until {timeout} ms passes. --- ---- Use to do a one-shot timer that calls `fn` +--- Use to do a one-shot timer that calls {fn} --- Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions are --- safe to call. ---@param fn function Callback to call once `timeout` expires diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 0d7fc3b1ec..9dbc65afe8 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -27,9 +27,9 @@ local keymap = {} --- - "replace_keycodes" defaults to `true` if "expr" is `true`. --- - "noremap": inverse of "remap" (see below). --- - Also accepts: ---- - "buffer" number|boolean Creates buffer-local mapping, `0` or `true` +--- - "buffer": (number|boolean) Creates buffer-local mapping, `0` or `true` --- for current buffer. ---- - remap: (boolean) Make the mapping recursive. Inverses "noremap". +--- - "remap": (boolean) Make the mapping recursive. Inverse of "noremap". --- Defaults to `false`. ---@see |nvim_set_keymap()| function keymap.set(mode, lhs, rhs, opts) @@ -83,8 +83,8 @@ end --- vim.keymap.del({'n', 'i', 'v'}, 'w', { buffer = 5 }) ---
---@param opts table|nil A table of optional arguments: ---- - buffer: (number or boolean) Remove a mapping from the given buffer. ---- When "true" or 0, use the current buffer. +--- - "buffer": (number|boolean) Remove a mapping from the given buffer. +--- When `0` or `true`, use the current buffer. ---@see |vim.keymap.set()| --- function keymap.del(modes, lhs, opts) -- cgit From 91f67fabe69f5e3be19f37709261ea7abaa1a3cd Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 13 Jun 2023 16:53:13 +0200 Subject: fix(lsp): handle stale bufnr on LspRequest autocmd trigger (#24013) Fixes a `Invalid buffer id: 123` race when the buffer gets deleted before the callback triggered. Alternative to https://github.com/neovim/neovim/pull/23981 --- 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 6f9a6c460b..9118e7e2e1 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1519,7 +1519,7 @@ function lsp.start_client(config) local request = client.requests[request_id] request.type = 'complete' nvim_exec_autocmds('LspRequest', { - buffer = bufnr, + buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil, modeline = false, data = { client_id = client_id, request_id = request_id, request = request }, }) -- cgit From bc67bbe4469b777a958f5ad515dec777777e9f2d Mon Sep 17 00:00:00 2001 From: Rohit Sukumaran Date: Tue, 13 Jun 2023 20:47:35 +0530 Subject: fix(codelens): add buffer and line checks before displaying codelens (#23887) Co-authored-by: Rohit Sukumaran --- runtime/lua/vim/lsp/codelens.lua | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index ea8c52c334..e26bdcc6d4 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -136,6 +136,10 @@ end ---@param bufnr integer ---@param client_id integer function M.display(lenses, bufnr, client_id) + if not api.nvim_buf_is_loaded(bufnr) then + return + end + local ns = namespaces[client_id] if not lenses or not next(lenses) then api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) @@ -181,6 +185,10 @@ end ---@param bufnr integer ---@param client_id integer function M.save(lenses, bufnr, client_id) + if not api.nvim_buf_is_loaded(bufnr) then + return + end + local lenses_by_client = lens_cache_by_buf[bufnr] if not lenses_by_client then lenses_by_client = {} @@ -221,19 +229,24 @@ local function resolve_lenses(lenses, bufnr, client_id, callback) countdown() else client.request('codeLens/resolve', lens, function(_, result) - if result and result.command then + 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 -- Once all lenses got resolved there will be a full redraw for all lenses -- So that multiple lens per line are properly displayed - api.nvim_buf_set_extmark( - bufnr, - ns, - lens.range.start.line, - 0, - { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' } - ) + + local num_lines = api.nvim_buf_line_count(bufnr) + if lens.range.start.line <= num_lines then + api.nvim_buf_set_extmark( + bufnr, + ns, + lens.range.start.line, + 0, + { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' } + ) + end end + countdown() end, bufnr) end -- cgit From 79a5b89d66db74560e751561542064674e980146 Mon Sep 17 00:00:00 2001 From: Jon Huhn Date: Wed, 14 Jun 2023 05:40:11 -0500 Subject: perf(lsp): reduce polling handles for workspace/didChangeWatchedFiles (#23500) Co-authored-by: Lewis Russell --- runtime/lua/vim/_watch.lua | 73 ++++++++++++++++++++++++++++--------- runtime/lua/vim/lsp/_watchfiles.lua | 52 +++++++++++++++++--------- 2 files changed, 91 insertions(+), 34 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 3bd8a56f6e..d489cef9fc 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -11,6 +11,7 @@ M.FileChangeType = vim.tbl_add_reverse_lookup({ --- Joins filepath elements by static '/' separator --- ---@param ... (string) The path elements. +---@return string local function filepath_join(...) return table.concat({ ... }, '/') end @@ -36,7 +37,7 @@ end --- - uvflags (table|nil) --- Same flags as accepted by |uv.fs_event_start()| ---@param callback (function) The function called when new events ----@returns (function) A function to stop the watch +---@return (function) A function to stop the watch function M.watch(path, opts, callback) vim.validate({ path = { path, 'string', false }, @@ -75,10 +76,25 @@ end local default_poll_interval_ms = 2000 +--- @class watch.Watches +--- @field is_dir boolean +--- @field children? table +--- @field cancel? fun() +--- @field started? boolean +--- @field handle? uv_fs_poll_t + +--- @class watch.PollOpts +--- @field interval? integer +--- @field include_pattern? userdata +--- @field exclude_pattern? userdata + ---@private --- Implementation for poll, hiding internally-used parameters. --- ----@param watches (table|nil) A tree structure to maintain state for recursive watches. +---@param path string +---@param opts watch.PollOpts +---@param callback fun(patch: string, filechangetype: integer) +---@param watches (watch.Watches|nil) A tree structure to maintain state for recursive watches. --- - handle (uv_fs_poll_t) --- The libuv handle --- - cancel (function) @@ -88,15 +104,36 @@ local default_poll_interval_ms = 2000 --- be invoked recursively) --- - children (table|nil) --- A mapping of directory entry name to its recursive watches --- - started (boolean|nil) --- Whether or not the watcher has first been initialized. Used --- to prevent a flood of Created events on startup. +--- - started (boolean|nil) +--- Whether or not the watcher has first been initialized. Used +--- to prevent a flood of Created events on startup. +---@return fun() Cancel function local function poll_internal(path, opts, callback, watches) path = vim.fs.normalize(path) local interval = opts and opts.interval or default_poll_interval_ms watches = watches or { is_dir = true, } + watches.cancel = function() + if watches.children then + for _, w in pairs(watches.children) do + w.cancel() + end + end + if watches.handle then + stop(watches.handle) + end + end + + local function incl_match() + return not opts.include_pattern or opts.include_pattern:match(path) ~= nil + end + local function excl_match() + return opts.exclude_pattern and opts.exclude_pattern:match(path) ~= nil + end + if not watches.is_dir and not incl_match() or excl_match() then + return watches.cancel + end if not watches.handle then local poll, new_err = vim.uv.new_fs_poll() @@ -120,18 +157,9 @@ local function poll_internal(path, opts, callback, watches) end end - watches.cancel = function() - if watches.children then - for _, w in pairs(watches.children) do - w.cancel() - end - end - stop(watches.handle) - end - if watches.is_dir then watches.children = watches.children or {} - local exists = {} + local exists = {} --- @type table for name, ftype in vim.fs.dir(path) do exists[name] = true if not watches.children[name] then @@ -143,14 +171,16 @@ local function poll_internal(path, opts, callback, watches) end end - local newchildren = {} + local newchildren = {} ---@type table for name, watch in pairs(watches.children) do if exists[name] then newchildren[name] = watch else watch.cancel() watches.children[name] = nil - callback(path .. '/' .. name, M.FileChangeType.Deleted) + if watch.handle then + callback(path .. '/' .. name, M.FileChangeType.Deleted) + end end end watches.children = newchildren @@ -168,6 +198,15 @@ end ---@param opts (table|nil) Additional options --- - interval (number|nil) --- Polling interval in ms as passed to |uv.fs_poll_start()|. Defaults to 2000. +--- - include_pattern (LPeg pattern|nil) +--- An |lpeg| pattern. Only changes to files whose full paths match the pattern +--- will be reported. Only matches against non-directoriess, all directories will +--- be watched for new potentially-matching files. exclude_pattern can be used to +--- filter out directories. When nil, matches any file name. +--- - exclude_pattern (LPeg pattern|nil) +--- An |lpeg| pattern. Only changes to files and directories whose full path does +--- not match the pattern will be reported. Matches against both files and +--- directories. When nil, matches nothing. ---@param callback (function) The function called when new events ---@returns (function) A function to stop the watch. function M.poll(path, opts, callback) diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 14e5dc6cf8..87938fe4d5 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -1,7 +1,7 @@ local bit = require('bit') -local lpeg = require('lpeg') local watch = require('vim._watch') local protocol = require('vim.lsp.protocol') +local lpeg = vim.lpeg local M = {} @@ -107,6 +107,13 @@ local to_lsp_change_type = { [watch.FileChangeType.Deleted] = protocol.FileChangeType.Deleted, } +--- Default excludes the same as VSCode's `files.watcherExclude` setting. +--- https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/workbench/contrib/files/browser/files.contribution.ts#L261 +---@type Lpeg pattern +M._poll_exclude_pattern = parse('**/.git/{objects,subtree-cache}/**') + + parse('**/node_modules/*/**') + + parse('**/.hg/store/**') + --- Registers the workspace/didChangeWatchedFiles capability dynamically. --- ---@param reg table LSP Registration object. @@ -122,10 +129,10 @@ function M.register(reg, ctx) then return end - local watch_regs = {} + local watch_regs = {} --- @type table for _, w in ipairs(reg.registerOptions.watchers) do local relative_pattern = false - local glob_patterns = {} + local glob_patterns = {} --- @type {baseUri:string, pattern: string}[] if type(w.globPattern) == 'string' then for _, folder in ipairs(client.workspace_folders) do table.insert(glob_patterns, { baseUri = folder.uri, pattern = w.globPattern }) @@ -135,7 +142,7 @@ function M.register(reg, ctx) table.insert(glob_patterns, w.globPattern) end for _, glob_pattern in ipairs(glob_patterns) do - local base_dir = nil + local base_dir = nil ---@type string? if type(glob_pattern.baseUri) == 'string' then base_dir = glob_pattern.baseUri elseif type(glob_pattern.baseUri) == 'table' then @@ -144,6 +151,7 @@ function M.register(reg, ctx) assert(base_dir, "couldn't identify root of watch") base_dir = vim.uri_to_fname(base_dir) + ---@type integer local kind = w.kind or protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete @@ -153,8 +161,8 @@ function M.register(reg, ctx) pattern = lpeg.P(base_dir .. '/') * pattern end - table.insert(watch_regs, { - base_dir = base_dir, + watch_regs[base_dir] = watch_regs[base_dir] or {} + table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind, }) @@ -163,12 +171,12 @@ function M.register(reg, ctx) local callback = function(base_dir) return function(fullpath, change_type) - for _, w in ipairs(watch_regs) do + for _, w in ipairs(watch_regs[base_dir]) do change_type = to_lsp_change_type[change_type] -- e.g. match kind with Delete bit (0b0100) to Delete change_type (3) local kind_mask = bit.lshift(1, change_type - 1) local change_type_match = bit.band(w.kind, kind_mask) == kind_mask - if base_dir == w.base_dir and M._match(w.pattern, fullpath) and change_type_match then + if M._match(w.pattern, fullpath) and change_type_match then local change = { uri = vim.uri_from_fname(fullpath), type = change_type, @@ -198,15 +206,25 @@ function M.register(reg, ctx) end end - local watching = {} - for _, w in ipairs(watch_regs) do - if not watching[w.base_dir] then - watching[w.base_dir] = true - table.insert( - cancels[client_id][reg.id], - M._watchfunc(w.base_dir, { uvflags = { recursive = true } }, callback(w.base_dir)) - ) - end + for base_dir, watches in pairs(watch_regs) do + local include_pattern = vim.iter(watches):fold(lpeg.P(false), function(acc, w) + return acc + w.pattern + end) + + table.insert( + cancels[client_id][reg.id], + M._watchfunc(base_dir, { + uvflags = { + recursive = true, + }, + -- include_pattern will ensure the pattern from *any* watcher definition for the + -- base_dir matches. This first pass prevents polling for changes to files that + -- will never be sent to the LSP server. A second pass in the callback is still necessary to + -- match a *particular* pattern+kind pair. + include_pattern = include_pattern, + exclude_pattern = M._poll_exclude_pattern, + }, callback(base_dir)) + ) end end -- cgit From c07dceba335c56c9a356395ad0d1e5a14d416752 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Sat, 17 Jun 2023 08:01:31 +0200 Subject: fix(lsp): allow Lua pattern chars in code action filter (#24041) Previously, filtering code actions with the "only" option failed if the code action kind contained special Lua pattern chars such as "-" (e.g. the ocaml language server supports a "type-annotate" code action). Solution: use string comparison instead of string.find --- runtime/lua/vim/lsp/buf.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index bb3ca0e6d6..e0034cf86e 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -608,9 +608,9 @@ local function on_code_action_results(results, ctx, options) end local found = false for _, o in ipairs(options.context.only) do - -- action kinds are hierarchical with . as a separator: when requesting only - -- 'quickfix' this filter allows both 'quickfix' and 'quickfix.foo', for example - if a.kind:find('^' .. o .. '$') or a.kind:find('^' .. o .. '%.') then + -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate' + -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example + if a.kind == o or vim.startswith(a.kind, o .. '.') then found = true break end -- cgit From 8a7e3353eb5bffb10015254917361266b4b20511 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Sun, 18 Jun 2023 14:49:33 +0300 Subject: fix(fs): make `normalize()` work with '/' path (#24047) Problem: Current implementation of "remove trailing /" doesn't account for the case of literal '/' as path. Solution: Remove trailing / only if it preceded by something else. Co-authored by: notomo --- 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 c89147dbd2..cdb314fa3d 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -348,9 +348,7 @@ function M.normalize(path, opts) path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) end - path = path:gsub('\\', '/'):gsub('/+', '/') - - return path:sub(-1) == '/' and path:sub(1, -2) or path + return (path:gsub('\\', '/'):gsub('/+', '/'):gsub('(.)/$', '%1')) end return M -- cgit From 8c9dab3e0d788d44c8a2fee83a6193f5955c814e Mon Sep 17 00:00:00 2001 From: Santos Gallegos Date: Sun, 18 Jun 2023 09:42:17 -0500 Subject: fix(treesitter): use vim.highlight.priorities instead of hardcoded 100 (#24052) Problem: Treesitter highlighting base priority cannot be customized. Solution: Use `vim.highlight.priorities.treesitter` instead of hard-coded value. --- runtime/lua/vim/treesitter/highlighter.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 4bb764c5c6..d4db6bc404 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -260,12 +260,14 @@ local function on_line_impl(self, buf, line, is_spell_nav) 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 = (tonumber(metadata.priority) or 100) + spell_pri_offset, -- Low but leaves room below + priority = priority, conceal = metadata.conceal, spell = spell, }) -- cgit From cee981bf09c81ab4b2fe6facf45076ea4bac46a5 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 19 Jun 2023 02:24:44 -0700 Subject: docs #22363 Co-authored by: zeertzjq Co-authored by: Steven Todd McIntyre II <114119064+stmii@users.noreply.github.com> Co-authored by: nobe4 - docs: mention --luadev-mod to run with lua runtime files When changing a lua file in the ./runtime folder, a new contributor might expect changes to be applied to the built Neovim binary. --- runtime/lua/vim/iter.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 204d22b9be..9c7bd13164 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -1,7 +1,8 @@ ---@defgroup lua-iter --- ---- The \*vim.iter\* module provides a generic "iterator" interface over tables ---- and iterator functions. +--- @brief The \*vim.iter\* module provides a generic interface for working with +--- iterables: tables, lists, iterator functions, pair()/ipair()-like iterators, +--- and \`vim.iter()\` objects. --- --- \*vim.iter()\* wraps its table or function argument into an \*Iter\* object --- with methods (such as |Iter:filter()| and |Iter:map()|) that transform the -- cgit From 72a6643b1380cdf6f1153d70eeaffb90bdca30d6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 19 Jun 2023 08:40:33 -0700 Subject: docs #24061 - nvim requires rpc responses in reverse order. https://github.com/neovim/neovim/issues/19932 - NVIM_APPNAME: UIs normally should NOT set this. ref #23520 fix #24050 fix #23660 fix #23353 fix #23337 fix #22213 fix #19161 fix #18088 fix #20693 --- runtime/lua/vim/lsp.lua | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 9118e7e2e1..761a8406f2 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1491,7 +1491,7 @@ function lsp.start_client(config) ---successful, then it will return {request_id} as the ---second result. You can use this with `client.cancel_request(request_id)` ---to cancel the-request. - ---@see |vim.lsp.buf_request()| + ---@see |vim.lsp.buf_request_all()| function client.request(method, params, handler, bufnr) if not handler then handler = assert( @@ -2119,22 +2119,19 @@ function lsp.buf_request(bufnr, method, params, handler) return client_request_ids, _cancel_all_requests end ----Sends an async request for all active clients attached to the buffer. ----Executes the callback on the combined result. ----Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are ----different. +--- Sends an async request for all active clients attached to the buffer and executes the `handler` +--- callback with the combined result. --- ---@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 callback fun(request_results: table) (function) ---- The callback to call when all requests are finished. ---- Unlike `buf_request`, this will collect all the responses from each server instead of handling them. ---- A map of client_id:request_result will be provided to the callback. ---- ----@return fun() cancel A function that will cancel all requests -function lsp.buf_request_all(bufnr, method, params, callback) - local request_results = {} +---@param handler fun(results: table) (function) +--- Handler called after all requests are completed. Server results are passed as +--- a `client_id:result` map. +--- +---@return fun() cancel Function that cancels all requests. +function lsp.buf_request_all(bufnr, method, params, handler) + local results = {} local result_count = 0 local expected_result_count = 0 @@ -2147,12 +2144,12 @@ function lsp.buf_request_all(bufnr, method, params, callback) end) local function _sync_handler(err, result, ctx) - request_results[ctx.client_id] = { error = err, result = result } + results[ctx.client_id] = { error = err, result = result } result_count = result_count + 1 set_expected_result_count() if result_count >= expected_result_count then - callback(request_results) + handler(results) end end @@ -2164,8 +2161,8 @@ end --- Sends a request to all server and waits for the response of all of them. --- --- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result. ---- Parameters are the same as |vim.lsp.buf_request()| but the return result is ---- different. Wait maximum of {timeout_ms} (default 1000) ms. +--- Parameters are the same as |vim.lsp.buf_request_all()| but the result is +--- different. Waits a maximum of {timeout_ms} (default 1000) ms. --- ---@param bufnr (integer) Buffer handle, or 0 for current. ---@param method (string) LSP method name -- cgit From ca5de9306c00d07cce1daef1f0038c937098bc66 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Tue, 20 Jun 2023 11:36:54 +0530 Subject: feat(lsp): inlay hints #23984 Add automatic refresh and a public interface on top of #23736 * add on_reload, on_detach handlers in `enable()` buf_attach, and LspDetach autocommand in case of manual detach * unify `__buffers` and `hint_cache_by_buf` * use callback bufnr in `on_lines` callback, bufstate: remove __index override * move user-facing functions into vim.lsp.buf, unify enable/disable/toggle Closes #18086 --- runtime/lua/vim/lsp.lua | 1 - runtime/lua/vim/lsp/_inlay_hint.lua | 164 +++++++++++++++++++++++++----------- runtime/lua/vim/lsp/buf.lua | 15 ++++ runtime/lua/vim/lsp/handlers.lua | 3 - 4 files changed, 131 insertions(+), 52 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 761a8406f2..917aeb6604 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -17,7 +17,6 @@ local if_nil = vim.F.if_nil local lsp = { protocol = protocol, - _inlay_hint = require('vim.lsp._inlay_hint'), handlers = default_handlers, diff --git a/runtime/lua/vim/lsp/_inlay_hint.lua b/runtime/lua/vim/lsp/_inlay_hint.lua index aa6ec9aca8..70d332a1ac 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -6,22 +6,30 @@ local M = {} ---@class lsp._inlay_hint.bufstate ---@field version integer ---@field client_hint table> client_id -> (lnum -> hints) +---@field enabled boolean Whether inlay hints are enabled for the buffer +---@field timer uv.uv_timer_t? Debounce timer associated with the buffer ---@type table -local hint_cache_by_buf = setmetatable({}, { - __index = function(t, b) - local key = b > 0 and b or api.nvim_get_current_buf() - return rawget(t, key) - end, -}) +local bufstates = {} local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') +local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) -M.__explicit_buffers = {} +--- Reset the request debounce timer of a buffer +---@private +local function reset_timer(reset_bufnr) + local timer = bufstates[reset_bufnr].timer + if timer then + bufstates[reset_bufnr].timer = nil + if not timer:is_closing() then + timer:stop() + timer:close() + end + end +end --- |lsp-handler| for the method `textDocument/inlayHint` --- Store hints for a specific buffer and client ---- Resolves unresolved hints ---@private function M.on_inlayhint(err, result, ctx, _) if err then @@ -36,21 +44,20 @@ function M.on_inlayhint(err, result, ctx, _) if not result then return end - local bufstate = hint_cache_by_buf[bufnr] - if not bufstate then - bufstate = { - client_hint = vim.defaulttable(), - version = ctx.version, - } - hint_cache_by_buf[bufnr] = bufstate + local bufstate = bufstates[bufnr] + if not (bufstate.client_hint and bufstate.version) then + bufstate.client_hint = vim.defaulttable() + bufstate.version = ctx.version api.nvim_buf_attach(bufnr, false, { - on_detach = function(_, b) - api.nvim_buf_clear_namespace(b, namespace, 0, -1) - hint_cache_by_buf[b] = nil + on_detach = function(_, cb_bufnr) + api.nvim_buf_clear_namespace(cb_bufnr, namespace, 0, -1) + bufstates[cb_bufnr].version = nil + bufstates[cb_bufnr].client_hint = nil end, - on_reload = function(_, b) - api.nvim_buf_clear_namespace(b, namespace, 0, -1) - hint_cache_by_buf[b] = nil + on_reload = function(_, cb_bufnr) + api.nvim_buf_clear_namespace(cb_bufnr, namespace, 0, -1) + bufstates[cb_bufnr].version = nil + bufstates[cb_bufnr].client_hint = nil end, }) end @@ -95,28 +102,24 @@ end ---@private local function resolve_bufnr(bufnr) - return bufnr == 0 and api.nvim_get_current_buf() or bufnr + return bufnr > 0 and bufnr or api.nvim_get_current_buf() end --- Refresh inlay hints for a buffer --- ---- It is recommended to trigger this using an autocmd or via keymap. ---@param opts (nil|table) Optional arguments --- - bufnr (integer, default: 0): Buffer whose hints to refresh --- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer --- ---- Example: ----
vim
----   autocmd BufEnter,InsertLeave,BufWritePost  lua vim.lsp._inlay_hint.refresh()
---- 
---- ---@private function M.refresh(opts) opts = opts or {} - local bufnr = opts.bufnr or 0 + local bufnr = resolve_bufnr(opts.bufnr or 0) + local bufstate = bufstates[bufnr] + if not (bufstate and bufstate.enabled) then + return + end local only_visible = opts.only_visible or false - bufnr = resolve_bufnr(bufnr) - M.__explicit_buffers[bufnr] = true local buffer_windows = {} for _, winid in ipairs(api.nvim_list_wins()) do if api.nvim_win_get_buf(winid) == bufnr then @@ -148,30 +151,95 @@ function M.refresh(opts) end --- Clear inlay hints ---- ----@param client_id integer|nil filter by client_id. All clients if nil ----@param bufnr integer|nil filter by buffer. All buffers if nil +---@param bufnr (integer) Buffer handle, or 0 for current ---@private -function M.clear(client_id, bufnr) - local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(hint_cache_by_buf) - for _, iter_bufnr in ipairs(buffers) do - M.__explicit_buffers[iter_bufnr] = false - local bufstate = hint_cache_by_buf[iter_bufnr] - local client_lens = (bufstate or {}).client_hint or {} - local client_ids = client_id and { client_id } or vim.tbl_keys(client_lens) - for _, iter_client_id in ipairs(client_ids) do - if bufstate then - bufstate.client_hint[iter_client_id] = {} - end +local function clear(bufnr) + bufnr = resolve_bufnr(bufnr) + reset_timer(bufnr) + local bufstate = bufstates[bufnr] + local client_lens = (bufstate or {}).client_hint or {} + local client_ids = vim.tbl_keys(client_lens) + for _, iter_client_id in ipairs(client_ids) do + if bufstate then + bufstate.client_hint[iter_client_id] = {} end - api.nvim_buf_clear_namespace(iter_bufnr, namespace, 0, -1) end - vim.cmd('redraw!') + api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) + api.nvim__buf_redraw_range(bufnr, 0, -1) +end + +---@private +local function make_request(request_bufnr) + reset_timer(request_bufnr) + M.refresh({ bufnr = request_bufnr }) +end + +--- Enable inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +function M.enable(bufnr) + bufnr = resolve_bufnr(bufnr) + local bufstate = bufstates[bufnr] + if not (bufstate and bufstate.enabled) then + bufstates[bufnr] = { enabled = true, timer = nil } + M.refresh({ bufnr = bufnr }) + api.nvim_buf_attach(bufnr, true, { + on_lines = function(_, cb_bufnr) + if not bufstates[cb_bufnr].enabled then + return true + end + reset_timer(cb_bufnr) + bufstates[cb_bufnr].timer = vim.defer_fn(function() + make_request(cb_bufnr) + end, 200) + end, + on_reload = function(_, cb_bufnr) + clear(cb_bufnr) + bufstates[cb_bufnr] = nil + M.refresh({ bufnr = cb_bufnr }) + end, + on_detach = function(_, cb_bufnr) + clear(cb_bufnr) + bufstates[cb_bufnr] = nil + end, + }) + api.nvim_create_autocmd('LspDetach', { + buffer = bufnr, + callback = function(opts) + clear(opts.buf) + end, + once = true, + group = augroup, + }) + end +end + +--- Disable inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +function M.disable(bufnr) + bufnr = resolve_bufnr(bufnr) + clear(bufnr) + bufstates[bufnr].enabled = nil + bufstates[bufnr].timer = nil +end + +--- Toggle inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +function M.toggle(bufnr) + bufnr = resolve_bufnr(bufnr) + local bufstate = bufstates[bufnr] + if bufstate and bufstate.enabled then + M.disable(bufnr) + else + M.enable(bufnr) + end end api.nvim_set_decoration_provider(namespace, { on_win = function(_, _, bufnr, topline, botline) - local bufstate = hint_cache_by_buf[bufnr] + local bufstate = bufstates[bufnr] if not bufstate then return end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index e0034cf86e..c3deffc1f9 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -810,4 +810,19 @@ function M.execute_command(command_params) request('workspace/executeCommand', command_params) end +--- Enable/disable/toggle inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@param enable (boolean|nil) true/false to enable/disable, nil to toggle +function M.inlay_hint(bufnr, enable) + vim.validate({ enable = { enable, { 'boolean', 'nil' } }, bufnr = { bufnr, 'number' } }) + local inlay_hint = require('vim.lsp._inlay_hint') + if enable then + inlay_hint.enable(bufnr) + elseif enable == false then + inlay_hint.disable(bufnr) + else + inlay_hint.toggle(bufnr) + end +end + return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 44a9a58aca..284e3ef2d0 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -619,9 +619,6 @@ end ---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh M['workspace/inlayHint/refresh'] = function(err, _, ctx) local inlay_hint = require('vim.lsp._inlay_hint') - if not inlay_hint.__explicit_buffers[ctx.bufnr] then - return vim.NIL - end if err then return vim.NIL end -- cgit From 96b94f8d77774e3dabdec48bba2b56246a3ccba8 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Tue, 20 Jun 2023 15:06:06 +0530 Subject: fix(lsp): duplicate on_detach, on_reload callbacks #24067 M.enable already clears bufstate[bufnr] and the namespace, the duplicate callbacks cause an error (indexing bufstate[bufnr] fails) --- runtime/lua/vim/lsp/_inlay_hint.lua | 12 ------------ 1 file changed, 12 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 70d332a1ac..bdce464480 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -48,18 +48,6 @@ function M.on_inlayhint(err, result, ctx, _) if not (bufstate.client_hint and bufstate.version) then bufstate.client_hint = vim.defaulttable() bufstate.version = ctx.version - api.nvim_buf_attach(bufnr, false, { - on_detach = function(_, cb_bufnr) - api.nvim_buf_clear_namespace(cb_bufnr, namespace, 0, -1) - bufstates[cb_bufnr].version = nil - bufstates[cb_bufnr].client_hint = nil - end, - on_reload = function(_, cb_bufnr) - api.nvim_buf_clear_namespace(cb_bufnr, namespace, 0, -1) - bufstates[cb_bufnr].version = nil - bufstates[cb_bufnr].client_hint = nil - end, - }) end local hints_by_client = bufstate.client_hint local client = vim.lsp.get_client_by_id(client_id) -- cgit From 64f2691a984a5b1e2958d5656a910054982a6f0e Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 20 Jun 2023 18:36:18 +0200 Subject: refactor(lsp): extract common execute command functionality (#24065) --- runtime/lua/vim/lsp.lua | 40 ++++++++++++++++++++++++++++++++++++++++ runtime/lua/vim/lsp/buf.lua | 18 ++---------------- runtime/lua/vim/lsp/codelens.lua | 24 +++--------------------- runtime/lua/vim/lsp/types.lua | 2 +- 4 files changed, 46 insertions(+), 38 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 917aeb6604..25e69a8006 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1653,6 +1653,46 @@ function lsp.start_client(config) return rpc.is_closing() end + ---@private + --- Execute a lsp command, either via client command function (if available) + --- or via workspace/executeCommand (if supported by the server) + --- + ---@param command lsp.Command + ---@param context? {bufnr: integer} + ---@param handler? lsp-handler only called if a server command + function client._exec_cmd(command, context, handler) + context = vim.deepcopy(context or {}) + context.bufnr = context.bufnr or api.nvim_get_current_buf() + context.client_id = client.id + local cmdname = command.command + local fn = client.commands[cmdname] or lsp.commands[cmdname] + if fn then + fn(command, context) + return + end + + local command_provider = client.server_capabilities.executeCommandProvider + local commands = type(command_provider) == 'table' and command_provider.commands or {} + if not vim.list_contains(commands, cmdname) then + vim.notify_once( + string.format( + 'Language server `%s` does not support command `%s`. This command may require a client extension.', + client.name, + cmdname + ), + vim.log.levels.WARN + ) + return + end + -- Not using command directly to exclude extra properties, + -- see https://github.com/python-lsp/python-lsp-server/issues/146 + local params = { + command = command.command, + arguments = command.arguments, + } + client.request('workspace/executeCommand', params, handler, context.bufnr) + end + ---@private --- Runs the on_attach function from the client's config if it was defined. ---@param bufnr integer Buffer number diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c3deffc1f9..45056cf272 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -646,21 +646,7 @@ local function on_code_action_results(results, ctx, options) end if action.command then local command = type(action.command) == 'table' and action.command or action - local fn = client.commands[command.command] or vim.lsp.commands[command.command] - if fn then - local enriched_ctx = vim.deepcopy(ctx) - enriched_ctx.client_id = client.id - fn(command, enriched_ctx) - else - -- Not using command directly to exclude extra properties, - -- see https://github.com/python-lsp/python-lsp-server/issues/146 - local params = { - command = command.command, - arguments = command.arguments, - workDoneToken = command.workDoneToken, - } - client.request('workspace/executeCommand', params, nil, ctx.bufnr) - end + client._exec_cmd(command, ctx) end end @@ -697,7 +683,7 @@ local function on_code_action_results(results, ctx, options) return end apply_action(resolved_action, client) - end) + end, ctx.bufnr) else apply_action(action, client) end diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index e26bdcc6d4..5acfe90d5e 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -33,30 +33,12 @@ local function execute_lens(lens, bufnr, client_id) local client = vim.lsp.get_client_by_id(client_id) assert(client, 'Client is required to execute lens, client_id=' .. client_id) - local command = lens.command - local fn = client.commands[command.command] or vim.lsp.commands[command.command] - if fn then - fn(command, { bufnr = bufnr, client_id = client_id }) - return - end - -- Need to use the client that returned the lens → must not use buf_request - local command_provider = client.server_capabilities.executeCommandProvider - local commands = type(command_provider) == 'table' and command_provider.commands or {} - if not vim.list_contains(commands, command.command) then - vim.notify( - string.format( - 'Language server does not support command `%s`. This command may require a client extension.', - command.command - ), - vim.log.levels.WARN - ) - return - end - client.request('workspace/executeCommand', command, function(...) + + client._exec_cmd(lens.command, { bufnr = bufnr }, function(...) local result = vim.lsp.handlers['workspace/executeCommand'](...) M.refresh() return result - end, bufnr) + end) end --- Return all lenses for the given buffer diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua index 108aeeb922..cdfbcfb11b 100644 --- a/runtime/lua/vim/lsp/types.lua +++ b/runtime/lua/vim/lsp/types.lua @@ -1,6 +1,6 @@ ---@meta ----@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil) +---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil): any? ---@class lsp.HandlerContext ---@field method string -- cgit From d3e0352574731ae47b2ad2e4683c326ae5b2a549 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Tue, 20 Jun 2023 22:06:31 +0530 Subject: fix(lsp): check if inlay hints are enabled for a buffer before disabling (#24074) disabling before enabling throws an error otherwise, because bufstate[bufnr] doesn't exist --- runtime/lua/vim/lsp/_inlay_hint.lua | 8 +++++--- 1 file changed, 5 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 bdce464480..e7cc8ba7ae 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -207,9 +207,11 @@ end ---@private function M.disable(bufnr) bufnr = resolve_bufnr(bufnr) - clear(bufnr) - bufstates[bufnr].enabled = nil - bufstates[bufnr].timer = nil + if bufstates[bufnr] and bufstates[bufnr].enabled then + clear(bufnr) + bufstates[bufnr].enabled = nil + bufstates[bufnr].timer = nil + end end --- Toggle inlay hints for a buffer -- cgit From 3bf887f6e08fa272679187340ca483809275b20a Mon Sep 17 00:00:00 2001 From: Sooryakiran Ponnath Date: Tue, 20 Jun 2023 15:17:13 -0400 Subject: fix(lsp): always return boolean in lsp.buf_client_attach (#24077) Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 25e69a8006..cb1c101c58 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1799,6 +1799,7 @@ end --- ---@param bufnr (integer) Buffer handle, or 0 for current ---@param client_id (integer) Client id +---@return boolean success `true` if client was attached successfully; `false` otherwise function lsp.buf_attach_client(bufnr, client_id) validate({ bufnr = { bufnr, 'n', true }, @@ -1887,7 +1888,7 @@ function lsp.buf_attach_client(bufnr, client_id) end if buffer_client_ids[client_id] then - return + return true end -- This is our first time attaching this client to this buffer. buffer_client_ids[client_id] = true -- cgit From e42fdaad21a87d0aaf882f1ad836b00d2eccd21a Mon Sep 17 00:00:00 2001 From: Akin <22454918+akinsho@users.noreply.github.com> Date: Wed, 21 Jun 2023 09:55:19 +0200 Subject: fix(lsp): add spacing for inlay hints separately #24079 Problem: Spacing around inlay hints has the same highlight as the hint itself. The LSP spec for inlay hints specifically mentions the padding should not be coloured: /** Render padding before the hint. Note: Padding should use the editor's background color, not the background color of the hint itself. That means padding can be used to visually align/separate an inlay hint. */ paddingLeft?: boolean; /** Render padding after the hint. Note: Padding should use the editor's background color, not the background color of the hint itself. That means padding can be used to visually align/separate an inlay hint. */ paddingRight?: boolean; Solution: Add the space as separate parts of the virtual text, don't add the space to the text itself. --- runtime/lua/vim/lsp/_inlay_hint.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 e7cc8ba7ae..8edf14e707 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -252,18 +252,18 @@ api.nvim_set_decoration_provider(namespace, { text = text .. part.value end end + local vt = {} if hint.paddingLeft then - text = ' ' .. text + vt[#vt + 1] = { ' ' } end + vt[#vt + 1] = { text, 'LspInlayHint' } if hint.paddingRight then - text = text .. ' ' + vt[#vt + 1] = { ' ' } end api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { virt_text_pos = 'inline', ephemeral = false, - virt_text = { - { text, 'LspInlayHint' }, - }, + virt_text = vt, hl_mode = 'combine', }) end -- cgit From 21b074feb09b7f2e4807a816be6fd0687f23c564 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 21 Jun 2023 11:34:49 +0200 Subject: refactor(lsp): report full Nvim version string in clientInfo --- runtime/lua/vim/lsp.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index cb1c101c58..ea81244c68 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1345,7 +1345,6 @@ function lsp.start_client(config) messages = 'messages', verbose = 'verbose', } - local version = vim.version() local workspace_folders --- @type table[]? local root_uri --- @type string? @@ -1379,7 +1378,7 @@ function lsp.start_client(config) -- since 3.15.0 clientInfo = { name = 'Neovim', - version = string.format('%s.%s.%s', version.major, version.minor, version.patch), + version = tostring(vim.version()), }, -- The rootPath of the workspace. Is null if no folder is open. -- -- cgit From 43e76cc3462bc5bcf2b6ade8af1c36e21d3da3c9 Mon Sep 17 00:00:00 2001 From: Julian Grinblat Date: Thu, 22 Jun 2023 16:36:38 +0900 Subject: fix: tostring(vim.version()) fails if build is NIL #24097 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Since #23925, Version.build may be vim.NIL, which causes tostring() to fail: E5108: Error executing lua E5114: Error while converting print argument #1: …/version.lua:129: attempt to concatenate field 'build' (a userdata value) stack traceback: [C]: in function 'print' [string ":lua"]:1: in main chunk Solution: Handle vim.NIL in Version:__tostring(). --- runtime/lua/vim/version.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 92250ff1f8..cd28a9b54b 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -125,7 +125,7 @@ function Version:__tostring() if self.prerelease then ret = ret .. '-' .. self.prerelease end - if self.build then + if self.build and self.build ~= vim.NIL then ret = ret .. '+' .. self.build end return ret -- cgit From 4d3a04279d32bc97d18ab2883c678c94f80487bc Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 22 Jun 2023 10:18:49 +0200 Subject: perf(lsp): remove grouping logic from lsp.status (#24096) With the title carry-over logic in the `$/progress` handler it's not necessary to group again in vim.lsp.status --- runtime/lua/vim/lsp.lua | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index ea81244c68..38e0e34790 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -902,18 +902,13 @@ end ---@return string function lsp.status() local percentage = nil - local groups = {} + local messages = {} for _, client in ipairs(vim.lsp.get_active_clients()) do 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 = {} - groups[progress.token] = group - end - group.title = value.title or group.title - group.message = value.message or group.message + local message = value.message and (value.title .. ': ' .. value.message) or value.title + messages[#messages + 1] = message if value.percentage then percentage = math.max(percentage or 0, value.percentage) end @@ -922,17 +917,6 @@ function lsp.status() -- Just ignore it as there is no sensible way to display it end end - local messages = {} - for _, group in pairs(groups) do - if group.title then - table.insert( - messages, - group.message and (group.title .. ': ' .. group.message) or group.title - ) - elseif group.message then - table.insert(messages, group.message) - end - end local message = table.concat(messages, ', ') if percentage then return string.format('%3d%%: %s', percentage, message) -- cgit From 4e6356559c8cd44dbcaa765d1f39e176064526ec Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Thu, 22 Jun 2023 03:44:51 -0700 Subject: test: spellcheck :help (vimdoc) files #24109 Enforce consistent terminology (defined in `gen_help_html.lua:spell_dict`) for common misspellings. This does not spellcheck English in general (perhaps a future TODO, though it may be noisy). --- runtime/lua/vim/_editor.lua | 6 +++--- runtime/lua/vim/_meta.lua | 4 ++-- runtime/lua/vim/loader.lua | 22 +++++++++++----------- runtime/lua/vim/lsp.lua | 2 +- runtime/lua/vim/lsp/buf.lua | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 1de2ade1a1..e2ed0d980e 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -3,7 +3,7 @@ -- Lua code lives in one of three places: -- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the -- `inspect` and `lpeg` modules. --- 2. runtime/lua/vim/shared.lua: pure lua functions which always +-- 2. runtime/lua/vim/shared.lua: pure Lua functions which always -- are available. Used in the test runner, as well as worker threads -- and processes launched from Nvim. -- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with @@ -839,10 +839,10 @@ do -- some bugs, so fake the two-step dance for now. local matches - --- Omnifunc for completing lua values from the runtime lua interpreter, + --- Omnifunc for completing Lua values from the runtime Lua interpreter, --- similar to the builtin completion for the `:lua` command. --- - --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a lua buffer. + --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a Lua buffer. function vim.lua_omnifunc(find_start, _) if find_start == 1 then local line = vim.api.nvim_get_current_line() diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index e3ad4d76c9..913f1fe203 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -239,7 +239,7 @@ local to_vim_value = { end, } ---- Convert a lua value to a vimoption_T value +--- Convert a Lua value to a vimoption_T value local function convert_value_to_vim(name, info, value) if value == nil then return vim.NIL @@ -250,7 +250,7 @@ local function convert_value_to_vim(name, info, value) return to_vim_value[info.metatype](info, value) end --- Map of OptionType to functions that take vimoption_T values and convert to lua values. +-- Map of OptionType to functions that take vimoption_T values and convert to Lua values. -- Each function takes (info, vim_value) -> lua_value local to_lua_value = { boolean = passthrough, diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index 9024bdb231..f340dc85b5 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -161,7 +161,7 @@ function Loader.read(name) end end ---- The `package.loaders` loader for lua files using the cache. +--- The `package.loaders` loader for Lua files using the cache. ---@param modname string module name ---@return string|function ---@private @@ -211,7 +211,7 @@ end ---@private -- luacheck: ignore 312 function Loader.loadfile(filename, mode, env) - -- ignore mode, since we byte-compile the lua source files + -- ignore mode, since we byte-compile the Lua source files mode = nil return Loader.load(normalize(filename), { mode = mode, env = env }) end @@ -268,7 +268,7 @@ function Loader.load(modpath, opts) return chunk, err end ---- Finds lua modules for the given module name. +--- Finds Lua modules for the given module name. ---@param modname string Module name, or `"*"` to find the top-level modules instead ---@param opts? ModuleFindOpts (table|nil) Options for finding a module: --- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`) @@ -289,7 +289,7 @@ function M.find(modname, opts) local idx = modname:find('.', 1, true) -- HACK: fix incorrect require statements. Really not a fan of keeping this, - -- but apparently the regular lua loader also allows this + -- but apparently the regular Lua loader also allows this if idx == 1 then modname = modname:gsub('^%.+', '') basename = modname:gsub('%.', '/') @@ -386,9 +386,9 @@ end --- Enables the experimental Lua module loader: --- * overrides loadfile ---- * adds the lua loader using the byte-compilation cache +--- * adds the Lua loader using the byte-compilation cache --- * adds the libs loader ---- * removes the default Neovim loader +--- * removes the default Nvim loader function M.enable() if M.enabled then return @@ -396,11 +396,11 @@ function M.enable() M.enabled = true vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p') _G.loadfile = Loader.loadfile - -- add lua loader + -- add Lua loader table.insert(loaders, 2, Loader.loader) -- add libs loader table.insert(loaders, 3, Loader.loader_lib) - -- remove Neovim loader + -- remove Nvim loader for l, loader in ipairs(loaders) do if loader == vim._load_package then table.remove(loaders, l) @@ -411,7 +411,7 @@ end --- Disables the experimental Lua module loader: --- * removes the loaders ---- * adds the default Neovim loader +--- * adds the default Nvim loader function M.disable() if not M.enabled then return @@ -426,8 +426,8 @@ function M.disable() table.insert(loaders, 2, vim._load_package) end ---- Return the top-level `/lua/*` modules for this path ----@param path string path to check for top-level lua modules +--- Return the top-level \`/lua/*` modules for this path +---@param path string path to check for top-level Lua modules ---@private function Loader.lsmod(path) if not Loader._indexed[path] then diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 38e0e34790..5b192ca514 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1076,7 +1076,7 @@ end --- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was --- sent to it. You can only modify the `client.offset_encoding` here before --- any notifications are sent. Most language servers expect to be sent client specified settings after ---- initialization. Neovim does not make this assumption. A +--- initialization. Nvim does not make this assumption. A --- `workspace/didChangeConfiguration` notification should be sent --- to the server during on_init. --- diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 45056cf272..17b444a6e8 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -159,7 +159,7 @@ end --- @param options table|nil Optional table which holds the following optional fields: --- - formatting_options (table|nil): --- Can be used to specify FormattingOptions. Some unspecified options will be ---- automatically derived from the current Neovim options. +--- automatically derived from the current Nvim options. --- See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions --- - timeout_ms (integer|nil, default 1000): --- Time in milliseconds to block for formatting requests. No effect if async=true -- cgit From 134b9ec483616e20d96c26fdb7ef3f3e912108a8 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 22 Jun 2023 13:54:35 +0200 Subject: feat(lsp): soft deprecate vim.lsp.for_each_buffer_client (#24104) There is no need for two ways to access all clients of a buffer. This doesn't add a `vim.deprecate` call yet, as the function is probably used a lot, but removes it from the documentation and annotates it with `@deprecated` --- runtime/lua/vim/lsp.lua | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5b192ca514..970bb56478 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1740,7 +1740,7 @@ local function text_document_did_save_handler(bufnr) bufnr = resolve_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr) local text = once(buf_get_full_text) - for_each_buffer_client(bufnr, function(client) + for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do local name = api.nvim_buf_get_name(bufnr) local old_name = changetracking._get_and_set_name(client, bufnr, name) if old_name and name ~= old_name then @@ -1772,7 +1772,7 @@ local function text_document_did_save_handler(bufnr) text = included_text, }) end - end) + end end --- Implements the `textDocument/did…` notifications required to track a buffer @@ -1808,7 +1808,7 @@ function lsp.buf_attach_client(bufnr, client_id) buffer = bufnr, desc = 'vim.lsp: textDocument/willSave', callback = function(ctx) - for_each_buffer_client(ctx.buf, function(client) + for _, client in ipairs(lsp.get_active_clients({ bufnr = ctx.buf })) do local params = { textDocument = { uri = uri, @@ -1827,7 +1827,7 @@ function lsp.buf_attach_client(bufnr, client_id) log.error(vim.inspect(err)) end end - end) + end end, }) api.nvim_create_autocmd('BufWritePost', { @@ -1843,23 +1843,23 @@ function lsp.buf_attach_client(bufnr, client_id) on_lines = text_document_did_change_handler, on_reload = function() local params = { textDocument = { uri = uri } } - for_each_buffer_client(bufnr, function(client, _) + for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do changetracking.reset_buf(client, bufnr) if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) end text_document_did_open_handler(bufnr, client) - end) + end end, on_detach = function() local params = { textDocument = { uri = uri } } - for_each_buffer_client(bufnr, function(client, _) + for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do changetracking.reset_buf(client, bufnr) if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) end client.attached_buffers[bufnr] = nil - end) + end util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil end, @@ -1932,7 +1932,7 @@ function lsp.buf_detach_client(bufnr, client_id) all_buffer_active_clients[bufnr] = nil end - local namespace = vim.lsp.diagnostic.get_namespace(client_id) + local namespace = lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id)) @@ -2104,34 +2104,30 @@ function lsp.buf_request(bufnr, method, params, handler) handler = { handler, 'f', true }, }) - local supported_clients = {} + bufnr = resolve_bufnr(bufnr) local method_supported = false - for_each_buffer_client(bufnr, function(client, client_id) + local clients = lsp.get_active_clients({ bufnr = bufnr }) + local client_request_ids = {} + for _, client in ipairs(clients) do if client.supports_method(method, { bufnr = bufnr }) then method_supported = true - table.insert(supported_clients, client_id) + + local request_success, request_id = client.request(method, params, handler, bufnr) + -- This could only fail if the client shut down in the time since we looked + -- it up and we did the request, which should be rare. + if request_success then + client_request_ids[client.id] = request_id + end end - end) + end -- if has client but no clients support the given method, notify the user - if - not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported - then + if next(clients) and not method_supported then vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR) nvim_command('redraw') return {}, function() end end - local client_request_ids = {} - for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr) - local request_success, request_id = client.request(method, params, handler, resolved_bufnr) - -- This could only fail if the client shut down in the time since we looked - -- it up and we did the request, which should be rare. - if request_success then - client_request_ids[client_id] = request_id - end - end, supported_clients) - local function _cancel_all_requests() for client_id, request_id in pairs(client_request_ids) do local client = active_clients[client_id] @@ -2159,11 +2155,11 @@ function lsp.buf_request_all(bufnr, method, params, handler) local expected_result_count = 0 local set_expected_result_count = once(function() - for_each_buffer_client(bufnr, function(client) + for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do if client.supports_method(method, { bufnr = bufnr }) then expected_result_count = expected_result_count + 1 end - end) + end end) local function _sync_handler(err, result, ctx) @@ -2226,11 +2222,11 @@ function lsp.buf_notify(bufnr, method, params) method = { method, 's' }, }) local resp = false - for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr) + for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do if client.rpc.notify(method, params) then resp = true end - end) + end return resp end @@ -2371,7 +2367,7 @@ function lsp.formatexpr(opts) local response = client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr) if response.result then - vim.lsp.util.apply_text_edits(response.result, 0, client.offset_encoding) + lsp.util.apply_text_edits(response.result, 0, client.offset_encoding) return 0 end end @@ -2452,6 +2448,7 @@ function lsp.get_log_path() return log.get_filename() end +---@private --- Invokes a function for each LSP client attached to a buffer. --- ---@param bufnr integer Buffer number @@ -2463,6 +2460,7 @@ end --- print(vim.inspect(client)) --- end) --- +---@deprecated use lsp.get_active_clients({ bufnr = bufnr }) with regular loop function lsp.for_each_buffer_client(bufnr, fn) return for_each_buffer_client(bufnr, fn) end -- cgit From 904ad5458de095fec563fb9180fed2c4c87331e1 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 22 Jun 2023 19:38:49 +0200 Subject: vim-patch:9.0.1645: zserio files are not recognized (#24120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: zserio files are not recognized. Solution: Add a pattern for zserio files. (Dominique Pellé, closes vim/vim#12544) https://github.com/vim/vim/commit/2b994da57a0ac6ec0ec09fe3783f48ecd2bce610 Co-authored-by: =?UTF-8?q?Dominique=20Pell=C3=A9?= --- 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 fc8871f593..08a042d376 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1174,6 +1174,7 @@ local extension = { zir = 'zir', zu = 'zimbu', zut = 'zimbutempl', + zs = 'zserio', zsh = 'zsh', vala = 'vala', web = function(path, bufnr) -- cgit From 12c2c16acf7051d364d29cfd71f2542b0943d8e8 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 22 Jun 2023 19:39:57 +0200 Subject: feat(lsp): opt-in to dynamicRegistration for inlay hints (#24102) Since https://github.com/neovim/neovim/pull/23681 there is dynamic registration support. We should use that for new features unless there is a good reason to turn it off. --- runtime/lua/vim/lsp/protocol.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index b3a7903420..ea38bfe237 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -642,7 +642,7 @@ function protocol.make_client_capabilities() }, textDocument = { inlayHint = { - dynamicRegistration = false, + dynamicRegistration = true, resolveSupport = { properties = {}, }, -- cgit From 94a904b453e5fe5b2cb098828c093e34246d4125 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Fri, 23 Jun 2023 17:19:54 +0530 Subject: fix(lsp): reapplying already-applied hints #24114 Problem: The decoration provider clears the whole buffer then redraws all the hints every time the window was redrawn. This may lead to an infinite loop. Solution: Store the last applied version for a line and only clear and redraw the line if the buffer version has changed. --- runtime/lua/vim/lsp/_inlay_hint.lua | 54 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 25 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 8edf14e707..66fed4f350 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -8,6 +8,7 @@ local M = {} ---@field client_hint table> client_id -> (lnum -> hints) ---@field enabled boolean Whether inlay hints are enabled for the buffer ---@field timer uv.uv_timer_t? Debounce timer associated with the buffer +---@field applied table Last version of hints applied to this line ---@type table local bufstates = {} @@ -169,7 +170,7 @@ function M.enable(bufnr) bufnr = resolve_bufnr(bufnr) local bufstate = bufstates[bufnr] if not (bufstate and bufstate.enabled) then - bufstates[bufnr] = { enabled = true, timer = nil } + bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } M.refresh({ bufnr = bufnr }) api.nvim_buf_attach(bufnr, true, { on_lines = function(_, cb_bufnr) @@ -238,35 +239,38 @@ api.nvim_set_decoration_provider(namespace, { return end local hints_by_client = bufstate.client_hint - api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) for lnum = topline, botline do - 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 - local text = '' - if type(hint.label) == 'string' then - text = hint.label - else - for _, part in ipairs(hint.label) do - text = text .. part.value + 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 + local text = '' + if type(hint.label) == 'string' then + text = hint.label + else + for _, part in ipairs(hint.label) do + text = text .. part.value + end end + local vt = {} + if hint.paddingLeft then + vt[#vt + 1] = { ' ' } + end + vt[#vt + 1] = { text, 'LspInlayHint' } + if hint.paddingRight then + vt[#vt + 1] = { ' ' } + end + api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { + virt_text_pos = 'inline', + ephemeral = false, + virt_text = vt, + hl_mode = 'combine', + }) end - local vt = {} - if hint.paddingLeft then - vt[#vt + 1] = { ' ' } - end - vt[#vt + 1] = { text, 'LspInlayHint' } - if hint.paddingRight then - vt[#vt + 1] = { ' ' } - end - api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { - virt_text_pos = 'inline', - ephemeral = false, - virt_text = vt, - hl_mode = 'combine', - }) end + bufstate.applied[lnum] = bufstate.version end end end, -- cgit From 4dc86477b674d056b137a3afafd8824f7b7717ec Mon Sep 17 00:00:00 2001 From: Raphael Date: Fri, 23 Jun 2023 19:54:47 +0800 Subject: build(luarc.json): disable luadoc-miss-see-name #24108 --- runtime/lua/vim/lsp.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 970bb56478..2c115007de 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1183,7 +1183,7 @@ function lsp.start_client(config) --- ---@param code (integer) Error code ---@param err (...) Other arguments may be passed depending on the error kind - ---@see `vim.lsp.rpc.client_errors` for possible errors. Use + ---@see vim.lsp.rpc.client_errors for possible errors. Use ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. function dispatch.on_error(code, err) if log.error() then @@ -2366,7 +2366,7 @@ function lsp.formatexpr(opts) } local response = client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr) - if response.result then + if response and response.result then lsp.util.apply_text_edits(response.result, 0, client.offset_encoding) return 0 end -- cgit From fdf5013e218c55ca8f9bdb7cf5f16f8596330ea2 Mon Sep 17 00:00:00 2001 From: smjonas Date: Wed, 21 Jun 2023 07:12:53 +0200 Subject: fix(filetype): correctly detect bash-fc-{id} files as "sh" --- 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 fc8871f593..91d5dc1c62 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2188,7 +2188,7 @@ local pattern = { ['.*/etc/profile'] = function(path, bufnr) return require('vim.filetype.detect').sh(path, M.getlines(bufnr)) end, - ['bash%-fc[%-%.]'] = function(path, bufnr) + ['bash%-fc[%-%.].*'] = function(path, bufnr) return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash') end, ['%.tcshrc.*'] = function(path, bufnr) -- cgit From fa0a25dcb3a4fd2d6f03e09bcc7edac2f9ea8ded Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Sat, 24 Jun 2023 05:03:15 +0530 Subject: fix(lsp): error in reset_timer on second detach #24117 Problem: On running `zig fmt` manually, the on_lines callback and the server both detach (for some reason), and both of them call `clear()`. This fixes it, otherwise the second one to detach has an error in `reset_timer` since the bufstate doesn't exist Solution: * exit early in clear if `bufstates[bufnr]` is nil * set bufstatte.enabled to true on reload instead of making bufstate nil --- runtime/lua/vim/lsp/_inlay_hint.lua | 7 ++++++- 1 file changed, 6 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 66fed4f350..84794841ae 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -144,6 +144,9 @@ end ---@private local function clear(bufnr) bufnr = resolve_bufnr(bufnr) + if not bufstates[bufnr] then + return + end reset_timer(bufnr) local bufstate = bufstates[bufnr] local client_lens = (bufstate or {}).client_hint or {} @@ -184,7 +187,9 @@ function M.enable(bufnr) end, on_reload = function(_, cb_bufnr) clear(cb_bufnr) - bufstates[cb_bufnr] = nil + if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then + bufstates[cb_bufnr] = { enabled = true } + end M.refresh({ bufnr = cb_bufnr }) end, on_detach = function(_, cb_bufnr) -- cgit From cc624fac683ce6dbd01f10781817cd654cceed38 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 24 Jun 2023 20:04:30 +0200 Subject: vim-patch:9.0.1661: BUCK files are not recognized (#24142) Problem: BUCK files are not recognized. Solution: Recognize BUCK files as "bzl". (Son Luong Ngoc, closes vim/vim#12564) https://github.com/vim/vim/commit/b46e0f3263acd99c61df06ee3c4d1f6e0b471bc3 Co-authored-by: Son Luong Ngoc --- 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 33e80690ff..7bcc67313d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1372,6 +1372,7 @@ local filename = { ['named.root'] = 'bindzone', WORKSPACE = 'bzl', ['WORKSPACE.bzlmod'] = 'bzl', + BUCK = 'bzl', BUILD = 'bzl', ['cabal.project'] = 'cabalproject', ['cabal.config'] = 'cabalconfig', -- cgit From 036da0d07921e67090d1a62c9a4e382ca09d8584 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 24 Jun 2023 13:47:10 +0200 Subject: fix(docs): vimdoc syntax errors gen_help_html: truncate parse-error sample text --- runtime/lua/vim/iter.lua | 2 +- runtime/lua/vim/lsp/buf.lua | 6 +++--- runtime/lua/vim/lsp/util.lua | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 9c7bd13164..245a33625e 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -359,7 +359,7 @@ function ListIter.totable(self) return self._table end ---- Fold an iterator or table into a single value. +--- Fold ("reduce") an iterator or table into a single value. --- --- Examples: ---
lua
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 17b444a6e8..c2e0179cc4 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -122,7 +122,7 @@ end
 ---@private
 ---@param bufnr integer
 ---@param mode "v"|"V"
----@return table {start={row, col}, end={row, col}} using (1, 0) indexing
+---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
 local function range_from_selection(bufnr, mode)
   -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896
 
@@ -189,7 +189,7 @@ end
 ---         Restrict formatting to the client with name (client.name) matching this field.
 ---
 ---     - range (table|nil) Range to format.
----         Table must contain `start` and `end` keys with {row, col} tuples using
+---         Table must contain `start` and `end` keys with {row,col} tuples using
 ---         (1,0) indexing.
 ---         Defaults to current selection in visual mode
 ---         Defaults to `nil` in other modes, formatting the full buffer
@@ -741,7 +741,7 @@ end
 ---  - range: (table|nil)
 ---           Range for which code actions should be requested.
 ---           If in visual mode this defaults to the active selection.
----           Table must contain `start` and `end` keys with {row, col} tuples
+---           Table must contain `start` and `end` keys with {row,col} tuples
 ---           using mark-like indexing. See |api-indexing|
 ---
 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 538e48c805..4c319e7c41 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -178,8 +178,8 @@ local _str_byteindex_enc = M._str_byteindex_enc
 --- CAUTION: Changes in-place!
 ---
 ---@param lines (table) Original list of strings
----@param A (table) Start position; a 2-tuple of {line, col} numbers
----@param B (table) End position; a 2-tuple of {line, col} numbers
+---@param A (table) Start position; a 2-tuple of {line,col} numbers
+---@param B (table) End position; a 2-tuple of {line,col} numbers
 ---@param new_lines A list of strings to replace the original
 ---@returns (table) The modified {lines} object
 function M.set_lines(lines, A, B, new_lines)
@@ -2075,9 +2075,9 @@ end
 --- Using the given range in the current buffer, creates an object that
 --- is similar to |vim.lsp.util.make_range_params()|.
 ---
----@param start_pos integer[]|nil {row, col} mark-indexed position.
+---@param start_pos integer[]|nil {row,col} mark-indexed position.
 --- Defaults to the start of the last visual selection.
----@param end_pos integer[]|nil {row, col} mark-indexed position.
+---@param end_pos integer[]|nil {row,col} mark-indexed position.
 --- Defaults to the end of the last visual selection.
 ---@param bufnr integer|nil buffer handle or 0 for current, defaults to current
 ---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of `bufnr`
-- 
cgit 


From 49a7585981cdf7403e76a614558e602a98e64301 Mon Sep 17 00:00:00 2001
From: "Justin M. Keyes" 
Date: Fri, 23 Jun 2023 12:16:55 +0200
Subject: docs: autocmds, misc

---
 runtime/lua/vim/_editor.lua | 2 ++
 runtime/lua/vim/lsp.lua     | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index e2ed0d980e..ab20c36b17 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -184,6 +184,7 @@ end
 
 --- Gets a human-readable representation of the given object.
 ---
+---@see |vim.print()|
 ---@see https://github.com/kikito/inspect.lua
 ---@see https://github.com/mpeterv/vinspect
 local function inspect(object, options) -- luacheck: no unused
@@ -870,6 +871,7 @@ end
 --- 
--- --- @see |vim.inspect()| +--- @see |:=| --- @return any # given arguments. function vim.print(...) if vim.in_fast_event() then diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 970bb56478..a8e75c4dc9 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -2457,7 +2457,7 @@ end --- buffer number as arguments. Example: ---
lua
 ---               vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr)
----                 print(vim.inspect(client))
+---                 vim.print(client)
 ---               end)
 ---             
---@deprecated use lsp.get_active_clients({ bufnr = bufnr }) with regular loop -- cgit From 0ca2d11c1f473d9924c261c9dbd4e38730932bb4 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Mon, 26 Jun 2023 12:19:13 +0200 Subject: vim-patch:9.0.1632: not all cabal config files are recognized (#24025) Problem: Not all cabal config files are recognized. Solution: Add a couple of patterns. (Marcin Szamotulski, closes vim/vim#12463) https://github.com/vim/vim/commit/166cd7b801ebe4aa042a9bbd6007d1951800aaa9 Also: - Do not expand Lua patterns in environment variables used in file patterns. - Test $XDG_CONFIG_HOME on Windows, as it can be used by Nvim (and the runner sets it). Co-authored-by: Marcin Szamotulski --- runtime/lua/vim/filetype.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 7bcc67313d..fc94091622 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1809,6 +1809,8 @@ local pattern = { ['bzr_log%..*'] = 'bzr', ['.*enlightenment/.*%.cfg'] = 'c', ['${HOME}/cabal%.config'] = 'cabalconfig', + ['${HOME}/%.config/cabal/config'] = 'cabalconfig', + ['${XDG_CONFIG_HOME}/cabal/config'] = 'cabalconfig', ['cabal%.project%..*'] = starsetf('cabalproject'), ['.*/%.calendar/.*'] = starsetf('calendar'), ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'), @@ -2490,7 +2492,7 @@ local function match_pattern(name, path, tail, pat) return_early = true return nil end - return vim.env[env] + return vim.pesc(vim.env[env]) end) if return_early then return false -- cgit From 8ea9a70d05d6a7f9bab410101a87e6b184f14634 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 27 Jun 2023 10:14:34 +0300 Subject: vim-patch:9.0.1668: PEM files are not recognized (#24169) Problem: PEM files are not recognized. Solution: Add patterns to match PEM files. (closes vim/vim#12582) https://github.com/vim/vim/commit/0256d76a3392aef270b38d1cf7633008e45c2003 --- 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 fc94091622..bc880ed130 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -783,6 +783,10 @@ local extension = { g = 'pccts', pcmk = 'pcmk', pdf = 'pdf', + pem = 'pem', + cer = 'pem', + crt = 'pem', + csr = 'pem', plx = 'perl', prisma = 'prisma', psgi = 'perl', -- cgit From 929e4865d1f59cb356f3f2f8a10ad6095b435544 Mon Sep 17 00:00:00 2001 From: NAKAI Tsuyoshi <82267684+uga-rosa@users.noreply.github.com> Date: Wed, 28 Jun 2023 00:14:17 +0900 Subject: docs(diagnostic): return value of get() #24144 --- 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 0d1d01b391..093bfb631e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -767,7 +767,7 @@ end --- - namespace: (number) Limit diagnostics to the given namespace. --- - lnum: (number) Limit diagnostics to the given line number. --- - severity: See |diagnostic-severity|. ----@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|. +---@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present. function M.get(bufnr, opts) vim.validate({ bufnr = { bufnr, 'n', true }, -- cgit From c7e7f1d4b4b62c75bb54e652f25c6c6b8785a7f4 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Wed, 28 Jun 2023 03:05:09 +0900 Subject: fix(treesitter): make foldexpr work without highlighting (#24167) Problem: Treesitter fold is not updated if treesitter hightlight is not active. More precisely, updating folds requires `LanguageTree:parse()`. Solution: Call `parse()` before computing folds and compute folds when lines are added/removed. This doesn't guarantee correctness of the folds, because some changes that don't add/remove line won't update the folds even if they should (e.g. adding pair of braces). But it is good enough for most cases, while not introducing big overhead. Also, if highlighting is active, it is likely that `TSHighlighter._on_buf` already ran `parse()` (or vice versa). --- runtime/lua/vim/treesitter/_fold.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index a8f8c7967e..d308657237 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -162,9 +162,7 @@ local function get_folds_levels(bufnr, info, srow, erow) local parser = ts.get_parser(bufnr) - if not parser:is_valid() then - return - end + parser:parse() parser:for_each_tree(function(tree, ltree) local query = ts.query.get(ltree:lang(), 'folds') @@ -283,10 +281,12 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) local end_row_old = start_row + old_row local end_row_new = start_row + new_row - if new_row < old_row then - foldinfo:remove_range(end_row_new, end_row_old) - elseif new_row > old_row then - foldinfo:add_range(start_row, end_row_new) + if new_row ~= old_row then + if new_row < old_row then + foldinfo:remove_range(end_row_new, end_row_old) + else + foldinfo:add_range(start_row, end_row_new) + end schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) recompute_folds() -- cgit From e85e7fc7bcccfa3b8f9e52ec4d6cb21591b50468 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Wed, 28 Jun 2023 03:05:44 +0900 Subject: fix(treesitter): handle empty region when logging (#24173) --- runtime/lua/vim/treesitter/languagetree.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index cabfa8ccc0..bf6333aaa4 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -447,6 +447,9 @@ end ---@private ---@param region Range6[] local function region_tostr(region) + if #region == 0 then + return '[]' + end local srow, scol = region[1][1], region[1][2] local erow, ecol = region[#region][4], region[#region][5] return string.format('[%d:%d-%d:%d]', srow, scol, erow, ecol) -- cgit From 7968322e7a20b557631f4b496751658e80f6e7b0 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Thu, 29 Jun 2023 19:56:29 +0530 Subject: fix(lsp): inlay_hint nil reference error #24202 Problem: vim_lsp_inlayhint: Error executing lua: .../lsp/_inlay_hint.lua:249: attempt to index field 'applied' (a nil value) Solution: Assign {} to bufstates.applied in on_reload fixes #24172 --- 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 84794841ae..ccf1b5cca4 100644 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ b/runtime/lua/vim/lsp/_inlay_hint.lua @@ -188,7 +188,7 @@ function M.enable(bufnr) on_reload = function(_, cb_bufnr) clear(cb_bufnr) if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then - bufstates[cb_bufnr] = { enabled = true } + bufstates[cb_bufnr] = { enabled = true, applied = {} } end M.refresh({ bufnr = cb_bufnr }) end, -- cgit From 37079fca58f396fd866dc7b7d87a0100c17ee760 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 30 Jun 2023 11:33:28 +0200 Subject: feat(lsp): move inlay_hint() to vim.lsp (#24130) Allows to keep more functions hidden and gives a path forward for further inlay_hint related functions - like applying textEdits. See https://github.com/neovim/neovim/pull/23984#pullrequestreview-1486624668 --- runtime/lua/vim/lsp.lua | 7 + runtime/lua/vim/lsp/_inlay_hint.lua | 284 -------------------------------- runtime/lua/vim/lsp/buf.lua | 15 -- runtime/lua/vim/lsp/handlers.lua | 20 +-- runtime/lua/vim/lsp/inlay_hint.lua | 313 ++++++++++++++++++++++++++++++++++++ 5 files changed, 323 insertions(+), 316 deletions(-) delete mode 100644 runtime/lua/vim/lsp/_inlay_hint.lua create mode 100644 runtime/lua/vim/lsp/inlay_hint.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1e5ce8fa10..ca4851f8d7 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -2474,6 +2474,13 @@ function lsp.with(handler, override_config) end end +--- Enable/disable/toggle inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@param enable (boolean|nil) true/false to enable/disable, nil to toggle +function lsp.inlay_hint(bufnr, enable) + return require('vim.lsp.inlay_hint')(bufnr, enable) +end + --- Helper function to use when implementing a handler. --- This will check that all of the keys in the user configuration --- are valid keys and make sense to include for this handler. diff --git a/runtime/lua/vim/lsp/_inlay_hint.lua b/runtime/lua/vim/lsp/_inlay_hint.lua deleted file mode 100644 index ccf1b5cca4..0000000000 --- a/runtime/lua/vim/lsp/_inlay_hint.lua +++ /dev/null @@ -1,284 +0,0 @@ -local util = require('vim.lsp.util') -local log = require('vim.lsp.log') -local api = vim.api -local M = {} - ----@class lsp._inlay_hint.bufstate ----@field version integer ----@field client_hint table> client_id -> (lnum -> hints) ----@field enabled boolean Whether inlay hints are enabled for the buffer ----@field timer uv.uv_timer_t? Debounce timer associated with the buffer ----@field applied table Last version of hints applied to this line - ----@type table -local bufstates = {} - -local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') -local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) - ---- Reset the request debounce timer of a buffer ----@private -local function reset_timer(reset_bufnr) - local timer = bufstates[reset_bufnr].timer - if timer then - bufstates[reset_bufnr].timer = nil - if not timer:is_closing() then - timer:stop() - timer:close() - end - end -end - ---- |lsp-handler| for the method `textDocument/inlayHint` ---- Store hints for a specific buffer and client ----@private -function M.on_inlayhint(err, result, ctx, _) - if err then - local _ = log.error() and log.error('inlayhint', err) - return - end - local bufnr = ctx.bufnr - if util.buf_versions[bufnr] ~= ctx.version then - return - end - local client_id = ctx.client_id - if not result then - return - end - local bufstate = bufstates[bufnr] - if not (bufstate.client_hint and bufstate.version) then - bufstate.client_hint = vim.defaulttable() - bufstate.version = ctx.version - end - local hints_by_client = bufstate.client_hint - local client = vim.lsp.get_client_by_id(client_id) - - local new_hints_by_lnum = vim.defaulttable() - local num_unprocessed = #result - if num_unprocessed == 0 then - hints_by_client[client_id] = {} - bufstate.version = ctx.version - api.nvim__buf_redraw_range(bufnr, 0, -1) - return - end - - local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - ---@private - local function pos_to_byte(position) - local col = position.character - if col > 0 then - local line = lines[position.line + 1] or '' - local ok, convert_result - ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding) - if ok then - return convert_result - end - return math.min(#line, col) - end - return col - end - - 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) - end - - hints_by_client[client_id] = new_hints_by_lnum - bufstate.version = ctx.version - api.nvim__buf_redraw_range(bufnr, 0, -1) -end - ----@private -local function resolve_bufnr(bufnr) - return bufnr > 0 and bufnr or api.nvim_get_current_buf() -end - ---- Refresh inlay hints for a buffer ---- ----@param opts (nil|table) Optional arguments ---- - bufnr (integer, default: 0): Buffer whose hints to refresh ---- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer ---- ----@private -function M.refresh(opts) - opts = opts or {} - local bufnr = resolve_bufnr(opts.bufnr or 0) - local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.enabled) then - return - end - local only_visible = opts.only_visible or false - local buffer_windows = {} - for _, winid in ipairs(api.nvim_list_wins()) do - if api.nvim_win_get_buf(winid) == bufnr then - table.insert(buffer_windows, winid) - end - end - for _, window in ipairs(buffer_windows) do - local first = vim.fn.line('w0', window) - local last = vim.fn.line('w$', window) - local params = { - textDocument = util.make_text_document_params(bufnr), - range = { - start = { line = first - 1, character = 0 }, - ['end'] = { line = last, character = 0 }, - }, - } - vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) - end - if not only_visible then - local params = { - textDocument = util.make_text_document_params(bufnr), - range = { - start = { line = 0, character = 0 }, - ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, - }, - } - vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) - end -end - ---- Clear inlay hints ----@param bufnr (integer) Buffer handle, or 0 for current ----@private -local function clear(bufnr) - bufnr = resolve_bufnr(bufnr) - if not bufstates[bufnr] then - return - end - reset_timer(bufnr) - local bufstate = bufstates[bufnr] - local client_lens = (bufstate or {}).client_hint or {} - local client_ids = vim.tbl_keys(client_lens) - for _, iter_client_id in ipairs(client_ids) do - if bufstate then - bufstate.client_hint[iter_client_id] = {} - end - end - api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) - api.nvim__buf_redraw_range(bufnr, 0, -1) -end - ----@private -local function make_request(request_bufnr) - reset_timer(request_bufnr) - M.refresh({ bufnr = request_bufnr }) -end - ---- Enable inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current ----@private -function M.enable(bufnr) - bufnr = resolve_bufnr(bufnr) - local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.enabled) then - bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } - M.refresh({ bufnr = bufnr }) - api.nvim_buf_attach(bufnr, true, { - on_lines = function(_, cb_bufnr) - if not bufstates[cb_bufnr].enabled then - return true - end - reset_timer(cb_bufnr) - bufstates[cb_bufnr].timer = vim.defer_fn(function() - make_request(cb_bufnr) - end, 200) - end, - on_reload = function(_, cb_bufnr) - clear(cb_bufnr) - if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then - bufstates[cb_bufnr] = { enabled = true, applied = {} } - end - M.refresh({ bufnr = cb_bufnr }) - end, - on_detach = function(_, cb_bufnr) - clear(cb_bufnr) - bufstates[cb_bufnr] = nil - end, - }) - api.nvim_create_autocmd('LspDetach', { - buffer = bufnr, - callback = function(opts) - clear(opts.buf) - end, - once = true, - group = augroup, - }) - end -end - ---- Disable inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current ----@private -function M.disable(bufnr) - bufnr = resolve_bufnr(bufnr) - if bufstates[bufnr] and bufstates[bufnr].enabled then - clear(bufnr) - bufstates[bufnr].enabled = nil - bufstates[bufnr].timer = nil - end -end - ---- Toggle inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current ----@private -function M.toggle(bufnr) - bufnr = resolve_bufnr(bufnr) - local bufstate = bufstates[bufnr] - if bufstate and bufstate.enabled then - M.disable(bufnr) - else - M.enable(bufnr) - end -end - -api.nvim_set_decoration_provider(namespace, { - on_win = function(_, _, bufnr, topline, botline) - local bufstate = bufstates[bufnr] - if not bufstate then - return - end - - if bufstate.version ~= util.buf_versions[bufnr] then - return - end - local hints_by_client = bufstate.client_hint - - 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 - local text = '' - if type(hint.label) == 'string' then - text = hint.label - else - for _, part in ipairs(hint.label) do - text = text .. part.value - end - end - local vt = {} - if hint.paddingLeft then - vt[#vt + 1] = { ' ' } - end - vt[#vt + 1] = { text, 'LspInlayHint' } - if hint.paddingRight then - vt[#vt + 1] = { ' ' } - end - api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { - virt_text_pos = 'inline', - ephemeral = false, - virt_text = vt, - hl_mode = 'combine', - }) - end - end - bufstate.applied[lnum] = bufstate.version - end - end - end, -}) - -return M diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c2e0179cc4..0369725216 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -796,19 +796,4 @@ function M.execute_command(command_params) request('workspace/executeCommand', command_params) end ---- Enable/disable/toggle inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current ----@param enable (boolean|nil) true/false to enable/disable, nil to toggle -function M.inlay_hint(bufnr, enable) - vim.validate({ enable = { enable, { 'boolean', 'nil' } }, bufnr = { bufnr, 'number' } }) - local inlay_hint = require('vim.lsp._inlay_hint') - if enable then - inlay_hint.enable(bufnr) - elseif enable == false then - inlay_hint.disable(bufnr) - else - inlay_hint.toggle(bufnr) - end -end - return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 284e3ef2d0..625a2ed282 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -220,7 +220,7 @@ M['textDocument/codeLens'] = function(...) end M['textDocument/inlayHint'] = function(...) - return require('vim.lsp._inlay_hint').on_inlayhint(...) + return require('vim.lsp.inlay_hint').on_inlayhint(...) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references @@ -617,22 +617,8 @@ M['window/showDocument'] = function(_, result, ctx, _) end ---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_inlayHint_refresh -M['workspace/inlayHint/refresh'] = function(err, _, ctx) - local inlay_hint = require('vim.lsp._inlay_hint') - if err then - return vim.NIL - end - - 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 - inlay_hint.refresh({ bufnr = bufnr }) - break - end - end - end - - return vim.NIL +M['workspace/inlayHint/refresh'] = function(err, result, ctx, config) + return require('vim.lsp.inlay_hint').on_refresh(err, result, ctx, config) end -- Add boilerplate error validation and logging for all of these. diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua new file mode 100644 index 0000000000..1af18bbc61 --- /dev/null +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -0,0 +1,313 @@ +local util = require('vim.lsp.util') +local log = require('vim.lsp.log') +local api = vim.api +local M = {} + +---@class lsp._inlay_hint.bufstate +---@field version integer +---@field client_hint table> client_id -> (lnum -> hints) +---@field enabled boolean Whether inlay hints are enabled for the buffer +---@field timer uv.uv_timer_t? Debounce timer associated with the buffer +---@field applied table Last version of hints applied to this line + +---@type table +local bufstates = {} + +local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') +local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) + +--- Reset the request debounce timer of a buffer +---@private +local function reset_timer(reset_bufnr) + local timer = bufstates[reset_bufnr].timer + if timer then + bufstates[reset_bufnr].timer = nil + if not timer:is_closing() then + timer:stop() + timer:close() + end + end +end + +--- |lsp-handler| for the method `textDocument/inlayHint` +--- Store hints for a specific buffer and client +---@private +function M.on_inlayhint(err, result, ctx, _) + if err then + local _ = log.error() and log.error('inlayhint', err) + return + end + local bufnr = ctx.bufnr + if util.buf_versions[bufnr] ~= ctx.version then + return + end + local client_id = ctx.client_id + if not result then + return + end + local bufstate = bufstates[bufnr] + if not (bufstate.client_hint and bufstate.version) then + bufstate.client_hint = vim.defaulttable() + bufstate.version = ctx.version + end + local hints_by_client = bufstate.client_hint + local client = vim.lsp.get_client_by_id(client_id) + + local new_hints_by_lnum = vim.defaulttable() + local num_unprocessed = #result + if num_unprocessed == 0 then + hints_by_client[client_id] = {} + bufstate.version = ctx.version + api.nvim__buf_redraw_range(bufnr, 0, -1) + return + end + + local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) + ---@private + local function pos_to_byte(position) + local col = position.character + if col > 0 then + local line = lines[position.line + 1] or '' + local ok, convert_result + ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding) + if ok then + return convert_result + end + return math.min(#line, col) + end + return col + end + + 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) + end + + hints_by_client[client_id] = new_hints_by_lnum + bufstate.version = ctx.version + api.nvim__buf_redraw_range(bufnr, 0, -1) +end + +---@private +local function resolve_bufnr(bufnr) + return bufnr > 0 and bufnr or api.nvim_get_current_buf() +end + +--- Refresh inlay hints for a buffer +--- +---@param opts (nil|table) Optional arguments +--- - bufnr (integer, default: 0): Buffer whose hints to refresh +--- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer +--- +---@private +local function refresh(opts) + opts = opts or {} + local bufnr = resolve_bufnr(opts.bufnr or 0) + local bufstate = bufstates[bufnr] + if not (bufstate and bufstate.enabled) then + return + end + local only_visible = opts.only_visible or false + local buffer_windows = {} + for _, winid in ipairs(api.nvim_list_wins()) do + if api.nvim_win_get_buf(winid) == bufnr then + table.insert(buffer_windows, winid) + end + end + for _, window in ipairs(buffer_windows) do + local first = vim.fn.line('w0', window) + local last = vim.fn.line('w$', window) + local params = { + textDocument = util.make_text_document_params(bufnr), + range = { + start = { line = first - 1, character = 0 }, + ['end'] = { line = last, character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) + end + if not only_visible then + local params = { + textDocument = util.make_text_document_params(bufnr), + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) + end +end + +--- |lsp-handler| for the method `textDocument/inlayHint/refresh` +---@private +function M.on_refresh(err, _, ctx, _) + if err then + return vim.NIL + end + 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 + refresh({ bufnr = bufnr }) + break + end + end + end + + return vim.NIL +end + +--- Clear inlay hints +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +local function clear(bufnr) + bufnr = resolve_bufnr(bufnr) + if not bufstates[bufnr] then + return + end + reset_timer(bufnr) + local bufstate = bufstates[bufnr] + local client_lens = (bufstate or {}).client_hint or {} + local client_ids = vim.tbl_keys(client_lens) + for _, iter_client_id in ipairs(client_ids) do + if bufstate then + bufstate.client_hint[iter_client_id] = {} + end + end + api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1) + api.nvim__buf_redraw_range(bufnr, 0, -1) +end + +---@private +local function make_request(request_bufnr) + reset_timer(request_bufnr) + refresh({ bufnr = request_bufnr }) +end + +--- Enable inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +local function enable(bufnr) + bufnr = resolve_bufnr(bufnr) + local bufstate = bufstates[bufnr] + if not (bufstate and bufstate.enabled) then + bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } + refresh({ bufnr = bufnr }) + api.nvim_buf_attach(bufnr, true, { + on_lines = function(_, cb_bufnr) + if not bufstates[cb_bufnr].enabled then + return true + end + reset_timer(cb_bufnr) + bufstates[cb_bufnr].timer = vim.defer_fn(function() + make_request(cb_bufnr) + end, 200) + end, + on_reload = function(_, cb_bufnr) + clear(cb_bufnr) + if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then + bufstates[cb_bufnr] = { enabled = true, applied = {} } + end + refresh({ bufnr = cb_bufnr }) + end, + on_detach = function(_, cb_bufnr) + clear(cb_bufnr) + bufstates[cb_bufnr] = nil + end, + }) + api.nvim_create_autocmd('LspDetach', { + buffer = bufnr, + callback = function(opts) + clear(opts.buf) + end, + once = true, + group = augroup, + }) + end +end + +--- Disable inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +local function disable(bufnr) + bufnr = resolve_bufnr(bufnr) + if bufstates[bufnr] and bufstates[bufnr].enabled then + clear(bufnr) + bufstates[bufnr].enabled = nil + bufstates[bufnr].timer = nil + end +end + +--- Toggle inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +local function toggle(bufnr) + bufnr = resolve_bufnr(bufnr) + local bufstate = bufstates[bufnr] + if bufstate and bufstate.enabled then + M.disable(bufnr) + else + M.enable(bufnr) + end +end + +api.nvim_set_decoration_provider(namespace, { + on_win = function(_, _, bufnr, topline, botline) + local bufstate = bufstates[bufnr] + if not bufstate then + return + end + + if bufstate.version ~= util.buf_versions[bufnr] then + return + end + local hints_by_client = bufstate.client_hint + + 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 + local text = '' + if type(hint.label) == 'string' then + text = hint.label + else + for _, part in ipairs(hint.label) do + text = text .. part.value + end + end + local vt = {} + if hint.paddingLeft then + vt[#vt + 1] = { ' ' } + end + vt[#vt + 1] = { text, 'LspInlayHint' } + if hint.paddingRight then + vt[#vt + 1] = { ' ' } + end + api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { + virt_text_pos = 'inline', + ephemeral = false, + virt_text = vt, + hl_mode = 'combine', + }) + end + end + bufstate.applied[lnum] = bufstate.version + end + end + end, +}) + +return setmetatable(M, { + __call = function(_, bufnr, enable_) + vim.validate({ enable = { enable_, { 'boolean', 'nil' } }, bufnr = { bufnr, 'number' } }) + if enable_ then + enable(bufnr) + elseif enable_ == false then + disable(bufnr) + else + toggle(bufnr) + end + end, +}) -- cgit From d191bdf9d5e54722e28ddb0cccc8eb2312a234df Mon Sep 17 00:00:00 2001 From: Sanchayan Maity Date: Fri, 30 Jun 2023 17:12:58 +0530 Subject: fix(lsp): fix attempt to call non existent function (#24212) Commit 37079fc moved inlay_hint to vim.lsp() but in the process did missed converting a call to disable/enable which are now local. Fixes the below error when trying to toggle inlay hints. E5108: Error executing lua /usr/local/share/nvim/runtime/lua/vim/lsp/inlay_hint.lua:248: attempt to call field 'disable' (a nil value) stack traceback: /usr/local/share/nvim/runtime/lua/vim/lsp/inlay_hint.lua:248: in function 'toggle' /usr/local/share/nvim/runtime/lua/vim/lsp/inlay_hint.lua:310: in function 'inlay_hint' [string ":lua"]:1: in main chunk --- runtime/lua/vim/lsp/inlay_hint.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 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 1af18bbc61..f577a31696 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -245,9 +245,9 @@ local function toggle(bufnr) bufnr = resolve_bufnr(bufnr) local bufstate = bufstates[bufnr] if bufstate and bufstate.enabled then - M.disable(bufnr) + disable(bufnr) else - M.enable(bufnr) + enable(bufnr) end end -- cgit From 11844dde81c41bded54f2383b57f8eef406f2736 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 1 Jul 2023 11:08:06 +0200 Subject: feat(treesitter): bundle markdown parser and queries (#22481) * bundle split Markdown parser from https://github.com/MDeiml/tree-sitter-markdown * add queries from https://github.com/nvim-treesitter/nvim-treesitter/tree/main * upstream `#trim!` and `#inject-language!` directives Co-authored-by: dundargoc --- runtime/lua/vim/treesitter/query.lua | 60 +++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 73b561c777..7f90fa10e8 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -475,7 +475,6 @@ local directive_handlers = { metadata[capture_id].range = range end end, - -- Transform the content of the node -- Example: (#gsub! @_node ".*%.(.*)" "%1") ['gsub!'] = function(match, _, bufnr, pred, metadata) @@ -497,6 +496,65 @@ local directive_handlers = { metadata[id].text = text:gsub(pattern, replacement) end, + -- Trim blank lines from end of the node + -- Example: (#trim! @fold) + -- TODO(clason): generalize to arbitrary whitespace removal + ['trim!'] = function(match, _, bufnr, pred, metadata) + local node = match[pred[2]] + if not node then + return + end + + local start_row, start_col, end_row, end_col = node:range() + + -- Don't trim if region ends in middle of a line + if end_col ~= 0 then + return + end + + while true do + -- As we only care when end_col == 0, always inspect one line above end_row. + local end_line = vim.api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] + + if end_line ~= '' then + break + end + + end_row = end_row - 1 + end + + -- If this produces an invalid range, we just skip it. + if start_row < end_row or (start_row == end_row and start_col <= end_col) then + metadata.range = { start_row, start_col, end_row, end_col } + end + end, + -- Set injection language from node text, interpreted first as language and then as filetype + -- Example: (#inject-language! @_lang) + ['inject-language!'] = function(match, _, bufnr, pred, metadata) + local id = pred[2] + local node = match[id] + if not node then + return + end + + -- TODO(clason): replace by refactored `ts.has_parser` API + local has_parser = function(lang) + return vim._ts_has_language(lang) + or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0 + end + + local alias = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) + if not alias then + return + elseif has_parser(alias) then + metadata['injection.language'] = alias + else + local lang = vim.treesitter.language.get_lang(alias) + if lang and has_parser(lang) then + metadata['injection.language'] = lang + end + end + end, } --- Adds a new predicate to be used in queries -- cgit From ba8f19ebb67ca27d746f4b1cd902ab3d807eace3 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 1 Jul 2023 18:42:37 +0800 Subject: fix(lsp): lint warnings, default offset_encoding #24046 - fix lint / analysis warnings - locations_to_items(): get default offset_encoding from active client - character_offset(): get default offset_encoding from active client --- runtime/lua/vim/lsp/buf.lua | 6 +- runtime/lua/vim/lsp/codelens.lua | 4 +- runtime/lua/vim/lsp/log.lua | 2 +- runtime/lua/vim/lsp/protocol.lua | 11 +++- runtime/lua/vim/lsp/rpc.lua | 2 +- runtime/lua/vim/lsp/util.lua | 134 +++++++++++++++++++++------------------ 6 files changed, 85 insertions(+), 74 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 0369725216..c742505ec6 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -581,9 +581,9 @@ function M.document_highlight() end --- Removes document highlights from current buffer. ---- -function M.clear_references() - util.buf_clear_references() +--- @param bufnr integer|nil +function M.clear_references(bufnr) + util.buf_clear_references(bufnr or 0) end ---@private diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 5acfe90d5e..67104cc40b 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -33,11 +33,9 @@ local function execute_lens(lens, bufnr, client_id) local client = vim.lsp.get_client_by_id(client_id) assert(client, 'Client is required to execute lens, client_id=' .. client_id) - client._exec_cmd(lens.command, { bufnr = bufnr }, function(...) - local result = vim.lsp.handlers['workspace/executeCommand'](...) + vim.lsp.handlers['workspace/executeCommand'](...) M.refresh() - return result end) end diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 07d0500797..c77e7c045b 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -154,7 +154,7 @@ function log.set_level(level) end --- Gets the current log level. ----@return string current log level +---@return integer current log level function log.get_level() return current_log_level end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index ea38bfe237..27da891656 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -880,7 +880,7 @@ end --- Creates a normalized object describing LSP server capabilities. ---@param server_capabilities table Table of capabilities supported by the server ----@return table Normalized table of capabilities +---@return table|nil Normalized table of capabilities function protocol.resolve_capabilities(server_capabilities) local TextDocumentSyncKind = protocol.TextDocumentSyncKind local textDocumentSync = server_capabilities.textDocumentSync @@ -898,7 +898,8 @@ function protocol.resolve_capabilities(server_capabilities) elseif type(textDocumentSync) == 'number' then -- Backwards compatibility if not TextDocumentSyncKind[textDocumentSync] then - return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync' + vim.notify('Invalid server TextDocumentSyncKind for textDocumentSync', vim.log.levels.ERROR) + return nil end server_capabilities.textDocumentSync = { openClose = true, @@ -910,7 +911,11 @@ function protocol.resolve_capabilities(server_capabilities) }, } elseif type(textDocumentSync) ~= 'table' then - return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync)) + vim.notify( + string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync)), + vim.log.levels.ERROR + ) + return nil end return server_capabilities end diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 64bc732bdf..6552eaa800 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -36,7 +36,7 @@ end local function parse_headers(header) assert(type(header) == 'string', 'header must be a string') local headers = {} - for line in vim.gsplit(header, '\r\n', true) do + for line in vim.gsplit(header, '\r\n', { plain = true }) do if line == '' then break end diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4c319e7c41..6cb91417f2 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -25,9 +25,9 @@ local default_border = { ---@private --- Check the border given by opts or the default border for the additional --- size it adds to a float. ----@param opts (table, optional) options for the floating window +---@param opts table optional options for the floating window --- - border (string or table) the border ----@returns (table) size of border in the form of { height = height, width = width } +---@return table size of border in the form of { height = height, width = width } local function get_border_size(opts) local border = opts and opts.border or default_border local height = 0 @@ -106,7 +106,7 @@ end ---@private local function split_lines(value) value = string.gsub(value, '\r\n?', '\n') - return split(value, '\n', true) + return split(value, '\n', { plain = true }) end ---@private @@ -122,7 +122,7 @@ end --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed ---@param index integer|nil byte index (utf-8), or `nil` for length ----@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16 ---@return integer `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) if not encoding then @@ -150,7 +150,7 @@ end ---Alternative to vim.str_byteindex that takes an encoding. ---@param line string line to be indexed ---@param index integer UTF index ----@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ---@return integer byte (utf-8) index of `encoding` index `index` in `line` function M._str_byteindex_enc(line, index, encoding) if not encoding then @@ -180,8 +180,8 @@ local _str_byteindex_enc = M._str_byteindex_enc ---@param lines (table) Original list of strings ---@param A (table) Start position; a 2-tuple of {line,col} numbers ---@param B (table) End position; a 2-tuple of {line,col} numbers ----@param new_lines A list of strings to replace the original ----@returns (table) The modified {lines} object +---@param new_lines (table) list of strings to replace the original +---@return table The modified {lines} object function M.set_lines(lines, A, B, new_lines) -- 0-indexing to 1-indexing local i_0 = A[1] + 1 @@ -241,7 +241,7 @@ end --- ---@param bufnr integer bufnr to get the lines from ---@param rows integer[] zero-indexed line numbers ----@return table a table mapping rows to lines +---@return table|string a table mapping rows to lines local function get_lines(bufnr, rows) rows = type(rows) == 'table' and rows or { rows } @@ -331,8 +331,9 @@ end ---@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to ----@param offset_encoding string utf-8|utf-16|utf-32 +---@param offset_encoding string|nil utf-8|utf-16|utf-32 --- 1-indexed +---@return integer local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed -- Vim's line and columns are 1-indexed @@ -598,8 +599,8 @@ end --- Can be used to extract the completion items from a --- `textDocument/completion` request, which may return one of --- `CompletionItem[]`, `CompletionList` or null. ----@param result (table) The result of a `textDocument/completion` request ----@returns (table) List of completion items +---@param result table The result of a `textDocument/completion` request +---@return table List of completion items ---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion function M.extract_completion_items(result) if type(result) == 'table' and result.items then @@ -658,7 +659,7 @@ end --- Parses snippets in a completion entry. --- ---@param input string unparsed snippet ----@returns string parsed snippet +---@return string parsed snippet function M.parse_snippet(input) local ok, parsed = pcall(function() return tostring(snippet.parse(input)) @@ -718,7 +719,7 @@ end --- specification. --- ---@param completion_item_kind (`vim.lsp.protocol.completionItemKind`) ----@returns (`vim.lsp.protocol.completionItemKind`) +---@return (`vim.lsp.protocol.completionItemKind`) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion function M._get_completion_item_kind_name(completion_item_kind) return protocol.CompletionItemKind[completion_item_kind] or 'Unknown' @@ -727,12 +728,12 @@ end --- Turns the result of a `textDocument/completion` request into vim-compatible --- |complete-items|. --- ----@param result The result of a `textDocument/completion` call, e.g. from +---@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 ----@returns { matches = complete-items table, incomplete = bool } ----@see |complete-items| +---@return table { matches = complete-items table, incomplete = bool } +---@see complete-items function M.text_document_completion_list_to_complete_items(result, prefix) local items = M.extract_completion_items(result) if vim.tbl_isempty(items) then @@ -937,7 +938,7 @@ end --- ---@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`) ---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}. ----@returns {contents}, extended with lines of converted markdown. +---@return table {contents} extended with lines of converted markdown. ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} @@ -985,10 +986,11 @@ end --- Converts `textDocument/SignatureHelp` response to markdown lines. --- ----@param signature_help Response of `textDocument/SignatureHelp` ----@param ft optional filetype that will be use as the `lang` for the label markdown code block ----@param triggers optional list of trigger characters from the lsp server. used to better determine parameter offsets ----@returns list of lines of converted markdown. +---@param signature_help table Response of `textDocument/SignatureHelp` +---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block +---@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets +---@return table|nil table list of lines of converted markdown. +---@return table|nil table of active hl ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers) if not signature_help.signatures then @@ -1015,7 +1017,7 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers -- wrap inside a code block so stylize_markdown can render it properly label = ('```%s\n%s\n```'):format(ft, label) end - list_extend(contents, split(label, '\n', true)) + list_extend(contents, split(label, '\n', { plain = true })) if signature.documentation then M.convert_input_to_markdown_lines(signature.documentation, contents) end @@ -1087,16 +1089,16 @@ end --- Creates a table with sensible default options for a floating window. The --- table can be passed to |nvim_open_win()|. --- ----@param width (integer) window width (in character cells) ----@param height (integer) window height (in character cells) ----@param opts (table, optional) +---@param width integer window width (in character cells) +---@param height integer window height (in character cells) +---@param opts table optional --- - offset_x (integer) offset to add to `col` --- - offset_y (integer) offset to add to `row` --- - border (string or table) override `border` --- - focusable (string or table) override `focusable` --- - zindex (string or table) override `zindex`, defaults to 50 --- - relative ("mouse"|"cursor") defaults to "cursor" ----@returns (table) Options +---@return table Options function M.make_floating_popup_options(width, height, opts) validate({ opts = { opts, 't', true }, @@ -1160,7 +1162,7 @@ end --- Shows document and optionally jumps to the location. --- ---@param location table (`Location`|`LocationLink`) ----@param offset_encoding "utf-8" | "utf-16" | "utf-32" +---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param opts table|nil options --- - reuse_win (boolean) Jump to existing window if buffer is already open. --- - focus (boolean) Whether to focus/jump to location if possible. Defaults to true. @@ -1217,7 +1219,7 @@ end --- Jumps to a location. --- ---@param location table (`Location`|`LocationLink`) ----@param offset_encoding "utf-8" | "utf-16" | "utf-32" +---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param reuse_win boolean|nil Jump to existing window if buffer is already open. ---@return boolean `true` if the jump succeeded function M.jump_to_location(location, offset_encoding, reuse_win) @@ -1237,8 +1239,9 @@ end --- - for Location, range is shown (e.g., function definition) --- - for LocationLink, targetRange is shown (e.g., body of function definition) --- ----@param location a single `Location` or `LocationLink` ----@returns (bufnr,winnr) buffer and window number of floating window or nil +---@param location table a single `Location` or `LocationLink` +---@return integer|nil buffer id of float window +---@return integer|nil window id of float window function M.preview_location(location, opts) -- location may be LocationLink or Location (more useful for the former) local uri = location.targetUri or location.uri @@ -1275,10 +1278,10 @@ end --- Trims empty lines from input and pad top and bottom with empty lines --- ---@param contents table of lines to trim and pad ----@param opts dictionary with optional fields +---@param opts table with optional fields --- - pad_top number of lines to pad contents at top (default 0) --- - pad_bottom number of lines to pad contents at bottom (default 0) ----@return contents table of trimmed and padded lines +---@return table table of trimmed and padded lines function M._trim(contents, opts) validate({ contents = { contents, 't' }, @@ -1301,7 +1304,7 @@ end --- Generates a table mapping markdown code block lang to vim syntax, --- based on g:markdown_fenced_languages ----@return a table of lang -> syntax mappings +---@return table table of lang -> syntax mappings ---@private local function get_markdown_fences() local fences = {} @@ -1324,7 +1327,7 @@ end --- If you want to open a popup with fancy markdown, use `open_floating_preview` instead --- ---@param contents table of lines to show in window ----@param opts dictionary with optional fields +---@param opts table with optional fields --- - height of floating window --- - width of floating window --- - wrap_at character to wrap at for computing height @@ -1333,7 +1336,7 @@ end --- - pad_top number of lines to pad contents at top --- - pad_bottom number of lines to pad contents at bottom --- - separator insert separator after code block ----@returns width,height size of float +---@return table stripped content function M.stylize_markdown(bufnr, contents, opts) validate({ contents = { contents, 't' }, @@ -1480,10 +1483,10 @@ function M.stylize_markdown(bufnr, contents, opts) if not langs[lang] then -- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set pcall(api.nvim_buf_del_var, bufnr, 'current_syntax') - -- TODO(ashkan): better validation before this. - if not pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft)) then + if #api.nvim_get_runtime_file(('syntax/%s.vim'):format(ft), true) == 0 then return end + vim.cmd(string.format('syntax include %s syntax/%s.vim', lang, ft)) langs[lang] = true end vim.cmd( @@ -1542,7 +1545,7 @@ end ---@param events table list of events ---@param winnr integer window id of preview window ---@param bufnrs table list of buffers where the preview window will remain visible ----@see |autocmd-events| +---@see autocmd-events local function close_preview_autocmd(events, winnr, bufnrs) local augroup = api.nvim_create_augroup('preview_window_' .. winnr, { clear = true, @@ -1572,13 +1575,14 @@ end --- Computes size of float needed to show contents (with optional wrapping) --- ---@param contents table of lines to show in window ----@param opts dictionary with optional fields +---@param opts table with optional fields --- - height of floating window --- - width of floating window --- - wrap_at character to wrap at for computing height --- - max_width maximal width of floating window --- - max_height maximal height of floating window ----@returns width,height size of float +---@return integer width size of float +---@return integer height size of float function M._make_floating_popup_size(contents, opts) validate({ contents = { contents, 't' }, @@ -1662,7 +1666,8 @@ end --- - focus: (boolean, default true) If `true`, and if {focusable} --- is also `true`, focus an existing floating window with the same --- {focus_id} ----@returns bufnr,winnr buffer and window number of the newly created floating +---@return integer bufnr of newly created float window +---@return integer winid of newly created float window ---preview window function M.open_floating_preview(contents, syntax, opts) validate({ @@ -1766,7 +1771,7 @@ do --[[ References ]] --- ---@param bufnr integer Buffer id function M.buf_clear_references(bufnr) - validate({ bufnr = { bufnr, 'n', true } }) + validate({ bufnr = { bufnr, { 'n', 'nil' }, true } }) api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1) end @@ -1828,13 +1833,15 @@ end) --- ---@param locations table list of `Location`s or `LocationLink`s ---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32 ----@returns (table) list of items +--- default to first client of buffer +---@return table list of items function M.locations_to_items(locations, offset_encoding) if offset_encoding == nil then vim.notify_once( 'locations_to_items must be called with valid offset encoding', vim.log.levels.WARN ) + offset_encoding = vim.lsp.get_active_clients({ bufnr = 0 })[1].offset_encoding end local items = {} @@ -1896,7 +1903,7 @@ end --- Converts symbols to quickfix list items. --- ----@param symbols DocumentSymbol[] or SymbolInformation[] +---@param symbols table DocumentSymbol[] or SymbolInformation[] function M.symbols_to_items(symbols, bufnr) ---@private local function _symbols_to_items(_symbols, _items, _bufnr) @@ -1936,8 +1943,8 @@ function M.symbols_to_items(symbols, bufnr) end --- Removes empty lines from the beginning and end. ----@param lines (table) list of lines to trim ----@returns (table) trimmed list of lines +---@param lines table list of lines to trim +---@return table trimmed list of lines function M.trim_empty_lines(lines) local start = 1 for i = 1, #lines do @@ -1961,8 +1968,8 @@ end --- --- CAUTION: Modifies the input in-place! --- ----@param lines (table) list of lines ----@returns (string) filetype or "markdown" if it was unchanged. +---@param lines table list of lines +---@return string filetype or "markdown" if it was unchanged. function M.try_trim_markdown_code_blocks(lines) local language_id = lines[1]:match('^```(.*)') if language_id then @@ -2007,7 +2014,7 @@ end --- ---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ----@returns `TextDocumentPositionParams` object +---@return table `TextDocumentPositionParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params(window, offset_encoding) window = window or 0 @@ -2021,7 +2028,7 @@ end --- Utility function for getting the encoding of the first LSP client on the given buffer. ---@param bufnr (integer) buffer handle or 0 for current, defaults to current ----@returns (string) encoding first client if there is one, nil otherwise +---@return string encoding first client if there is one, nil otherwise function M._get_offset_encoding(bufnr) validate({ bufnr = { bufnr, 'n', true }, @@ -2060,7 +2067,7 @@ end --- ---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of buffer of `window` ----@returns { textDocument = { uri = `current_file_uri` }, range = { start = +---@return table { textDocument = { uri = `current_file_uri` }, range = { start = ---`current_position`, end = `current_position` } } function M.make_range_params(window, offset_encoding) local buf = api.nvim_win_get_buf(window or 0) @@ -2081,7 +2088,7 @@ end --- Defaults to the end of the last visual selection. ---@param bufnr integer|nil buffer handle or 0 for current, defaults to current ---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of `bufnr` ----@returns { textDocument = { uri = `current_file_uri` }, range = { start = +---@return table { textDocument = { uri = `current_file_uri` }, range = { start = ---`start_position`, end = `end_position` } } function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) validate({ @@ -2121,15 +2128,15 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- ---@param bufnr integer|nil: Buffer handle, defaults to current ----@returns `TextDocumentIdentifier` +---@return table `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params ----@param added ----@param removed +---@param added table +---@param removed table function M.make_workspace_params(added, removed) return { event = { added = added, removed = removed } } end @@ -2137,7 +2144,7 @@ end --- ---@see 'shiftwidth' ---@param bufnr (integer|nil): Buffer handle, defaults to current ----@returns (integer) indentation size +---@return (integer) indentation size function M.get_effective_tabstop(bufnr) validate({ bufnr = { bufnr, 'n', true } }) local bo = bufnr and vim.bo[bufnr] or vim.bo @@ -2148,7 +2155,7 @@ end --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position. --- ---@param options table|nil with valid `FormattingOptions` entries ----@returns `DocumentFormattingParams` object +---@return `DocumentFormattingParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting function M.make_formatting_params(options) validate({ options = { options, 't', true } }) @@ -2167,8 +2174,8 @@ end ---@param buf integer buffer number (0 for current) ---@param row 0-indexed line ---@param col 0-indexed byte offset in line ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `buf` ----@returns (integer, integer) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} +---@param offset_encoding string utf-8|utf-16|utf-32 defaults to `offset_encoding` of first client of `buf` +---@return integer `offset_encoding` index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col, offset_encoding) local line = get_line(buf, row) if offset_encoding == nil then @@ -2176,6 +2183,7 @@ function M.character_offset(buf, row, col, offset_encoding) 'character_offset must be called with valid offset encoding', vim.log.levels.WARN ) + offset_encoding = vim.lsp.get_active_clients({ bufnr = buf })[1].offset_encoding end -- If the col is past the EOL, use the line length. if col > #line then @@ -2186,11 +2194,11 @@ end --- Helper function to return nested values in language server settings --- ----@param settings a table of language server settings ----@param section a string indicating the field of the settings table ----@returns (table or string) The value of settings accessed via section +---@param settings table language server settings +---@param section string indicating the field of the settings table +---@return table|string The value of settings accessed via section function M.lookup_section(settings, section) - for part in vim.gsplit(section, '.', true) do + for part in vim.gsplit(section, '.', { plain = true }) do settings = settings[part] if settings == nil then return vim.NIL -- cgit From 4fd852b8cb88ed035203d3f9ae2e6a8258244974 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Mon, 3 Jul 2023 00:44:21 +0900 Subject: perf(treesitter): cache fold query (#24222) perf(treesitter): cache vim.treesitter.query.get Problem: vim.treesitter.query.get searches and reads query files every time it's called, if user hasn't overridden the query. So this can incur slowdown when called frequently. This can happen when using treesitter foldexpr. For example, when using `:h :range!` in markdown file to format fenced codeblock, on_changedtree in _fold.lua is triggered many times despite that the tree doesn't have syntactic changes (might be a bug in LanguageTree). (Incidentally, the resulting fold is incorrect due to a bug in `:h range!`.) on_changedtree calls vim.treesitter.query.get for each tree changes. In addition, it may request folds queries for injected languages without fold queries, such as markdown_inline. Solution: * Cache the result of vim.treesitter.query.get. * If query file was not found, fail quickly at later calls. --- runtime/lua/vim/treesitter/query.lua | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7f90fa10e8..7610ef7b7f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -195,6 +195,12 @@ function M.set(lang, query_name, text) explicit_queries[lang][query_name] = M.parse(lang, text) end +--- `false` if query files didn't exist or were empty +---@type table> +local query_get_cache = vim.defaulttable(function() + return setmetatable({}, { __mode = 'v' }) +end) + ---@deprecated function M.get_query(...) vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10') @@ -212,16 +218,28 @@ function M.get(lang, query_name) return explicit_queries[lang][query_name] end + local cached = query_get_cache[lang][query_name] + if cached then + return cached + elseif cached == false then + return nil + end + local query_files = M.get_files(lang, query_name) local query_string = read_query_files(query_files) - if #query_string > 0 then - return M.parse(lang, query_string) + if #query_string == 0 then + query_get_cache[lang][query_name] = false + return nil end + + local query = M.parse(lang, query_string) + query_get_cache[lang][query_name] = query + return query end ----@type {[string]: {[string]: Query}} -local query_cache = vim.defaulttable(function() +---@type table> +local query_parse_cache = vim.defaulttable(function() return setmetatable({}, { __mode = 'v' }) end) @@ -250,7 +268,7 @@ end ---@return Query Parsed query function M.parse(lang, query) language.add(lang) - local cached = query_cache[lang][query] + local cached = query_parse_cache[lang][query] if cached then return cached end @@ -259,7 +277,7 @@ function M.parse(lang, query) self.query = vim._ts_parse_query(lang, query) self.info = self.query:inspect() self.captures = self.info.captures - query_cache[lang][query] = self + query_parse_cache[lang][query] = self return self end -- cgit From cf5f1492d702f940934b0b40024d1741e4474542 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 4 Jul 2023 20:30:31 +0800 Subject: fix(lsp): revert change to buf.clear_references() #24238 Problem: in #24046 the signature of buf.clear_references() changed, which indirectly breaks callers that were passing "ignored" args. Solution: because util.buf_clear_references() already defaulted to "current buffer", the change to buf.clear_references() isn't actually needed, so just revert it. --- runtime/lua/vim/lsp/buf.lua | 5 ++--- runtime/lua/vim/lsp/util.lua | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index c742505ec6..bac66b671d 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -581,9 +581,8 @@ function M.document_highlight() end --- Removes document highlights from current buffer. ---- @param bufnr integer|nil -function M.clear_references(bufnr) - util.buf_clear_references(bufnr or 0) +function M.clear_references() + util.buf_clear_references() end ---@private diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 6cb91417f2..8d6f88bb2c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1769,9 +1769,9 @@ do --[[ References ]] --- Removes document highlights from a buffer. --- - ---@param bufnr integer Buffer id + ---@param bufnr integer|nil Buffer id function M.buf_clear_references(bufnr) - validate({ bufnr = { bufnr, { 'n', 'nil' }, true } }) + validate({ bufnr = { bufnr, { 'n' }, true } }) api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1) end -- cgit From af6e6ccf3dee815850639ec5613dda3442caa7d6 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Sun, 30 Apr 2023 15:53:02 +1000 Subject: feat(vim.ui): vim.ui.open, "gx" without netrw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mathias Fußenegger Co-authored-by: Justin M. Keyes Co-authored-by: ii14 <59243201+ii14@users.noreply.github.com> --- runtime/lua/vim/lsp/handlers.lua | 15 +++------- runtime/lua/vim/ui.lua | 65 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 11 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 625a2ed282..9b102c0f84 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -573,22 +573,15 @@ M['window/showDocument'] = function(_, result, ctx, _) if result.external then -- TODO(lvimuser): ask the user for confirmation - local cmd - if vim.fn.has('win32') == 1 then - cmd = { 'cmd.exe', '/c', 'start', '""', uri } - elseif vim.fn.has('macunix') == 1 then - cmd = { 'open', uri } - else - cmd = { 'xdg-open', uri } - end - local ret = vim.fn.system(cmd) - if vim.v.shell_error ~= 0 then + local ret = vim.ui.open(uri) + + if ret.code ~= 0 or ret == nil then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret, + message = ret and ret.stderr or 'No handler could be found', }, } end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index aaee175f3a..2200ee7bc3 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -104,4 +104,69 @@ function M.input(opts, on_confirm) end end +--- Opens a path in the system's default handler. +--- This function utilizes `xdg-open`, `wslview`, `explorer`, or `open` commands +--- depending on the system to open the provided path. +--- +--- Notifies the user if unsuccessful +--- +---@param path string Path to be opened +--- +---@return SystemCompleted|nil result Result of command, if an appropriate one +---could be found. +--- +---@see |vim.system| +--- +--- Example: +---
lua
+--- vim.ui.open("https://neovim.io/")
+---
+--- vim.ui.open("/path/to/file")
+--- 
+function M.open(path) + if not path or path == '' then + vim.notify('os_open: No path provided', vim.log.levels.ERROR) + return nil + end + + local cmd + + if vim.fn.has('macunix') == 1 then + cmd = { 'open', path } + elseif vim.fn.has('win32') == 1 then + cmd = { 'explorer', path } + else + if vim.fn.executable('wslview') == 1 then + cmd = { 'wslview', path } + elseif vim.fn.executable('xdg-open') == 1 then + cmd = { 'xdg-open', path } + else + vim.notify( + 'os_open: Could not find an appropriate command to use (Is xdg-open installed?)', + vim.log.levels.ERROR + ) + + return nil + end + end + + local ret = vim + .system(cmd, { + text = true, + detach = true, + }) + :wait() + + if ret.code ~= 0 then + local msg = { + 'Failed to open path', + ret, + vim.inspect(cmd), + } + vim.notify(table.concat(msg, '\n'), vim.log.levels.ERROR) + end + + return ret +end + return M -- cgit From 67b2ed1004ae551c9fe1bbd29a86b5a301570800 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 2 Jul 2023 16:51:30 +0200 Subject: fix(gx): visual selection, expand env vars --- Rejected experiment: move vim.ui.open() to vim.env.open() Problem: `vim.ui` is where user-interface "providers" live, which can be overridden. It would also be useful to have a "providers" namespace for platform-specific features such as "open", clipboard, python, and the other providers listed in `:help providers`. We could overload `vim.ui` to serve that purpose as the single "providers" namespace, but `vim.ui.nodejs()` for example seems awkward. Solution: `vim.env` currently has too narrow of a purpose. Overload it to also be a namespace for `vim.env.open`. diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 913f1fe20348..17d05ff37595 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -37,8 +37,28 @@ local options_info = setmetatable({}, { end, }) -vim.env = setmetatable({}, { - __index = function(_, k) +vim.env = setmetatable({ + open = setmetatable({}, { + __call = function(_, uri) + print('xxxxx'..uri) + return true + end, + __tostring = function() + local v = vim.fn.getenv('open') + if v == vim.NIL then + return nil + end + return v + end, + }) + }, + { + __index = function(t, k, ...) + if k == 'open' then + error() + -- vim.print({...}) + -- return rawget(t, k) + end local v = vim.fn.getenv(k) if v == vim.NIL then return nil --- runtime/lua/vim/lsp/handlers.lua | 6 ++-- runtime/lua/vim/ui.lua | 75 ++++++++++++++++------------------------ 2 files changed, 33 insertions(+), 48 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 9b102c0f84..79d3f7aab0 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -576,12 +576,12 @@ M['window/showDocument'] = function(_, result, ctx, _) local ret = vim.ui.open(uri) - if ret.code ~= 0 or ret == nil then + if ret == nil or ret.code ~= 0 then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret and ret.stderr or 'No handler could be found', + message = ret and ret.stderr or 'No handler found', }, } end @@ -593,7 +593,7 @@ M['window/showDocument'] = function(_, result, ctx, _) local client = vim.lsp.get_client_by_id(client_id) local client_name = client and client.name or string.format('id=%d', client_id) if not client then - err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method }) + err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method) return vim.NIL end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 2200ee7bc3..3ffa329f74 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -104,69 +104,54 @@ function M.input(opts, on_confirm) end end ---- Opens a path in the system's default handler. ---- This function utilizes `xdg-open`, `wslview`, `explorer`, or `open` commands ---- depending on the system to open the provided path. +--- Opens `path` with the system default handler (macOS `open`, Windows `explorer.exe`, Linux +--- `xdg-open`, …), or shows a message on failure. --- ---- Notifies the user if unsuccessful +--- Expands "~/" and environment variables in filesystem paths. --- ----@param path string Path to be opened ---- ----@return SystemCompleted|nil result Result of command, if an appropriate one ----could be found. ---- ----@see |vim.system| ---- ---- Example: +--- Examples: ---
lua
 --- vim.ui.open("https://neovim.io/")
----
---- vim.ui.open("/path/to/file")
+--- vim.ui.open("~/path/to/file")
+--- vim.ui.open("$VIMRUNTIME")
 --- 
+--- +---@param path string Path or URL to open +--- +---@return SystemCompleted|nil result Command result, or nil if not found. +--- +---@see |vim.system()| function M.open(path) - if not path or path == '' then - vim.notify('os_open: No path provided', vim.log.levels.ERROR) - return nil + vim.validate{ + path={path, 'string'} + } + local is_uri = path:match('%w+:') + if not is_uri then + path = vim.fn.expand(path) end local cmd - if vim.fn.has('macunix') == 1 then + if vim.fn.has('mac') == 1 then cmd = { 'open', path } elseif vim.fn.has('win32') == 1 then cmd = { 'explorer', path } + elseif vim.fn.executable('wslview') == 1 then + cmd = { 'wslview', path } + elseif vim.fn.executable('xdg-open') == 1 then + cmd = { 'xdg-open', path } else - if vim.fn.executable('wslview') == 1 then - cmd = { 'wslview', path } - elseif vim.fn.executable('xdg-open') == 1 then - cmd = { 'xdg-open', path } - else - vim.notify( - 'os_open: Could not find an appropriate command to use (Is xdg-open installed?)', - vim.log.levels.ERROR - ) - - return nil - end + vim.notify('vim.ui.open: no handler found (tried: wslview, xdg-open)', vim.log.levels.ERROR) + return nil end - local ret = vim - .system(cmd, { - text = true, - detach = true, - }) - :wait() - - if ret.code ~= 0 then - local msg = { - 'Failed to open path', - ret, - vim.inspect(cmd), - } - vim.notify(table.concat(msg, '\n'), vim.log.levels.ERROR) + 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)) + vim.notify(msg, vim.log.levels.ERROR) end - return ret + return rv end return M -- cgit From e644e7ce0b36dd5e75770f3faa0a84f15e2561e8 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 4 Jul 2023 23:33:23 +0200 Subject: fix(vim.ui.open): return (don't show) error message Problem: Showing an error via vim.notify() makes it awkward for callers such as lsp/handlers.lua to avoid showing redundant errors. Solution: Return the message instead of showing it. Let the caller decide whether and when to show the message. --- runtime/lua/vim/lsp/handlers.lua | 5 ++--- runtime/lua/vim/ui.lua | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 79d3f7aab0..70781cb7a6 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -573,15 +573,14 @@ M['window/showDocument'] = function(_, result, ctx, _) if result.external then -- TODO(lvimuser): ask the user for confirmation - - local ret = vim.ui.open(uri) + local ret, err = vim.ui.open(uri) if ret == nil or ret.code ~= 0 then return { success = false, error = { code = protocol.ErrorCodes.UnknownErrorCode, - message = ret and ret.stderr or 'No handler found', + message = ret and ret.stderr or err, }, } end diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 3ffa329f74..fd06611da2 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -105,7 +105,7 @@ function M.input(opts, on_confirm) end --- Opens `path` with the system default handler (macOS `open`, Windows `explorer.exe`, Linux ---- `xdg-open`, …), or shows a message on failure. +--- `xdg-open`, …), or returns (but does not show) an error message on failure. --- --- Expands "~/" and environment variables in filesystem paths. --- @@ -118,13 +118,14 @@ end --- ---@param path string Path or URL to open --- ----@return SystemCompleted|nil result Command result, or nil if not found. +---@return SystemCompleted|nil # Command result, or nil if not found. +---@return string|nil # Error message on failure --- ---@see |vim.system()| function M.open(path) - vim.validate{ - path={path, 'string'} - } + vim.validate({ + path = { path, 'string' }, + }) local is_uri = path:match('%w+:') if not is_uri then path = vim.fn.expand(path) @@ -141,17 +142,16 @@ function M.open(path) elseif vim.fn.executable('xdg-open') == 1 then cmd = { 'xdg-open', path } else - vim.notify('vim.ui.open: no handler found (tried: wslview, xdg-open)', vim.log.levels.ERROR) - return nil + return nil, 'vim.ui.open: no handler found (tried: wslview, xdg-open)' end - local rv = vim.system(cmd, { text = true, detach = true, }):wait() + 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)) - vim.notify(msg, vim.log.levels.ERROR) + return rv, msg end - return rv + return rv, nil end return M -- cgit From 2afb04758c341e17c70b8d2e3869c901c8cdb7d2 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 6 Jul 2023 12:56:19 +0800 Subject: fix(vim.system): close check handle (#24270) Fix hang after running vim.system() with sanitizers. --- runtime/lua/vim/_system.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua index eadf801a31..ff566866c0 100644 --- a/runtime/lua/vim/_system.lua +++ b/runtime/lua/vim/_system.lua @@ -287,6 +287,7 @@ function M.run(cmd, opts, on_exit) end end check:stop() + check:close() state.done = true state.result = { -- cgit From abd380e28d48dd155b1e29cd2453f13b28bf7e08 Mon Sep 17 00:00:00 2001 From: Steven Ward Date: Mon, 5 Jun 2023 22:05:51 -0500 Subject: fix(defaults): visual mode star (*,#) is fragile Problem: Visual mode "*", "#" mappings don't work on text with "/", "\", "?", and newlines. Solution: Get the visual selection and escape it as a search pattern. Add functions vim.get_visual_selection and _search_for_visual_selection. Fix #21676 --- runtime/lua/vim/_editor.lua | 84 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 6 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index ab20c36b17..e00d512a2a 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -435,7 +435,7 @@ vim.cmd = setmetatable({}, { do local validate = vim.validate - --@private + ---@private local function make_dict_accessor(scope, handle) validate({ scope = { scope, 's' }, @@ -535,6 +535,55 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) return region end +--- Gets the content of the visual selection. +--- +--- The result is either a string or, if {list} is `true`, a list of strings. +--- If not in any |visual-mode|, `nil` is returned. +--- +--- @param list boolean|nil +--- Return a list of strings instead of a string. See |getreg()|. +--- Defaults to `false`. +--- @param append_empty boolean|nil +--- Append an empty string to the result when in |linewise-visual| mode and {list} is `true`. +--- This will preserve the trailing newline of the selection when the result is concatenated with `"\n"`. +--- Defaults to `false`. +--- @return string|table +function vim.get_visual_selection(list, append_empty) + list = list or false + append_empty = append_empty or false + + local mode = vim.api.nvim_get_mode().mode + if mode ~= 'v' and mode ~= 'V' and mode:byte() ~= 22 then + return nil + end + + local reg_name_unnamed = '"' + local reg_name_yank = '0' + + local reg_info_unnamed = vim.fn.getreginfo(reg_name_unnamed) + local reg_info_yank = vim.fn.getreginfo(reg_name_yank) + local opt_clipboard = vim.o.clipboard + local opt_report = vim.o.report + + vim.o.clipboard = '' + vim.o.report = vim.v.maxcol + + vim.api.nvim_feedkeys('y', 'nx', false) + + local yanked = vim.fn.getreg(reg_name_yank, 1, list) + + vim.fn.setreg(reg_name_unnamed, reg_info_unnamed) + vim.fn.setreg(reg_name_yank, reg_info_yank) + vim.o.clipboard = opt_clipboard + vim.o.report = opt_report + + if list and append_empty and mode == 'V' then + table.insert(yanked, '') + end + + return yanked +end + --- Defers calling {fn} until {timeout} ms passes. --- --- Use to do a one-shot timer that calls {fn} @@ -985,7 +1034,7 @@ end --- Defaults to "Nvim". ---@param backtrace boolean|nil Prints backtrace. Defaults to true. --- ----@returns Deprecated message, or nil if no message was shown. +---@return Deprecated message, or nil if no message was shown. function vim.deprecate(name, alternative, version, plugin, backtrace) local msg = ('%s is deprecated'):format(name) plugin = plugin or 'Nvim' @@ -1010,9 +1059,28 @@ end function vim._init_default_mappings() -- mappings - --@private + ---@private + local function _search_for_visual_selection(search_prefix) + if search_prefix ~= '/' and search_prefix ~= '?' then + return + end + -- Escape these characters + local replacements = { + [search_prefix] = [[\]] .. search_prefix, + [ [[\]] ] = [[\\]], + ['\t'] = [[\t]], + ['\n'] = [[\n]], + } + local pattern = '[' .. table.concat(vim.tbl_keys(replacements), '') .. ']' + local visual_selection = vim.get_visual_selection(false) + local escaped_visual_selection = string.gsub(visual_selection, pattern, replacements) + local search_cmd = search_prefix .. [[\V]] .. escaped_visual_selection .. '\n' + vim.api.nvim_feedkeys(search_cmd, 'nx', true) + end + + ---@private local function map(mode, lhs, rhs) - vim.api.nvim_set_keymap(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' }) + vim.keymap.set(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' }) end map('n', 'Y', 'y$') @@ -1020,8 +1088,12 @@ function vim._init_default_mappings() map('n', '', 'nohlsearchdiffupdatenormal! ') map('i', '', 'u') map('i', '', 'u') - map('x', '*', 'y/\\V"') - map('x', '#', 'y?\\V"') + map('x', '*', function() + _search_for_visual_selection('/') + end) + map('x', '#', function() + _search_for_visual_selection('?') + end) -- Use : instead of so that ranges are supported. #19365 map('n', '&', ':&&') -- cgit From f39ca5df232cb6e70b1932b7444840acf42cf87b Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Wed, 5 Jul 2023 19:47:43 +0200 Subject: refactor(defaults): use vim.region for visual star (*,#) Problem: The parent commit added a new vim.get_visual_selection() function to improve visual star. But that is redundant with vim.region(). Any current limitations of vim.region() should be fixed instead of adding a new function. Solution: Delete vim.get_visual_selection(). Use vim.region() to get the visual selection. TODO: fails with visual "block" selections. --- runtime/lua/vim/_editor.lua | 107 ++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 74 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index e00d512a2a..6f701e9ae9 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -535,55 +535,6 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive) return region end ---- Gets the content of the visual selection. ---- ---- The result is either a string or, if {list} is `true`, a list of strings. ---- If not in any |visual-mode|, `nil` is returned. ---- ---- @param list boolean|nil ---- Return a list of strings instead of a string. See |getreg()|. ---- Defaults to `false`. ---- @param append_empty boolean|nil ---- Append an empty string to the result when in |linewise-visual| mode and {list} is `true`. ---- This will preserve the trailing newline of the selection when the result is concatenated with `"\n"`. ---- Defaults to `false`. ---- @return string|table -function vim.get_visual_selection(list, append_empty) - list = list or false - append_empty = append_empty or false - - local mode = vim.api.nvim_get_mode().mode - if mode ~= 'v' and mode ~= 'V' and mode:byte() ~= 22 then - return nil - end - - local reg_name_unnamed = '"' - local reg_name_yank = '0' - - local reg_info_unnamed = vim.fn.getreginfo(reg_name_unnamed) - local reg_info_yank = vim.fn.getreginfo(reg_name_yank) - local opt_clipboard = vim.o.clipboard - local opt_report = vim.o.report - - vim.o.clipboard = '' - vim.o.report = vim.v.maxcol - - vim.api.nvim_feedkeys('y', 'nx', false) - - local yanked = vim.fn.getreg(reg_name_yank, 1, list) - - vim.fn.setreg(reg_name_unnamed, reg_info_unnamed) - vim.fn.setreg(reg_name_yank, reg_info_yank) - vim.o.clipboard = opt_clipboard - vim.o.report = opt_report - - if list and append_empty and mode == 'V' then - table.insert(yanked, '') - end - - return yanked -end - --- Defers calling {fn} until {timeout} ms passes. --- --- Use to do a one-shot timer that calls {fn} @@ -1034,7 +985,7 @@ end --- Defaults to "Nvim". ---@param backtrace boolean|nil Prints backtrace. Defaults to true. --- ----@return Deprecated message, or nil if no message was shown. +---@return string|nil # Deprecated message, or nil if no message was shown. function vim.deprecate(name, alternative, version, plugin, backtrace) local msg = ('%s is deprecated'):format(name) plugin = plugin or 'Nvim' @@ -1049,9 +1000,7 @@ function vim.deprecate(name, alternative, version, plugin, backtrace) if displayed and backtrace ~= false then vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN) end - if displayed then - return msg - end + return displayed and msg or nil end --- Create builtin mappings (incl. menus). @@ -1060,27 +1009,37 @@ function vim._init_default_mappings() -- mappings ---@private - local function _search_for_visual_selection(search_prefix) - if search_prefix ~= '/' and search_prefix ~= '?' then - return + local function region_chunks(region) + local chunks = {} + local maxcol = vim.v.maxcol + for line, cols in vim.spairs(region) do + local endcol = cols[2] == maxcol and -1 or cols[2] + local chunk = vim.api.nvim_buf_get_text(0, line, cols[1], line, endcol, {})[1] + table.insert(chunks, chunk) end - -- Escape these characters - local replacements = { - [search_prefix] = [[\]] .. search_prefix, - [ [[\]] ] = [[\\]], - ['\t'] = [[\t]], - ['\n'] = [[\n]], - } - local pattern = '[' .. table.concat(vim.tbl_keys(replacements), '') .. ']' - local visual_selection = vim.get_visual_selection(false) - local escaped_visual_selection = string.gsub(visual_selection, pattern, replacements) - local search_cmd = search_prefix .. [[\V]] .. escaped_visual_selection .. '\n' + return chunks + end + + ---@private + local function _visual_search(cmd) + assert(cmd == '/' or cmd == '?') + vim.api.nvim_feedkeys('\27', 'nx', true) -- Escape visual mode. + local region = vim.region(0, "'<", "'>", vim.fn.visualmode(), vim.o.selection == 'inclusive') + local chunks = region_chunks(region) + local esc_chunks = vim + .iter(chunks) + :map(function(v) + return vim.fn.escape(v, [[/\]]) + end) + :totable() + local esc_pat = table.concat(esc_chunks, [[\n]]) + local search_cmd = ([[%s\V%s%s]]):format(cmd, esc_pat, '\n') vim.api.nvim_feedkeys(search_cmd, 'nx', true) end ---@private local function map(mode, lhs, rhs) - vim.keymap.set(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' }) + vim.keymap.set(mode, lhs, rhs, { desc = 'Nvim builtin' }) end map('n', 'Y', 'y$') @@ -1088,12 +1047,12 @@ function vim._init_default_mappings() map('n', '', 'nohlsearchdiffupdatenormal! ') map('i', '', 'u') map('i', '', 'u') - map('x', '*', function() - _search_for_visual_selection('/') - end) - map('x', '#', function() - _search_for_visual_selection('?') - end) + vim.keymap.set('x', '*', function() + _visual_search('/') + end, { desc = 'Nvim builtin', silent = true }) + vim.keymap.set('x', '#', function() + _visual_search('?') + end, { desc = 'Nvim builtin', silent = true }) -- Use : instead of so that ranges are supported. #19365 map('n', '&', ':&&') -- cgit From c44d819ae1f29cd34ee3b2350b5c702caed949c3 Mon Sep 17 00:00:00 2001 From: Jaehwang Jung Date: Fri, 7 Jul 2023 19:12:46 +0900 Subject: fix(treesitter): update folds in all relevant windows (#24230) Problem: When using treesitter foldexpr, * :diffput/get open diff folds, and * folds are not updated in other windows that contain the updated buffer. Solution: Update folds in all windows that contain the updated buffer and use expr foldmethod. --- runtime/lua/vim/treesitter/_fold.lua | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index d308657237..a02d0a584d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -232,20 +232,40 @@ local M = {} ---@type table local foldinfos = {} -local function recompute_folds() +--- Update the folds in the windows that contain the buffer and use expr foldmethod (assuming that +--- the user doesn't use different foldexpr for the same buffer). +--- +--- 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 + end + if api.nvim_get_mode().mode == 'i' then -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave api.nvim_create_autocmd('InsertLeave', { once = true, - callback = vim._foldupdate, + callback = do_update, }) return end - vim._foldupdate() + do_update() end ---- Schedule a function only if bufnr is loaded +--- Schedule a function only if bufnr is loaded. +--- We schedule fold level computation for the following reasons: +--- * 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. ---@param bufnr integer ---@param fn function local function schedule_if_loaded(bufnr, fn) @@ -261,14 +281,12 @@ end ---@param foldinfo TS.FoldInfo ---@param tree_changes Range4[] local function on_changedtree(bufnr, foldinfo, tree_changes) - -- For some reason, queries seem to use the old buffer state in on_bytes. - -- Get around this by scheduling and manually updating folds. schedule_if_loaded(bufnr, function() for _, change in ipairs(tree_changes) do local srow, _, erow = Range.unpack4(change) get_folds_levels(bufnr, foldinfo, srow, erow) end - recompute_folds() + foldupdate(bufnr) end) end @@ -289,7 +307,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row) end schedule_if_loaded(bufnr, function() get_folds_levels(bufnr, foldinfo, start_row, end_row_new) - recompute_folds() + foldupdate(bufnr) end) end end -- cgit From c379d72c490544b3a56eb0e52ce3c8ef740051d8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 7 Jul 2023 16:37:36 +0100 Subject: feat(lua): allow vim.wo to be double indexed (#20288) * feat(lua): allow vim.wo to be double indexed Problem: `vim.wo` does not implement `setlocal` Solution: Allow `vim.wo` to be double indexed Co-authored-by: Christian Clason --- runtime/lua/vim/_meta.lua | 53 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 9 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 913f1fe203..41e6e8be86 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -67,25 +67,60 @@ local function opt_validate(option_name, target_scope) end end -local function new_opt_accessor(handle, scope) +local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) - if handle == nil and type(k) == 'number' then - return new_opt_accessor(k, scope) + if bufnr == nil and type(k) == 'number' then + return new_buf_opt_accessor(k) end - opt_validate(k, scope) - return api.nvim_get_option_value(k, { [scope] = handle or 0 }) + opt_validate(k, 'buf') + return api.nvim_get_option_value(k, { buf = bufnr or 0 }) end, __newindex = function(_, k, v) - opt_validate(k, scope) - return api.nvim_set_option_value(k, v, { [scope] = handle or 0 }) + opt_validate(k, 'buf') + return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) end, }) end -vim.bo = new_opt_accessor(nil, 'buf') -vim.wo = new_opt_accessor(nil, 'win') +vim.bo = new_buf_opt_accessor() + +local function new_win_opt_accessor(winid, bufnr) + return setmetatable({}, { + __index = function(_, k) + if bufnr == nil and type(k) == 'number' then + if winid == nil then + return new_win_opt_accessor(k) + else + return new_win_opt_accessor(winid, k) + end + end + + if bufnr ~= nil and bufnr ~= 0 then + error('only bufnr=0 is supported') + end + + opt_validate(k, 'win') + -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value + return api.nvim_get_option_value(k, { + scope = bufnr and 'local' or nil, + win = winid or 0, + }) + end, + + __newindex = function(_, k, v) + opt_validate(k, 'win') + -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value + return api.nvim_set_option_value(k, v, { + scope = bufnr and 'local' or nil, + win = winid or 0, + }) + end, + }) +end + +vim.wo = new_win_opt_accessor() -- vim global option -- this ONLY sets the global option. like `setglobal` -- cgit From 21fa19f3e8c1d7b427c6d7c0dbcd6702d1e4e397 Mon Sep 17 00:00:00 2001 From: Chinmay Dalal Date: Sat, 8 Jul 2023 12:44:52 +0530 Subject: fix(lsp): don't use hl_mode = combine for inlay hints #24276 Problem: `hl_mode` for inlay hints is `combine`, causing bugs like inlay hints using highlights from the previous character (#24152, #24068) Solution: Don't use hl_mode=combine for inlay hints. --- runtime/lua/vim/lsp/inlay_hint.lua | 1 - 1 file changed, 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 f577a31696..6426b0c039 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -289,7 +289,6 @@ api.nvim_set_decoration_provider(namespace, { virt_text_pos = 'inline', ephemeral = false, virt_text = vt, - hl_mode = 'combine', }) end end -- cgit From 00d2f4b96eb9c8dcb6b9f67e256bb7faa19354db Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Tue, 4 Jul 2023 19:22:04 +0200 Subject: docs: MAINTAIN.md, nvim_get_mark --- runtime/lua/vim/_editor.lua | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 6f701e9ae9..80e7518b01 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -460,17 +460,18 @@ do vim.t = make_dict_accessor('t') end ---- Get a table of lines with start, end columns for a region marked by two points. ---- Input and output positions are (0,0)-indexed and indicate byte positions. +--- Gets a dict of line segment ("chunk") positions for the region from `pos1` to `pos2`. --- ----@param bufnr integer number of buffer ----@param pos1 integer[]|string start of region as a (line, column) tuple or string accepted by |getpos()| ----@param pos2 integer[]|string end of region as a (line, column) tuple or string accepted by |getpos()| ----@param regtype string type of selection, see |setreg()| ----@param inclusive boolean indicating whether column of pos2 is inclusive ----@return table region Table of the form `{linenr = {startcol,endcol}}`. ---- `endcol` is exclusive, and whole lines are marked with ---- `{startcol,endcol} = {0,-1}`. +--- Input and output positions are byte positions, (0,0)-indexed. "End of line" column +--- position (for example, |linewise| visual selection) is returned as |v:maxcol| (big number). +--- +---@param bufnr integer Buffer number, or 0 for current buffer +---@param pos1 integer[]|string Start of region as a (line, column) tuple or |getpos()|-compatible string +---@param pos2 integer[]|string End of region as a (line, column) tuple or |getpos()|-compatible string +---@param regtype string \|setreg()|-style selection type +---@param inclusive boolean Controls whether `pos2` column is inclusive (see also 'selection'). +---@return table region Dict of the form `{linenr = {startcol,endcol}}`. `endcol` is exclusive, and +---whole lines are returned as `{startcol,endcol} = {0,-1}`. function vim.region(bufnr, pos1, pos2, regtype, inclusive) if not vim.api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -610,18 +611,17 @@ local on_key_cbs = {} --- The Nvim command-line option |-w| is related but does not support callbacks --- and cannot be toggled dynamically. --- ----@param fn function: Callback function. It should take one string argument. ---- On each key press, Nvim passes the key char to fn(). |i_CTRL-V| ---- If {fn} is nil, it removes the callback for the associated {ns_id} +---@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| +--- Returning nil removes the callback associated with namespace {ns_id}. ---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a new --- |nvim_create_namespace()| id. --- ---@return integer Namespace id associated with {fn}. Or count of all callbacks ---if on_key() is called without arguments. ---- ----@note {fn} will be removed if an error occurs while calling. ----@note {fn} will not be cleared by |nvim_buf_clear_namespace()| ----@note {fn} will receive the keys after mappings have been evaluated function vim.on_key(fn, ns_id) if fn == nil and ns_id == nil then return #on_key_cbs @@ -1049,10 +1049,10 @@ function vim._init_default_mappings() map('i', '', 'u') vim.keymap.set('x', '*', function() _visual_search('/') - end, { desc = 'Nvim builtin', silent = true }) + end, { desc = ':help v_star-default', silent = true }) vim.keymap.set('x', '#', function() _visual_search('?') - end, { desc = 'Nvim builtin', silent = true }) + end, { desc = ':help v_#-default', silent = true }) -- Use : instead of so that ranges are supported. #19365 map('n', '&', ':&&') -- cgit From 6318edadc32acce3ed41a6995a5faa5395b5f562 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 8 Jul 2023 15:28:13 +0200 Subject: fix(defaults): visual hash (#) on text with "?" Problem: The default "#" mapping fails on the following example after v$h# with cursor at start of the first line: aa?/\bb aa aa?/\bb Solution: Also escape "?". --- runtime/lua/vim/_editor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 80e7518b01..0bbbed74bb 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1029,7 +1029,7 @@ function vim._init_default_mappings() local esc_chunks = vim .iter(chunks) :map(function(v) - return vim.fn.escape(v, [[/\]]) + return vim.fn.escape(v, [[?/\]]) end) :totable() local esc_pat = table.concat(esc_chunks, [[\n]]) -- cgit From b9a0e762f1d79d17631b7d17cf25b6e25006d8c2 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sat, 8 Jul 2023 16:55:27 +0200 Subject: fix(defaults): visual star (*) on text with "?" regression from 6318edadc32acce3ed41a6995a5faa5395b5f562 --- runtime/lua/vim/_editor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 0bbbed74bb..5a93f5cd60 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1029,7 +1029,7 @@ function vim._init_default_mappings() local esc_chunks = vim .iter(chunks) :map(function(v) - return vim.fn.escape(v, [[?/\]]) + return vim.fn.escape(v, cmd == '/' and [[/\]] or [[?\]]) end) :totable() local esc_pat = table.concat(esc_chunks, [[\n]]) -- cgit From 766f4978d6cb146511cf0b676c01e5327db46647 Mon Sep 17 00:00:00 2001 From: Raphael Date: Mon, 10 Jul 2023 19:38:15 +0800 Subject: fix(lint): lint warnings #24226 --- runtime/lua/vim/_editor.lua | 2 +- runtime/lua/vim/_watch.lua | 4 ++-- runtime/lua/vim/filetype.lua | 2 +- runtime/lua/vim/fs.lua | 8 ++++---- runtime/lua/vim/lsp.lua | 20 ++++++++++---------- runtime/lua/vim/lsp/buf.lua | 15 ++++++++------- runtime/lua/vim/lsp/handlers.lua | 7 ++++--- runtime/lua/vim/lsp/log.lua | 2 +- runtime/lua/vim/lsp/rpc.lua | 17 ++++++++--------- runtime/lua/vim/lsp/sync.lua | 12 +++++++----- runtime/lua/vim/uri.lua | 10 ++-------- 11 files changed, 48 insertions(+), 51 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 5a93f5cd60..47215416e0 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -219,7 +219,7 @@ do --- - 1: starts the paste (exactly once) --- - 2: continues the paste (zero or more times) --- - 3: ends the paste (exactly once) - ---@returns boolean # false if client should cancel the paste. + ---@return boolean result false if client should cancel the paste. function vim.paste(lines, phase) local now = vim.uv.now() local is_first_chunk = phase < 2 diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index d489cef9fc..00b7416098 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -37,7 +37,7 @@ end --- - uvflags (table|nil) --- Same flags as accepted by |uv.fs_event_start()| ---@param callback (function) The function called when new events ----@return (function) A function to stop the watch +---@return (function) Stops the watcher function M.watch(path, opts, callback) vim.validate({ path = { path, 'string', false }, @@ -208,7 +208,7 @@ end --- not match the pattern will be reported. Matches against both files and --- directories. When nil, matches nothing. ---@param callback (function) The function called when new events ----@returns (function) A function to stop the watch. +---@return function Stops the watcher function M.poll(path, opts, callback) vim.validate({ path = { path, 'string', false }, diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bc880ed130..33d451315d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -28,7 +28,7 @@ end --- If only start_lnum is specified, return a single line as a string. --- If both start_lnum and end_lnum are omitted, return all lines from the buffer. --- ----@param bufnr integer|nil The buffer to get the lines from +---@param bufnr integer The buffer to get the lines from ---@param start_lnum integer|nil The line number of the first line (inclusive, 1-based) ---@param end_lnum integer|nil The line number of the last line (inclusive, 1-based) ---@return table|string Array of lines, or string when end_lnum is omitted diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index cdb314fa3d..48d76e5d7e 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -20,7 +20,7 @@ local iswin = vim.uv.os_uname().sysname == 'Windows_NT' --- --- ---@param start (string) Initial file or directory. ----@return (function) Iterator +---@return function Iterator function M.parents(start) return function(_, dir) local parent = M.dirname(dir) @@ -37,7 +37,7 @@ end --- Return the parent directory of the given file or directory --- ---@param file (string) File or directory ----@return (string) Parent directory of {file} +---@return string|nil Parent directory of {file} function M.dirname(file) if file == nil then return nil @@ -59,8 +59,8 @@ end --- Return the basename of the given file or directory --- ----@param file (string) File or directory ----@return (string) Basename of {file} +---@param file string File or directory +---@return string|nil Basename of {file} function M.basename(file) if file == nil then return nil diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index ca4851f8d7..ed431e080e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1951,7 +1951,7 @@ end --- ---@param client_id integer client id --- ----@returns |vim.lsp.client| object, or nil +---@return (nil|lsp.Client) client rpc object function lsp.get_client_by_id(client_id) return active_clients[client_id] or uninitialized_clients[client_id] end @@ -2090,13 +2090,14 @@ api.nvim_create_autocmd('VimLeavePre', { ---@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 lsp-handler|nil See |lsp-handler| +---@param handler lsp-handler See |lsp-handler| --- If nil, follows resolution strategy defined in |lsp-handler-configuration| --- ----@return table, fun() 2-tuple: ---- - Map of client-id:request-id pairs for all successful requests. ---- - Function which can be used to cancel all the requests. You could instead ---- iterate all clients and call their `cancel_request()` methods. +---@return table client_request_ids Map of client-id:request-id pairs +---for all successful requests. +---@return function _cancel_all_requests Function which can be used to +---cancel all the requests. You could instead +---iterate all clients and call their `cancel_request()` methods. function lsp.buf_request(bufnr, method, params, handler) validate({ bufnr = { bufnr, 'n', true }, @@ -2147,8 +2148,7 @@ end ---@param handler fun(results: table) (function) --- Handler called after all requests are completed. Server results are passed as --- a `client_id:result` map. ---- ----@return fun() cancel Function that cancels all requests. +---@return function cancel Function that cancels all requests. function lsp.buf_request_all(bufnr, method, params, handler) local results = {} local result_count = 0 @@ -2257,7 +2257,7 @@ end ---@param findstart integer 0 or 1, decides behavior ---@param base integer findstart=0, text to match against --- ----@returns (integer) Decided by {findstart}: +---@return integer|table Decided by {findstart}: --- - findstart=0: column where the completion starts, or -2 or -3 --- - findstart=1: list of matches (actually just calls |complete()|) function lsp.omnifunc(findstart, base) @@ -2405,7 +2405,7 @@ end --- is a |vim.lsp.client| object. --- ---@param bufnr (integer|nil): Buffer handle, or 0 for current ----@returns (table) Table of (client_id, client) pairs +---@return table result is table of (client_id, client) pairs ---@deprecated Use |vim.lsp.get_active_clients()| instead. function lsp.buf_get_clients(bufnr) local result = {} diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index bac66b671d..8ae0e0cde3 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -13,10 +13,11 @@ local M = {} ---@param params (table|nil) Parameters to send to the server ---@param handler (function|nil) See |lsp-handler|. Follows |lsp-handler-resolution| -- ----@returns 2-tuple: ---- - Map of client-id:request-id pairs for all successful requests. ---- - Function which can be used to cancel all the requests. You could instead ---- iterate all clients and call their `cancel_request()` methods. +---@return table client_request_ids Map of client-id:request-id pairs +---for all successful requests. +---@return function _cancel_all_requests Function which can be used to +---cancel all the requests. You could instead +---iterate all clients and call their `cancel_request()` methods. --- ---@see |vim.lsp.buf_request()| local function request(method, params, handler) @@ -30,7 +31,7 @@ end --- Checks whether the language servers attached to the current buffer are --- ready. --- ----@returns `true` if server responds. +---@return boolean if server responds. ---@deprecated function M.server_ready() vim.deprecate('vim.lsp.buf.server_ready', nil, '0.10.0') @@ -108,7 +109,7 @@ end --- Retrieves the completion items at the current cursor position. Can only be --- called in Insert mode. --- ----@param context (context support not yet implemented) Additional information +---@param context table (context support not yet implemented) Additional information --- about the context in which a completion was triggered (how it was triggered, --- and by which trigger character, if applicable) --- @@ -549,7 +550,7 @@ end --- call, the user is prompted to enter a string on the command line. An empty --- string means no filtering is done. --- ----@param query (string, optional) +---@param query string|nil optional ---@param options table|nil additional options --- - on_list: (function) handler for list results. See |lsp-on-list-handler| function M.workspace_symbol(query, options) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 70781cb7a6..20c4d7458b 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -382,7 +382,7 @@ M['textDocument/hover'] = M.hover ---@private --- Jumps to a location. Used as a handler for multiple LSP methods. ----@param _ (not used) +---@param _ nil not used ---@param result (table) result of LSP method; a location or a list of locations. ---@param ctx (table) table containing the context of the request, including the method ---(`textDocument/definition` can return `Location` or `Location[]` @@ -496,8 +496,9 @@ end --- Displays call hierarchy in the quickfix window. --- ---@param direction `"from"` for incoming calls and `"to"` for outgoing calls ----@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`, ----@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, +---@return function +--- `CallHierarchyIncomingCall[]` if {direction} is `"from"`, +--- `CallHierarchyOutgoingCall[]` if {direction} is `"to"`, local make_call_hierarchy_handler = function(direction) return function(_, result) if not result then diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index c77e7c045b..033f93bd6e 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -44,7 +44,7 @@ do vim.fn.mkdir(vim.fn.stdpath('log'), 'p') --- Returns the log filename. - ---@returns (string) log filename + ---@return string log filename function log.get_filename() return logfilename end diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 6552eaa800..c110c3a67c 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -8,7 +8,7 @@ local is_win = uv.os_uname().version:find('Windows') ---@private --- Checks whether a given path exists and is a directory. ---@param filename (string) path to check ----@returns (bool) +---@return boolean local function is_dir(filename) local stat = uv.fs_stat(filename) return stat and stat.type == 'directory' or false @@ -18,7 +18,7 @@ end --- Embeds the given string into a table and correctly computes `Content-Length`. --- ---@param encoded_message (string) ----@returns (table) table containing encoded message and `Content-Length` attribute +---@return string containing encoded message and `Content-Length` attribute local function format_message_with_content_length(encoded_message) return table.concat({ 'Content-Length: ', @@ -32,7 +32,7 @@ end --- Parses an LSP Message's header --- ---@param header string: The header to parse. ----@return table parsed headers +---@return table # parsed headers local function parse_headers(header) assert(type(header) == 'string', 'header must be a string') local headers = {} @@ -190,7 +190,8 @@ end --- ---@param method (string) The invoked LSP method ---@param params (table): Parameters for the invoked LSP method ----@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`. +---@return nil +---@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound` function default_dispatchers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound) @@ -268,7 +269,7 @@ end --- Sends a notification to the LSP server. ---@param method (string) The invoked LSP method ---@param params (any): Parameters for the invoked LSP method ----@returns (bool) `true` if notification could be sent, `false` if not +---@return boolean `true` if notification could be sent, `false` if not function Client:notify(method, params) return self:encode_and_send({ jsonrpc = '2.0', @@ -522,7 +523,7 @@ local function public_client(client) --- Sends a notification to the LSP server. ---@param method (string) The invoked LSP method ---@param params (table|nil): Parameters for the invoked LSP method - ---@returns (bool) `true` if notification could be sent, `false` if not + ---@return boolean `true` if notification could be sent, `false` if not function result.notify(method, params) return client:notify(method, params) end @@ -624,9 +625,7 @@ end --- server process. May contain: --- - {cwd} (string) Working directory for the LSP server process --- - {env} (table) Additional environment variables for LSP server process ----@returns Client RPC object. ---- ----@returns Methods: +---@return table|nil 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. diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index fb5b0b3194..350c096b47 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -94,7 +94,8 @@ end ---@param line string the line to index into ---@param byte integer the byte idx ---@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8) ----@returns table byte_idx and char_idx of first change position +---@return integer byte_idx of first change position +---@return integer char_idx of first change position local function align_end_position(line, byte, offset_encoding) local char -- If on the first byte, or an empty string: the trivial case @@ -129,7 +130,7 @@ end ---@param lastline integer lastline from on_lines, adjusted to 1-index ---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index ---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8) ----@returns table line_idx, byte_idx, and char_idx of first change position +---@return table result table include line_idx, byte_idx, and char_idx of first change position local function compute_start_range( prev_lines, curr_lines, @@ -209,7 +210,8 @@ end ---@param lastline integer ---@param new_lastline integer ---@param offset_encoding string ----@returns (int, int) end_line_idx and end_col_idx of range +---@return integer|table end_line_idx and end_col_idx of range +---@return table|nil end_col_idx of range local function compute_end_range( prev_lines, curr_lines, @@ -310,7 +312,7 @@ end ---@param lines table list of lines ---@param start_range table table returned by first_difference ---@param end_range table new_end_range returned by last_difference ----@returns string text extracted from defined region +---@return string text extracted from defined region local function extract_text(lines, start_range, end_range, line_ending) if not lines[start_range.line_idx] then return '' @@ -392,7 +394,7 @@ end ---@param lastline integer line to begin search in old_lines for last difference ---@param new_lastline integer line to begin search in new_lines for last difference ---@param offset_encoding string encoding requested by language server ----@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent +---@return table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent function M.compute_diff( prev_lines, curr_lines, diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index 08ed829114..dec7840eb0 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -30,14 +30,8 @@ do -- https://tools.ietf.org/html/rfc3986#section-2.2 rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/", } - local sbyte, tohex = string.byte - if jit then - tohex = require('bit').tohex - else - tohex = function(b) - return string.format('%02x', b) - end - end + local sbyte = string.byte + local tohex = require('bit').tohex ---@private local function percent_encode_char(char) -- cgit From 317c80f460a7826421f40f57ee8bdbd736b0f225 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 12 Jul 2023 14:48:21 +0200 Subject: feat(lsp): add method filter to get_active_clients (#24319) --- runtime/lua/vim/lsp.lua | 3 +++ runtime/lua/vim/lsp/buf.lua | 28 +++++++++++----------------- 2 files changed, 14 insertions(+), 17 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index ed431e080e..1f9b6c4360 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1995,6 +1995,7 @@ end ---@field id integer|nil Match clients by id ---@field bufnr integer|nil match clients attached to the given buffer ---@field name string|nil match clients by name +---@field method string|nil match client by supported method name --- Get active clients. --- @@ -2004,6 +2005,7 @@ end --- - id (number): Only return clients with the given id --- - bufnr (number): Only return clients attached to this buffer --- - name (string): Only return clients with the given name +--- - method (string): Only return clients supporting the given method ---@return lsp.Client[]: List of |vim.lsp.client| objects function lsp.get_active_clients(filter) validate({ filter = { filter, 't', true } }) @@ -2020,6 +2022,7 @@ function lsp.get_active_clients(filter) client and (filter.id == nil or client.id == filter.id) and (filter.name == nil or client.name == filter.name) + and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr })) then clients[#clients + 1] = client end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8ae0e0cde3..b238b5c221 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -197,15 +197,6 @@ end function M.format(options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_active_clients({ - id = options.id, - bufnr = bufnr, - name = options.name, - }) - - if options.filter then - clients = vim.tbl_filter(options.filter, clients) - end local mode = api.nvim_get_mode().mode local range = options.range @@ -214,9 +205,15 @@ function M.format(options) end local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' - clients = vim.tbl_filter(function(client) - return client.supports_method(method) - end, clients) + local clients = vim.lsp.get_active_clients({ + id = options.id, + bufnr = bufnr, + name = options.name, + method = method, + }) + if options.filter then + clients = vim.tbl_filter(options.filter, clients) + end if #clients == 0 then vim.notify('[LSP] Format request failed, no matching language servers.') @@ -277,16 +274,13 @@ function M.rename(new_name, options) local clients = vim.lsp.get_active_clients({ bufnr = bufnr, name = options.name, + -- Clients must at least support rename, prepareRename is optional + method = 'textDocument/rename', }) if options.filter then clients = vim.tbl_filter(options.filter, clients) end - -- Clients must at least support rename, prepareRename is optional - clients = vim.tbl_filter(function(client) - return client.supports_method('textDocument/rename') - end, clients) - if #clients == 0 then vim.notify('[LSP] Rename, no matching language servers with rename capability.') end -- cgit From a3f4598226c4d01e4fbc41181a1ad21793862fe3 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 13 Jul 2023 14:43:36 +0200 Subject: docs(lua): adds links to related keymap functions to keymap.set (#24337) Might help with discovery, given that there is no `keymap.get()` --- runtime/lua/vim/keymap.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 9dbc65afe8..2b55ddc787 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -32,6 +32,9 @@ local keymap = {} --- - "remap": (boolean) Make the mapping recursive. Inverse of "noremap". --- Defaults to `false`. ---@see |nvim_set_keymap()| +---@see |maparg()| +---@see |mapcheck()| +---@see |mapset()| function keymap.set(mode, lhs, rhs, opts) vim.validate({ mode = { mode, { 's', 't' } }, -- cgit From 2ecba65b4ba741618ecbfd2a50a939987078bc98 Mon Sep 17 00:00:00 2001 From: Techatrix <19954306+Techatrix@users.noreply.github.com> Date: Fri, 14 Jul 2023 05:36:10 +0000 Subject: fix(lsp): remove unknown LSP protocol property (#24345) 'hierarchicalWorkspaceSymbolSupport' is not part of the LSP Specification --- runtime/lua/vim/lsp/protocol.lua | 1 - 1 file changed, 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 27da891656..5bc0baf241 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -844,7 +844,6 @@ function protocol.make_client_capabilities() return res end)(), }, - hierarchicalWorkspaceSymbolSupport = true, }, configuration = true, workspaceFolders = true, -- cgit From 33e1a8cd7042816a064c0d2bf32b6570d7e88b79 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Fri, 14 Jul 2023 18:47:18 +0200 Subject: feat(lsp): map K to hover by default #24331 Related: https://github.com/neovim/neovim/issues/24252 --- runtime/lua/vim/lsp.lua | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1f9b6c4360..e9a1423d2d 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -963,6 +963,15 @@ function lsp._set_defaults(client, bufnr) then vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()' end + api.nvim_buf_call(bufnr, function() + if + client.supports_method('textDocument/hover') + 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 }) + end + end) end --- @class lsp.ClientConfig @@ -1202,7 +1211,7 @@ function lsp.start_client(config) ---@private --- Reset defaults set by `set_defaults`. --- Must only be called if the last client attached to a buffer exits. - local function unset_defaults(bufnr) + local function reset_defaults(bufnr) if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then vim.bo[bufnr].tagfunc = nil end @@ -1212,6 +1221,12 @@ function lsp.start_client(config) if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then vim.bo[bufnr].formatexpr = nil end + api.nvim_buf_call(bufnr, function() + local keymap = vim.fn.maparg('K', 'n', false, true) + if keymap and keymap.callback == vim.lsp.buf.hover then + vim.keymap.del('n', 'K', { buffer = bufnr }) + end + end) end ---@private @@ -1243,7 +1258,7 @@ function lsp.start_client(config) client_ids[client_id] = nil if vim.tbl_isempty(client_ids) then - unset_defaults(bufnr) + reset_defaults(bufnr) end end) end -- cgit From 251ca45ac94851c896db0d27685622fb78a73b3e Mon Sep 17 00:00:00 2001 From: Mike <10135646+mikesmithgh@users.noreply.github.com> Date: Sun, 16 Jul 2023 06:11:45 -0400 Subject: fix(lsp): markdown code fence should allow space before info string #24364 Problem: Bash language server returns "hover" markdown content that starts with a code fence and info string of `man` preceded by whitespace, which Nvim does not render properly. See https://github.com/bash-lsp/bash-language-server/blob/0ee73c53cebdc18311d4a4ad9367185ea4d98a03/server/src/server.ts#L821C15-L821C15 ```typescript function getMarkdownContent(documentation: string, language?: string): LSP.MarkupContent { return { value: language ? // eslint-disable-next-line prefer-template ['``` ' + language, documentation, '```'].join('\n') : documentation, kind: LSP.MarkupKind.Markdown, } } ``` For example, ``` ``` man NAME git - the stupid content tracker ``` ``` If I remove the white space, then it is properly formatted. ``` ```man instead of ``` man ``` Per CommonMark Spec https://spec.commonmark.org/0.30/#info-string whitespace is allowed before and after the `info string` which identifies the language in a codeblock. > The line with the opening code fence may optionally contain some text > following the code fence; this is trimmed of leading and trailing > spaces or tabs and called the [info > string](https://spec.commonmark.org/0.30/#info-string). If the [info > string](https://spec.commonmark.org/0.30/#info-string) comes after > a backtick fence, it may not contain any backtick characters. (The > reason for this restriction is that otherwise some inline code would > be incorrectly interpreted as the beginning of a fenced code block.) Solution: Adjust stylize_markdown() to allow whitespace before codeblock info. --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 8d6f88bb2c..0da88f800e 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1347,7 +1347,7 @@ function M.stylize_markdown(bufnr, contents, opts) -- table of fence types to {ft, begin, end} -- when ft is nil, we get the ft from the regex match local matchers = { - block = { nil, '```+([a-zA-Z0-9_]*)', '```+' }, + block = { nil, '```+%s*([a-zA-Z0-9_]*)', '```+' }, pre = { nil, '
([a-z0-9]*)', '
' }, code = { '', '', '' }, text = { 'text', '', '' }, -- cgit From d0b612f360125785eb95afaa51620c5c7695e381 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sun, 16 Jul 2023 09:27:39 +0100 Subject: refactor: rename _meta.lua to _options.lua --- runtime/lua/vim/_editor.lua | 2 +- runtime/lua/vim/_meta.lua | 575 ------------------------------------------- runtime/lua/vim/_options.lua | 575 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 576 insertions(+), 576 deletions(-) delete mode 100644 runtime/lua/vim/_meta.lua create mode 100644 runtime/lua/vim/_options.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 47215416e0..bb0aa97a0d 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1094,7 +1094,7 @@ function vim._init_defaults() vim._init_default_autocmds() end -require('vim._meta') +require('vim._options') -- Remove at Nvim 1.0 ---@deprecated diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua deleted file mode 100644 index 41e6e8be86..0000000000 --- a/runtime/lua/vim/_meta.lua +++ /dev/null @@ -1,575 +0,0 @@ -local api = vim.api - --- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. --- Can be done in a separate PR. -local key_value_options = { - fillchars = true, - fcs = true, - listchars = true, - lcs = true, - winhighlight = true, - winhl = true, -} - ---- Convert a vimoption_T style dictionary to the correct OptionType associated with it. ----@return string -local function get_option_metatype(name, info) - if info.type == 'string' then - if info.flaglist then - return 'set' - elseif info.commalist then - if key_value_options[name] then - return 'map' - end - return 'array' - end - return 'string' - end - return info.type -end - -local options_info = setmetatable({}, { - __index = function(t, k) - local info = api.nvim_get_option_info(k) - info.metatype = get_option_metatype(k, info) - rawset(t, k, info) - return rawget(t, k) - end, -}) - -vim.env = setmetatable({}, { - __index = function(_, k) - local v = vim.fn.getenv(k) - if v == vim.NIL then - return nil - end - return v - end, - - __newindex = function(_, k, v) - vim.fn.setenv(k, v) - end, -}) - -local function opt_validate(option_name, target_scope) - local scope = options_info[option_name].scope - if scope ~= target_scope then - local scope_to_string = { buf = 'buffer', win = 'window' } - error( - string.format( - [['%s' is a %s option, not a %s option. See ":help %s"]], - option_name, - scope_to_string[scope] or scope, - scope_to_string[target_scope] or target_scope, - option_name - ) - ) - end -end - -local function new_buf_opt_accessor(bufnr) - return setmetatable({}, { - __index = function(_, k) - if bufnr == nil and type(k) == 'number' then - return new_buf_opt_accessor(k) - end - opt_validate(k, 'buf') - return api.nvim_get_option_value(k, { buf = bufnr or 0 }) - end, - - __newindex = function(_, k, v) - opt_validate(k, 'buf') - return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) - end, - }) -end - -vim.bo = new_buf_opt_accessor() - -local function new_win_opt_accessor(winid, bufnr) - return setmetatable({}, { - __index = function(_, k) - if bufnr == nil and type(k) == 'number' then - if winid == nil then - return new_win_opt_accessor(k) - else - return new_win_opt_accessor(winid, k) - end - end - - if bufnr ~= nil and bufnr ~= 0 then - error('only bufnr=0 is supported') - end - - opt_validate(k, 'win') - -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value - return api.nvim_get_option_value(k, { - scope = bufnr and 'local' or nil, - win = winid or 0, - }) - end, - - __newindex = function(_, k, v) - opt_validate(k, 'win') - -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value - return api.nvim_set_option_value(k, v, { - scope = bufnr and 'local' or nil, - win = winid or 0, - }) - end, - }) -end - -vim.wo = new_win_opt_accessor() - --- vim global option --- this ONLY sets the global option. like `setglobal` -vim.go = setmetatable({}, { - __index = function(_, k) - return api.nvim_get_option_value(k, { scope = 'global' }) - end, - __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, { scope = 'global' }) - end, -}) - --- vim `set` style options. --- it has no additional metamethod magic. -vim.o = setmetatable({}, { - __index = function(_, k) - return api.nvim_get_option_value(k, {}) - end, - __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, {}) - end, -}) - ----@brief [[ ---- vim.opt, vim.opt_local and vim.opt_global implementation ---- ---- To be used as helpers for working with options within neovim. ---- For information on how to use, see :help vim.opt ---- ----@brief ]] - ---- Preserves the order and does not mutate the original list -local function remove_duplicate_values(t) - local result, seen = {}, {} - for _, v in ipairs(t) do - if not seen[v] then - table.insert(result, v) - end - - seen[v] = true - end - - return result -end - --- Check whether the OptionTypes is allowed for vim.opt --- If it does not match, throw an error which indicates which option causes the error. -local function assert_valid_value(name, value, types) - local type_of_value = type(value) - for _, valid_type in ipairs(types) do - if valid_type == type_of_value then - return - end - end - - error( - string.format( - "Invalid option type '%s' for '%s', should be %s", - type_of_value, - name, - table.concat(types, ' or ') - ) - ) -end - -local function passthrough(_, x) - return x -end - -local function tbl_merge(left, right) - return vim.tbl_extend('force', left, right) -end - -local function tbl_remove(t, value) - if type(value) == 'string' then - t[value] = nil - else - for _, v in ipairs(value) do - t[v] = nil - end - end - - return t -end - -local valid_types = { - boolean = { 'boolean' }, - number = { 'number' }, - string = { 'string' }, - set = { 'string', 'table' }, - array = { 'string', 'table' }, - map = { 'string', 'table' }, -} - --- Map of functions to take a Lua style value and convert to vimoption_T style value. --- Each function takes (info, lua_value) -> vim_value -local to_vim_value = { - boolean = passthrough, - number = passthrough, - string = passthrough, - - set = function(info, value) - if type(value) == 'string' then - return value - end - - if info.flaglist and info.commalist then - local keys = {} - for k, v in pairs(value) do - if v then - table.insert(keys, k) - end - end - - table.sort(keys) - return table.concat(keys, ',') - else - local result = '' - for k, v in pairs(value) do - if v then - result = result .. k - end - end - - return result - end - end, - - array = function(info, value) - if type(value) == 'string' then - return value - end - if not info.allows_duplicates then - value = remove_duplicate_values(value) - end - return table.concat(value, ',') - end, - - map = function(_, value) - if type(value) == 'string' then - return value - end - - local result = {} - for opt_key, opt_value in pairs(value) do - table.insert(result, string.format('%s:%s', opt_key, opt_value)) - end - - table.sort(result) - return table.concat(result, ',') - end, -} - ---- Convert a Lua value to a vimoption_T value -local function convert_value_to_vim(name, info, value) - if value == nil then - return vim.NIL - end - - assert_valid_value(name, value, valid_types[info.metatype]) - - return to_vim_value[info.metatype](info, value) -end - --- Map of OptionType to functions that take vimoption_T values and convert to Lua values. --- Each function takes (info, vim_value) -> lua_value -local to_lua_value = { - boolean = passthrough, - number = passthrough, - string = passthrough, - - array = function(info, value) - if type(value) == 'table' then - if not info.allows_duplicates then - value = remove_duplicate_values(value) - end - - return value - end - - -- Empty strings mean that there is nothing there, - -- so empty table should be returned. - if value == '' then - return {} - end - - -- Handles unescaped commas in a list. - if string.find(value, ',,,') then - local left, right = unpack(vim.split(value, ',,,')) - - local result = {} - vim.list_extend(result, vim.split(left, ',')) - table.insert(result, ',') - vim.list_extend(result, vim.split(right, ',')) - - table.sort(result) - - return result - end - - if string.find(value, ',^,,', 1, true) then - local left, right = unpack(vim.split(value, ',^,,', true)) - - local result = {} - vim.list_extend(result, vim.split(left, ',')) - table.insert(result, '^,') - vim.list_extend(result, vim.split(right, ',')) - - table.sort(result) - - return result - end - - return vim.split(value, ',') - end, - - set = function(info, value) - if type(value) == 'table' then - return value - end - - -- Empty strings mean that there is nothing there, - -- so empty table should be returned. - if value == '' then - return {} - end - - assert(info.flaglist, 'That is the only one I know how to handle') - - if info.flaglist and info.commalist then - local split_value = vim.split(value, ',') - local result = {} - for _, v in ipairs(split_value) do - result[v] = true - end - - return result - else - local result = {} - for i = 1, #value do - result[value:sub(i, i)] = true - end - - return result - end - end, - - map = function(info, raw_value) - if type(raw_value) == 'table' then - return raw_value - end - - assert(info.commalist, 'Only commas are supported currently') - - local result = {} - - local comma_split = vim.split(raw_value, ',') - for _, key_value_str in ipairs(comma_split) do - local key, value = unpack(vim.split(key_value_str, ':')) - key = vim.trim(key) - - result[key] = value - end - - return result - end, -} - ---- Converts a vimoption_T style value to a Lua value -local function convert_value_to_lua(info, option_value) - return to_lua_value[info.metatype](info, option_value) -end - -local prepend_methods = { - number = function() - error("The '^' operator is not currently supported for") - end, - - string = function(left, right) - return right .. left - end, - - array = function(left, right) - for i = #right, 1, -1 do - table.insert(left, 1, right[i]) - end - - return left - end, - - map = tbl_merge, - set = tbl_merge, -} - ---- Handles the '^' operator -local function prepend_value(info, current, new) - return prepend_methods[info.metatype]( - convert_value_to_lua(info, current), - convert_value_to_lua(info, new) - ) -end - -local add_methods = { - number = function(left, right) - return left + right - end, - - string = function(left, right) - return left .. right - end, - - array = function(left, right) - for _, v in ipairs(right) do - table.insert(left, v) - end - - return left - end, - - map = tbl_merge, - set = tbl_merge, -} - ---- Handles the '+' operator -local function add_value(info, current, new) - return add_methods[info.metatype]( - convert_value_to_lua(info, current), - convert_value_to_lua(info, new) - ) -end - -local function remove_one_item(t, val) - if vim.tbl_islist(t) then - local remove_index = nil - for i, v in ipairs(t) do - if v == val then - remove_index = i - end - end - - if remove_index then - table.remove(t, remove_index) - end - else - t[val] = nil - end -end - -local remove_methods = { - number = function(left, right) - return left - right - end, - - string = function() - error('Subtraction not supported for strings.') - end, - - array = function(left, right) - if type(right) == 'string' then - remove_one_item(left, right) - else - for _, v in ipairs(right) do - remove_one_item(left, v) - end - end - - return left - end, - - map = tbl_remove, - set = tbl_remove, -} - ---- Handles the '-' operator -local function remove_value(info, current, new) - return remove_methods[info.metatype](convert_value_to_lua(info, current), new) -end - -local function create_option_accessor(scope) - local option_mt - - local function make_option(name, value) - local info = assert(options_info[name], 'Not a valid option name: ' .. name) - - if type(value) == 'table' and getmetatable(value) == option_mt then - assert(name == value._name, "must be the same value, otherwise that's weird.") - - value = value._value - end - - return setmetatable({ - _name = name, - _value = value, - _info = info, - }, option_mt) - end - - option_mt = { - -- To set a value, instead use: - -- opt[my_option] = value - _set = function(self) - local value = convert_value_to_vim(self._name, self._info, self._value) - api.nvim_set_option_value(self._name, value, { scope = scope }) - end, - - get = function(self) - return convert_value_to_lua(self._info, self._value) - end, - - append = function(self, right) - self._value = add_value(self._info, self._value, right) - self:_set() - end, - - __add = function(self, right) - return make_option(self._name, add_value(self._info, self._value, right)) - end, - - prepend = function(self, right) - self._value = prepend_value(self._info, self._value, right) - self:_set() - end, - - __pow = function(self, right) - return make_option(self._name, prepend_value(self._info, self._value, right)) - end, - - remove = function(self, right) - self._value = remove_value(self._info, self._value, right) - self:_set() - end, - - __sub = function(self, right) - return make_option(self._name, remove_value(self._info, self._value, right)) - end, - } - option_mt.__index = option_mt - - return setmetatable({}, { - __index = function(_, k) - return make_option(k, api.nvim_get_option_value(k, {})) - end, - - __newindex = function(_, k, v) - make_option(k, v):_set() - end, - }) -end - -vim.opt = create_option_accessor() -vim.opt_local = create_option_accessor('local') -vim.opt_global = create_option_accessor('global') diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua new file mode 100644 index 0000000000..41e6e8be86 --- /dev/null +++ b/runtime/lua/vim/_options.lua @@ -0,0 +1,575 @@ +local api = vim.api + +-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. +-- Can be done in a separate PR. +local key_value_options = { + fillchars = true, + fcs = true, + listchars = true, + lcs = true, + winhighlight = true, + winhl = true, +} + +--- Convert a vimoption_T style dictionary to the correct OptionType associated with it. +---@return string +local function get_option_metatype(name, info) + if info.type == 'string' then + if info.flaglist then + return 'set' + elseif info.commalist then + if key_value_options[name] then + return 'map' + end + return 'array' + end + return 'string' + end + return info.type +end + +local options_info = setmetatable({}, { + __index = function(t, k) + local info = api.nvim_get_option_info(k) + info.metatype = get_option_metatype(k, info) + rawset(t, k, info) + return rawget(t, k) + end, +}) + +vim.env = setmetatable({}, { + __index = function(_, k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil + end + return v + end, + + __newindex = function(_, k, v) + vim.fn.setenv(k, v) + end, +}) + +local function opt_validate(option_name, target_scope) + local scope = options_info[option_name].scope + if scope ~= target_scope then + local scope_to_string = { buf = 'buffer', win = 'window' } + error( + string.format( + [['%s' is a %s option, not a %s option. See ":help %s"]], + option_name, + scope_to_string[scope] or scope, + scope_to_string[target_scope] or target_scope, + option_name + ) + ) + end +end + +local function new_buf_opt_accessor(bufnr) + return setmetatable({}, { + __index = function(_, k) + if bufnr == nil and type(k) == 'number' then + return new_buf_opt_accessor(k) + end + opt_validate(k, 'buf') + return api.nvim_get_option_value(k, { buf = bufnr or 0 }) + end, + + __newindex = function(_, k, v) + opt_validate(k, 'buf') + return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) + end, + }) +end + +vim.bo = new_buf_opt_accessor() + +local function new_win_opt_accessor(winid, bufnr) + return setmetatable({}, { + __index = function(_, k) + if bufnr == nil and type(k) == 'number' then + if winid == nil then + return new_win_opt_accessor(k) + else + return new_win_opt_accessor(winid, k) + end + end + + if bufnr ~= nil and bufnr ~= 0 then + error('only bufnr=0 is supported') + end + + opt_validate(k, 'win') + -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value + return api.nvim_get_option_value(k, { + scope = bufnr and 'local' or nil, + win = winid or 0, + }) + end, + + __newindex = function(_, k, v) + opt_validate(k, 'win') + -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value + return api.nvim_set_option_value(k, v, { + scope = bufnr and 'local' or nil, + win = winid or 0, + }) + end, + }) +end + +vim.wo = new_win_opt_accessor() + +-- vim global option +-- this ONLY sets the global option. like `setglobal` +vim.go = setmetatable({}, { + __index = function(_, k) + return api.nvim_get_option_value(k, { scope = 'global' }) + end, + __newindex = function(_, k, v) + return api.nvim_set_option_value(k, v, { scope = 'global' }) + end, +}) + +-- vim `set` style options. +-- it has no additional metamethod magic. +vim.o = setmetatable({}, { + __index = function(_, k) + return api.nvim_get_option_value(k, {}) + end, + __newindex = function(_, k, v) + return api.nvim_set_option_value(k, v, {}) + end, +}) + +---@brief [[ +--- vim.opt, vim.opt_local and vim.opt_global implementation +--- +--- To be used as helpers for working with options within neovim. +--- For information on how to use, see :help vim.opt +--- +---@brief ]] + +--- Preserves the order and does not mutate the original list +local function remove_duplicate_values(t) + local result, seen = {}, {} + for _, v in ipairs(t) do + if not seen[v] then + table.insert(result, v) + end + + seen[v] = true + end + + return result +end + +-- Check whether the OptionTypes is allowed for vim.opt +-- If it does not match, throw an error which indicates which option causes the error. +local function assert_valid_value(name, value, types) + local type_of_value = type(value) + for _, valid_type in ipairs(types) do + if valid_type == type_of_value then + return + end + end + + error( + string.format( + "Invalid option type '%s' for '%s', should be %s", + type_of_value, + name, + table.concat(types, ' or ') + ) + ) +end + +local function passthrough(_, x) + return x +end + +local function tbl_merge(left, right) + return vim.tbl_extend('force', left, right) +end + +local function tbl_remove(t, value) + if type(value) == 'string' then + t[value] = nil + else + for _, v in ipairs(value) do + t[v] = nil + end + end + + return t +end + +local valid_types = { + boolean = { 'boolean' }, + number = { 'number' }, + string = { 'string' }, + set = { 'string', 'table' }, + array = { 'string', 'table' }, + map = { 'string', 'table' }, +} + +-- Map of functions to take a Lua style value and convert to vimoption_T style value. +-- Each function takes (info, lua_value) -> vim_value +local to_vim_value = { + boolean = passthrough, + number = passthrough, + string = passthrough, + + set = function(info, value) + if type(value) == 'string' then + return value + end + + if info.flaglist and info.commalist then + local keys = {} + for k, v in pairs(value) do + if v then + table.insert(keys, k) + end + end + + table.sort(keys) + return table.concat(keys, ',') + else + local result = '' + for k, v in pairs(value) do + if v then + result = result .. k + end + end + + return result + end + end, + + array = function(info, value) + if type(value) == 'string' then + return value + end + if not info.allows_duplicates then + value = remove_duplicate_values(value) + end + return table.concat(value, ',') + end, + + map = function(_, value) + if type(value) == 'string' then + return value + end + + local result = {} + for opt_key, opt_value in pairs(value) do + table.insert(result, string.format('%s:%s', opt_key, opt_value)) + end + + table.sort(result) + return table.concat(result, ',') + end, +} + +--- Convert a Lua value to a vimoption_T value +local function convert_value_to_vim(name, info, value) + if value == nil then + return vim.NIL + end + + assert_valid_value(name, value, valid_types[info.metatype]) + + return to_vim_value[info.metatype](info, value) +end + +-- Map of OptionType to functions that take vimoption_T values and convert to Lua values. +-- Each function takes (info, vim_value) -> lua_value +local to_lua_value = { + boolean = passthrough, + number = passthrough, + string = passthrough, + + array = function(info, value) + if type(value) == 'table' then + if not info.allows_duplicates then + value = remove_duplicate_values(value) + end + + return value + end + + -- Empty strings mean that there is nothing there, + -- so empty table should be returned. + if value == '' then + return {} + end + + -- Handles unescaped commas in a list. + if string.find(value, ',,,') then + local left, right = unpack(vim.split(value, ',,,')) + + local result = {} + vim.list_extend(result, vim.split(left, ',')) + table.insert(result, ',') + vim.list_extend(result, vim.split(right, ',')) + + table.sort(result) + + return result + end + + if string.find(value, ',^,,', 1, true) then + local left, right = unpack(vim.split(value, ',^,,', true)) + + local result = {} + vim.list_extend(result, vim.split(left, ',')) + table.insert(result, '^,') + vim.list_extend(result, vim.split(right, ',')) + + table.sort(result) + + return result + end + + return vim.split(value, ',') + end, + + set = function(info, value) + if type(value) == 'table' then + return value + end + + -- Empty strings mean that there is nothing there, + -- so empty table should be returned. + if value == '' then + return {} + end + + assert(info.flaglist, 'That is the only one I know how to handle') + + if info.flaglist and info.commalist then + local split_value = vim.split(value, ',') + local result = {} + for _, v in ipairs(split_value) do + result[v] = true + end + + return result + else + local result = {} + for i = 1, #value do + result[value:sub(i, i)] = true + end + + return result + end + end, + + map = function(info, raw_value) + if type(raw_value) == 'table' then + return raw_value + end + + assert(info.commalist, 'Only commas are supported currently') + + local result = {} + + local comma_split = vim.split(raw_value, ',') + for _, key_value_str in ipairs(comma_split) do + local key, value = unpack(vim.split(key_value_str, ':')) + key = vim.trim(key) + + result[key] = value + end + + return result + end, +} + +--- Converts a vimoption_T style value to a Lua value +local function convert_value_to_lua(info, option_value) + return to_lua_value[info.metatype](info, option_value) +end + +local prepend_methods = { + number = function() + error("The '^' operator is not currently supported for") + end, + + string = function(left, right) + return right .. left + end, + + array = function(left, right) + for i = #right, 1, -1 do + table.insert(left, 1, right[i]) + end + + return left + end, + + map = tbl_merge, + set = tbl_merge, +} + +--- Handles the '^' operator +local function prepend_value(info, current, new) + return prepend_methods[info.metatype]( + convert_value_to_lua(info, current), + convert_value_to_lua(info, new) + ) +end + +local add_methods = { + number = function(left, right) + return left + right + end, + + string = function(left, right) + return left .. right + end, + + array = function(left, right) + for _, v in ipairs(right) do + table.insert(left, v) + end + + return left + end, + + map = tbl_merge, + set = tbl_merge, +} + +--- Handles the '+' operator +local function add_value(info, current, new) + return add_methods[info.metatype]( + convert_value_to_lua(info, current), + convert_value_to_lua(info, new) + ) +end + +local function remove_one_item(t, val) + if vim.tbl_islist(t) then + local remove_index = nil + for i, v in ipairs(t) do + if v == val then + remove_index = i + end + end + + if remove_index then + table.remove(t, remove_index) + end + else + t[val] = nil + end +end + +local remove_methods = { + number = function(left, right) + return left - right + end, + + string = function() + error('Subtraction not supported for strings.') + end, + + array = function(left, right) + if type(right) == 'string' then + remove_one_item(left, right) + else + for _, v in ipairs(right) do + remove_one_item(left, v) + end + end + + return left + end, + + map = tbl_remove, + set = tbl_remove, +} + +--- Handles the '-' operator +local function remove_value(info, current, new) + return remove_methods[info.metatype](convert_value_to_lua(info, current), new) +end + +local function create_option_accessor(scope) + local option_mt + + local function make_option(name, value) + local info = assert(options_info[name], 'Not a valid option name: ' .. name) + + if type(value) == 'table' and getmetatable(value) == option_mt then + assert(name == value._name, "must be the same value, otherwise that's weird.") + + value = value._value + end + + return setmetatable({ + _name = name, + _value = value, + _info = info, + }, option_mt) + end + + option_mt = { + -- To set a value, instead use: + -- opt[my_option] = value + _set = function(self) + local value = convert_value_to_vim(self._name, self._info, self._value) + api.nvim_set_option_value(self._name, value, { scope = scope }) + end, + + get = function(self) + return convert_value_to_lua(self._info, self._value) + end, + + append = function(self, right) + self._value = add_value(self._info, self._value, right) + self:_set() + end, + + __add = function(self, right) + return make_option(self._name, add_value(self._info, self._value, right)) + end, + + prepend = function(self, right) + self._value = prepend_value(self._info, self._value, right) + self:_set() + end, + + __pow = function(self, right) + return make_option(self._name, prepend_value(self._info, self._value, right)) + end, + + remove = function(self, right) + self._value = remove_value(self._info, self._value, right) + self:_set() + end, + + __sub = function(self, right) + return make_option(self._name, remove_value(self._info, self._value, right)) + end, + } + option_mt.__index = option_mt + + return setmetatable({}, { + __index = function(_, k) + return make_option(k, api.nvim_get_option_value(k, {})) + end, + + __newindex = function(_, k, v) + make_option(k, v):_set() + end, + }) +end + +vim.opt = create_option_accessor() +vim.opt_local = create_option_accessor('local') +vim.opt_global = create_option_accessor('global') -- cgit From 0ac3c4d6314df5fe40571a83e157a425ab7ce16d Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 15 Jul 2023 16:55:32 +0100 Subject: docs(lua): move function docs to lua files --- runtime/lua/vim/_meta/builtin.lua | 236 +++++++++++++++++++++++ runtime/lua/vim/_meta/diff.lua | 67 +++++++ runtime/lua/vim/_meta/json.lua | 35 ++++ runtime/lua/vim/_meta/misc.lua | 11 ++ runtime/lua/vim/_meta/mpack.lua | 13 ++ runtime/lua/vim/_meta/regex.lua | 34 ++++ runtime/lua/vim/_meta/spell.lua | 29 +++ runtime/lua/vim/_options.lua | 381 ++++++++++++++++++++++++++++++++++++-- runtime/lua/vim/highlight.lua | 65 +++++-- 9 files changed, 838 insertions(+), 33 deletions(-) create mode 100644 runtime/lua/vim/_meta/builtin.lua create mode 100644 runtime/lua/vim/_meta/diff.lua create mode 100644 runtime/lua/vim/_meta/json.lua create mode 100644 runtime/lua/vim/_meta/misc.lua create mode 100644 runtime/lua/vim/_meta/mpack.lua create mode 100644 runtime/lua/vim/_meta/regex.lua create mode 100644 runtime/lua/vim/_meta/spell.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua new file mode 100644 index 0000000000..384dd4351d --- /dev/null +++ b/runtime/lua/vim/_meta/builtin.lua @@ -0,0 +1,236 @@ +---@meta + +---@defgroup lua-builtin +--- +---@brief
help
+---vim.api.{func}({...})                                                    *vim.api*
+---    Invokes Nvim |API| function {func} with arguments {...}.
+---    Example: call the "nvim_get_current_line()" API function: >lua
+---        print(tostring(vim.api.nvim_get_current_line()))
+---
+---vim.NIL                                                                  *vim.NIL*
+---    Special value representing NIL in |RPC| and |v:null| in Vimscript
+---    conversion, and similar cases. Lua `nil` cannot be used as part of a Lua
+---    table representing a Dictionary or Array, because it is treated as
+---    missing: `{"foo", nil}` is the same as `{"foo"}`.
+---
+---vim.type_idx                                                        *vim.type_idx*
+---    Type index for use in |lua-special-tbl|. Specifying one of the values from
+---    |vim.types| allows typing the empty table (it is unclear whether empty Lua
+---    table represents empty list or empty array) and forcing integral numbers
+---    to be |Float|. See |lua-special-tbl| for more details.
+---
+---vim.val_idx                                                          *vim.val_idx*
+---    Value index for tables representing |Float|s. A table representing
+---    floating-point value 1.0 looks like this: >lua
+---        {
+---          [vim.type_idx] = vim.types.float,
+---          [vim.val_idx] = 1.0,
+---        }
+---<    See also |vim.type_idx| and |lua-special-tbl|.
+---
+---vim.types                                                              *vim.types*
+---    Table with possible values for |vim.type_idx|. Contains two sets of
+---    key-value pairs: first maps possible values for |vim.type_idx| to
+---    human-readable strings, second maps human-readable type names to values
+---    for |vim.type_idx|. Currently contains pairs for `float`, `array` and
+---        `dictionary` types.
+---
+---    Note: One must expect that values corresponding to `vim.types.float`,
+---    `vim.types.array` and `vim.types.dictionary` fall under only two following
+---    assumptions:
+---    1. Value may serve both as a key and as a value in a table. Given the
+---       properties of Lua tables this basically means “value is not `nil`”.
+---    2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
+---       same as `value`.
+---    No other restrictions are put on types, and it is not guaranteed that
+---    values corresponding to `vim.types.float`, `vim.types.array` and
+---    `vim.types.dictionary` will not change or that `vim.types` table will only
+---    contain values for these three types.
+---
+---                                                   *log_levels* *vim.log.levels*
+---Log levels are one of the values defined in `vim.log.levels`:
+---
+---    vim.log.levels.DEBUG
+---    vim.log.levels.ERROR
+---    vim.log.levels.INFO
+---    vim.log.levels.TRACE
+---    vim.log.levels.WARN
+---    vim.log.levels.OFF
+---
+---
+ +--- Returns true if the code is executing as part of a "fast" event handler, +--- where most of the API is disabled. These are low-level events (e.g. +--- |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input. +--- When this is `false` most API functions are callable (but may be subject +--- to other restrictions such as |textlock|). +function vim.in_fast_event() end + +--- Creates a special empty table (marked with a metatable), which Nvim to an +--- empty dictionary when translating Lua values to Vimscript or API types. +--- Nvim by default converts an empty table `{}` without this metatable to an +--- list/array. +--- +--- Note: If numeric keys are present in the table, Nvim ignores the metatable +--- marker and converts the dict to a list/array anyway. +function vim.empty_dict() end + +--- Sends {event} to {channel} via |RPC| and returns immediately. If {channel} +--- is 0, the event is broadcast to all channels. +--- +--- This function also works in a fast callback |lua-loop-callbacks|. +--- @param channel integer +--- @param method string +--- @param args? any[] +--- @param ...? any +function vim.rpcnotify(channel, method, args, ...) end + +--- Sends a request to {channel} to invoke {method} via |RPC| and blocks until +--- a response is received. +--- +--- Note: NIL values as part of the return value is represented as |vim.NIL| +--- special value +--- @param channel integer +--- @param method string +--- @param args? any[] +--- @param ...? any +function vim.rpcrequest(channel, method, args, ...) end + +--- Compares strings case-insensitively. +--- @param a string +--- @param b string +--- @return 0|1|-1 +--- if strings are +--- equal, {a} is greater than {b} or {a} is lesser than {b}, respectively. +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()|. +--- 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 +function vim.str_byteindex(str, index, use_utf16) end + +--- Convert byte index to UTF-32 and UTF-16 indices. If {index} is not +--- supplied, the length of the string is used. All indices are zero-based. +--- +--- Embedded NUL bytes are treated as terminating the string. Invalid UTF-8 +--- bytes, and embedded surrogates are counted as one code point each. An +--- {index} in the middle of a UTF-8 sequence is rounded upwards to the end of +--- that sequence. +--- @param str string +--- @param index? number +--- @return integer UTF-32 index +--- @return integer UTF-16 index +function vim.str_utfindex(str, index) end + +--- The result is a String, which is the text {str} converted from +--- encoding {from} to encoding {to}. When the conversion fails `nil` is +--- returned. When some characters could not be converted they +--- are replaced with "?". +--- The encoding names are whatever the iconv() library function +--- 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. +function vim.iconv(str, from, to, opts) end + +--- Schedules {callback} to be invoked soon by the main event-loop. Useful +--- to avoid |textlock| or other temporary restrictions. +--- @param callback fun() +function vim.schedule(callback) end + +--- Wait for {time} in milliseconds until {callback} returns `true`. +--- +--- Executes {callback} immediately and at approximately {interval} +--- milliseconds (default 200). Nvim still processes other events during +--- this time. +--- +--- Examples: +---
lua
+---
+--- ---
+--- -- Wait for 100 ms, allowing other events to process
+--- vim.wait(100, function() end)
+---
+--- ---
+--- -- Wait for 100 ms or until global variable set.
+--- vim.wait(100, function() return vim.g.waiting_for_var end)
+---
+--- ---
+--- -- Wait for 1 second or until global variable set, checking every ~500 ms
+--- vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
+---
+--- ---
+--- -- Schedule a function to set a value in 100ms
+--- vim.defer_fn(function() vim.g.timer_result = true end, 100)
+---
+--- -- Would wait ten seconds if results blocked. Actually only waits  100 ms
+--- if vim.wait(10000, function() return vim.g.timer_result end) then
+---   print('Only waiting a little bit of time!')
+--- end
+--- 
+--- +--- @param time integer Number of milliseconds to wait +--- @param callback? fun(): boolean Optional callback. Waits until {callback} returns true +--- @param interval? integer (Approximate) number of milliseconds to wait between polls +--- @param fast_only? boolean If true, only |api-fast| events will be processed. +--- If called from while in an |api-fast| event, will +--- automatically be set to `true`. +--- @return boolean, nil|-1|-2 +--- - If {callback} returns `true` during the {time}: `true, nil` +--- - If {callback} never returns `true` during the {time}: `false, -1` +--- - If {callback} is interrupted during the {time}: `false, -2` +--- - If {callback} errors, the error is raised. +function vim.wait(time, callback, interval, fast_only) end + +--- Attach to ui events, similar to |nvim_ui_attach()| but receive events +--- as Lua callback. Can be used to implement screen elements like +--- popupmenu or message handling in Lua. +--- +--- {options} should be a dictionary-like table, where `ext_...` options should +--- be set to true to receive events for the respective external element. +--- +--- {callback} receives event name plus additional parameters. See |ui-popupmenu| +--- and the sections below for event format for respective events. +--- +--- WARNING: This api is considered experimental. Usability will vary for +--- different screen elements. In particular `ext_messages` behavior is subject +--- to further changes and usability improvements. This is expected to be +--- used to handle messages when setting 'cmdheight' to zero (which is +--- likewise experimental). +--- +--- Example (stub for a |ui-popupmenu| implementation): +---
lua
+---
+---   ns = vim.api.nvim_create_namespace('my_fancy_pum')
+---
+---   vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...)
+---     if event == "popupmenu_show" then
+---       local items, selected, row, col, grid = ...
+---       print("display pum ", #items)
+---     elseif event == "popupmenu_select" then
+---       local selected = ...
+---       print("selected", selected)
+---     elseif event == "popupmenu_hide" then
+---       print("FIN")
+---     end
+---   end)
+--- 
+--- @param ns integer +--- @param options table +--- @param callback fun() +function vim.ui_attach(ns, options, callback) end + +--- Detach a callback previously attached with |vim.ui_attach()| for the +--- given namespace {ns}. +--- @param ns integer +function vim.ui_detach(ns) end diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua new file mode 100644 index 0000000000..8e8aaf8b64 --- /dev/null +++ b/runtime/lua/vim/_meta/diff.lua @@ -0,0 +1,67 @@ +---@meta + +--- Run diff on strings {a} and {b}. Any indices returned by this function, +--- either directly or via callback arguments, are 1-based. +--- +--- Examples: +---
lua
+---     vim.diff('a\\n', 'b\\nc\\n')
+---     -- =>
+---     -- @@ -1 +1,2 @@
+---     -- -a
+---     -- +b
+---     -- +c
+---
+---     vim.diff('a\\n', 'b\\nc\\n', {result_type = 'indices'})
+---     -- =>
+---     -- {
+---     --   {1, 1, 1, 2}
+---     -- }
+--- 
+--- +--- @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 +--- 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/json.lua b/runtime/lua/vim/_meta/json.lua new file mode 100644 index 0000000000..15e81d5004 --- /dev/null +++ b/runtime/lua/vim/_meta/json.lua @@ -0,0 +1,35 @@ +--- @meta + +--- @defgroup lua-json +--- +--- @brief The \*vim.json\* module provides encoding and decoding of Lua objects to and +--- from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|. + +--- Decodes (or "unpacks") the JSON-encoded {str} to a Lua object. +--- +--- - Decodes JSON "null" as |vim.NIL| (controllable by {opts}, see below). +--- - Decodes empty object as |vim.empty_dict()|. +--- - Decodes empty array as `{}` (empty Lua table). +--- +--- Example: +---
lua
+--- :lua vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}'))
+--- --> { bar = {}, foo = vim.empty_dict(), zub = vim.NIL }
+--- 
+--- Parameters: ~ +--- • {str} Stringified JSON data. +--- • {opts} Options map keys: +--- • luanil: { object: bool, array: bool } +--- • `luanil.object=true` converts `null` in JSON objects to +--- Lua `nil` instead of `vim.NIL`. +--- • `luanil.array=true` converts `null` in JSON arrays to Lua +--- `nil` instead of `vim.NIL`. +--- @param str string +--- @param opts? table +--- @return any +function vim.json.decode(str, opts) end + +--- Encodes (or "packs") Lua object {obj} as JSON in a Lua string. +--- @param obj any +--- @return string +function vim.json.encode(obj) end diff --git a/runtime/lua/vim/_meta/misc.lua b/runtime/lua/vim/_meta/misc.lua new file mode 100644 index 0000000000..954e8b4675 --- /dev/null +++ b/runtime/lua/vim/_meta/misc.lua @@ -0,0 +1,11 @@ +---@meta + +--- Invokes |vim-function| or |user-function| {func} with arguments {...}. +--- See also |vim.fn|. +--- Equivalent to: +---
lua
+---     vim.fn[func]({...})
+--- 
+--- @param func fun() +--- @param ... any +function vim.call(func, ...) end diff --git a/runtime/lua/vim/_meta/mpack.lua b/runtime/lua/vim/_meta/mpack.lua new file mode 100644 index 0000000000..2764177e5c --- /dev/null +++ b/runtime/lua/vim/_meta/mpack.lua @@ -0,0 +1,13 @@ +--- @meta + +--- @defgroup lua-mpack +--- +--- @brief The \*vim.mpack\* module provides encoding and decoding of Lua objects to and +--- from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|. + +--- Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object. +--- @param str string +function vim.mpack.decode(str) end + +--- Encodes (or "packs") Lua object {obj} as msgpack in a Lua string. +function vim.mpack.encode(obj) end diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua new file mode 100644 index 0000000000..afa78772da --- /dev/null +++ b/runtime/lua/vim/_meta/regex.lua @@ -0,0 +1,34 @@ +--- @meta + +--- @defgroup lua-regex +--- +--- @brief Vim regexes can be used directly from Lua. Currently they only allow +--- matching within a single line. + +--- Parse the Vim regex {re} and return a regex object. Regexes are "magic" +--- and case-sensitive by default, regardless of 'magic' and 'ignorecase'. +--- They can be controlled with flags, see |/magic| and |/ignorecase|. +--- @param re string +--- @return vim.regex +function vim.regex(re) end + +--- @class vim.regex +local regex = {} + +--- Match the string against the regex. If the string should match the regex +--- precisely, surround the regex with `^` and `$`. If the was a match, the +--- byte indices for the beginning and end of the match is returned. When +--- there is no match, `nil` is returned. As any integer is truth-y, +--- `regex:match()` can be directly used as a condition in an if-statement. +--- @param str string +function regex:match_str(str) end + +--- Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end} +--- are supplied, match only this byte index range. Otherwise see +--- |regex:match_str()|. If {start} is used, then the returned byte indices +--- will be relative {start}. +--- @param bufnr integer +--- @param line_idx integer +--- @param start? integer +--- @param end_? integer +function regex:match_line(bufnr, line_idx, start, end_) end diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua new file mode 100644 index 0000000000..926c8a686d --- /dev/null +++ b/runtime/lua/vim/_meta/spell.lua @@ -0,0 +1,29 @@ +--- @meta + +--- Check {str} for spelling errors. Similar to the Vimscript function +--- |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()|. +--- +--- Example: +---
lua
+---     vim.spell.check("the quik brown fox")
+---     -- =>
+---     -- {
+---     --     {'quik', 'bad', 5}
+---     -- }
+--- 
+--- +--- @param str string +--- @return {[1]: string, [2]: string, [3]: string}[] +--- List of tuples with three items: +--- - The badly spelled word. +--- - The type of the spelling error: +--- "bad" spelling mistake +--- "rare" rare word +--- "local" word only valid in another region +--- "caps" word should start with Capital +--- - The position in {str} where the word begins. +function vim.spell.check(str) end diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 41e6e8be86..d498ae0a2c 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -1,3 +1,108 @@ +---@defgroup lua-vimscript +--- +---@brief Nvim Lua provides an interface or "bridge" to Vimscript variables and +---functions, and editor commands and options. +--- +---Objects passed over this bridge are COPIED (marshalled): there are no +---"references". |lua-guide-variables| For example, using \`vim.fn.remove()\` on +---a Lua list copies the list object to Vimscript and does NOT modify the Lua +---list: +---
lua
+---    local list = { 1, 2, 3 }
+---    vim.fn.remove(list, 0)
+---    vim.print(list)  --> "{ 1, 2, 3 }"
+---
+ +---@addtogroup lua-vimscript +---@brief
help
+---vim.call({func}, {...})                                           *vim.call()*
+---    Invokes |vim-function| or |user-function| {func} with arguments {...}.
+---    See also |vim.fn|.
+---    Equivalent to: >lua
+---        vim.fn[func]({...})
+---<
+---vim.cmd({command})
+---    See |vim.cmd()|.
+---
+---vim.fn.{func}({...})                                                  *vim.fn*
+---    Invokes |vim-function| or |user-function| {func} with arguments {...}.
+---    To call autoload functions, use the syntax: >lua
+---        vim.fn['some\#function']({...})
+---<
+---    Unlike vim.api.|nvim_call_function()| this converts directly between Vim
+---    objects and Lua objects. If the Vim function returns a float, it will be
+---    represented directly as a Lua number. Empty lists and dictionaries both
+---    are represented by an empty table.
+---
+---    Note: |v:null| values as part of the return value is represented as
+---    |vim.NIL| special value
+---
+---    Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
+---    enumerates functions that were called at least once.
+---
+---    Note: The majority of functions cannot run in |api-fast| callbacks with some
+---    undocumented exceptions which are allowed.
+---
+---                                                           *lua-vim-variables*
+---The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed
+---from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables
+---described below. In this way you can easily read and modify global Vimscript
+---variables from Lua.
+---
+---Example: >lua
+---
+---    vim.g.foo = 5     -- Set the g:foo Vimscript variable.
+---    print(vim.g.foo)  -- Get and print the g:foo Vimscript variable.
+---    vim.g.foo = nil   -- Delete (:unlet) the Vimscript variable.
+---    vim.b[2].foo = 6  -- Set b:foo for buffer 2
+---<
+---
+---Note that setting dictionary fields directly will not write them back into
+---Nvim. This is because the index into the namespace simply returns a copy.
+---Instead the whole dictionary must be written as one. This can be achieved by
+---creating a short-lived temporary.
+---
+---Example: >lua
+---
+---    vim.g.my_dict.field1 = 'value'  -- Does not work
+---
+---    local my_dict = vim.g.my_dict   --
+---    my_dict.field1 = 'value'        -- Instead do
+---    vim.g.my_dict = my_dict         --
+---
+---vim.g                                                                  *vim.g*
+---    Global (|g:|) editor variables.
+---    Key with no value returns `nil`.
+---
+---vim.b                                                                  *vim.b*
+---    Buffer-scoped (|b:|) variables for the current buffer.
+---    Invalid or unset key returns `nil`. Can be indexed with
+---    an integer to access variables for a specific buffer.
+---
+---vim.w                                                                  *vim.w*
+---    Window-scoped (|w:|) variables for the current window.
+---    Invalid or unset key returns `nil`. Can be indexed with
+---    an integer to access variables for a specific window.
+---
+---vim.t                                                                  *vim.t*
+---    Tabpage-scoped (|t:|) variables for the current tabpage.
+---    Invalid or unset key returns `nil`. Can be indexed with
+---    an integer to access variables for a specific tabpage.
+---
+---vim.v                                                                  *vim.v*
+---    |v:| variables.
+---    Invalid or unset key returns `nil`.
+---
+---vim.env                                                              *vim.env*
+---    Environment variables defined in the editor session.
+---    See |expand-env| and |:let-environment| for the Vimscript behavior.
+---    Invalid or unset key returns `nil`.
+---    Example: >lua
+---        vim.env.FOO = 'bar'
+---        print(vim.env.TERM)
+---<
+---
+ local api = vim.api -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. @@ -13,6 +118,7 @@ local key_value_options = { --- Convert a vimoption_T style dictionary to the correct OptionType associated with it. ---@return string +---@private local function get_option_metatype(name, info) if info.type == 'string' then if info.flaglist then @@ -51,6 +157,7 @@ vim.env = setmetatable({}, { end, }) +---@private local function opt_validate(option_name, target_scope) local scope = options_info[option_name].scope if scope ~= target_scope then @@ -67,6 +174,7 @@ local function opt_validate(option_name, target_scope) end end +---@private local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) @@ -84,8 +192,7 @@ local function new_buf_opt_accessor(bufnr) }) end -vim.bo = new_buf_opt_accessor() - +---@private local function new_win_opt_accessor(winid, bufnr) return setmetatable({}, { __index = function(_, k) @@ -120,30 +227,118 @@ local function new_win_opt_accessor(winid, bufnr) }) end -vim.wo = new_win_opt_accessor() - --- vim global option --- this ONLY sets the global option. like `setglobal` -vim.go = setmetatable({}, { +---@addtogroup lua-vimscript +---@brief
help
+---` `                                                                *lua-options*
+---                                                             *lua-vim-options*
+---                                                                 *lua-vim-set*
+---                                                            *lua-vim-setlocal*
+---
+---Vim options can be accessed through |vim.o|, which behaves like Vimscript
+---|:set|.
+---
+---    Examples: ~
+---
+---    To set a boolean toggle:
+---        Vimscript: `set number`
+---        Lua:       `vim.o.number = true`
+---
+---    To set a string value:
+---        Vimscript: `set wildignore=*.o,*.a,__pycache__`
+---        Lua:       `vim.o.wildignore = '*.o,*.a,__pycache__'`
+---
+---Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
+---window-scoped options. Note that this must NOT be confused with
+---|local-options| and |:setlocal|. There is also |vim.go| that only accesses the
+---global value of a |global-local| option, see |:setglobal|.
+---
+ +---@addtogroup lua-vimscript +---@brief
help
+---vim.o                                                                  *vim.o*
+---    Get or set |options|. Like `:set`. Invalid key is an error.
+---
+---    Note: this works on both buffer-scoped and window-scoped options using the
+---    current buffer and window.
+---
+---    Example: >lua
+---        vim.o.cmdheight = 4
+---        print(vim.o.columns)
+---        print(vim.o.foo)     -- error: invalid key
+---<
+---
+vim.o = setmetatable({}, { __index = function(_, k) - return api.nvim_get_option_value(k, { scope = 'global' }) + return api.nvim_get_option_value(k, {}) end, __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, { scope = 'global' }) + return api.nvim_set_option_value(k, v, {}) end, }) --- vim `set` style options. --- it has no additional metamethod magic. -vim.o = setmetatable({}, { +---@addtogroup lua-vimscript +---@brief
help
+---vim.go                                                                *vim.go*
+---    Get or set global |options|. Like `:setglobal`. Invalid key is
+---    an error.
+---
+---    Note: this is different from |vim.o| because this accesses the global
+---    option value and thus is mostly useful for use with |global-local|
+---    options.
+---
+---    Example: >lua
+---        vim.go.cmdheight = 4
+---        print(vim.go.columns)
+---        print(vim.go.bar)     -- error: invalid key
+---<
+---
+vim.go = setmetatable({}, { __index = function(_, k) - return api.nvim_get_option_value(k, {}) + return api.nvim_get_option_value(k, { scope = 'global' }) end, __newindex = function(_, k, v) - return api.nvim_set_option_value(k, v, {}) + return api.nvim_set_option_value(k, v, { scope = 'global' }) end, }) +---@addtogroup lua-vimscript +---@brief
help
+---vim.bo[{bufnr}]                                                                *vim.bo*
+---    Get or set buffer-scoped |options| for the buffer with number {bufnr}.
+---    Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
+---    buffer is used. Invalid {bufnr} or key is an error.
+---
+---    Note: this is equivalent to both `:set` and `:setlocal`.
+---
+---    Example: >lua
+---        local bufnr = vim.api.nvim_get_current_buf()
+---        vim.bo[bufnr].buflisted = true    -- same as vim.bo.buflisted = true
+---        print(vim.bo.comments)
+---        print(vim.bo.baz)                 -- error: invalid key
+---
+vim.bo = new_buf_opt_accessor() + +---@addtogroup lua-vimscript +---@brief
help
+---vim.wo[{winid}][{bufnr}]                                                       *vim.wo*
+---    Get or set window-scoped |options| for the window with handle {winid} and
+---    buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
+---    `:set` otherwise. If [{winid}] is omitted then the current window is
+---    used. Invalid {winid}, {bufnr} or key is an error.
+---
+---    Note: only {bufnr} with value `0` (the current buffer in the window) is
+---    supported.
+---
+---    Example: >lua
+---        local winid = vim.api.nvim_get_current_win()
+---        vim.wo[winid].number = true    -- same as vim.wo.number = true
+---        print(vim.wo.foldmarker)
+---        print(vim.wo.quux)             -- error: invalid key
+---        vim.wo[winid][0].spell = false -- like ':setlocal nospell'
+---<
+---
+vim.wo = new_win_opt_accessor() + ---@brief [[ --- vim.opt, vim.opt_local and vim.opt_global implementation --- @@ -153,6 +348,7 @@ vim.o = setmetatable({}, { ---@brief ]] --- Preserves the order and does not mutate the original list +--- @private local function remove_duplicate_values(t) local result, seen = {}, {} for _, v in ipairs(t) do @@ -168,6 +364,7 @@ end -- Check whether the OptionTypes is allowed for vim.opt -- If it does not match, throw an error which indicates which option causes the error. +--- @private local function assert_valid_value(name, value, types) local type_of_value = type(value) for _, valid_type in ipairs(types) do @@ -186,14 +383,17 @@ local function assert_valid_value(name, value, types) ) end +--- @private local function passthrough(_, x) return x end +--- @private local function tbl_merge(left, right) return vim.tbl_extend('force', left, right) end +--- @private local function tbl_remove(t, value) if type(value) == 'string' then t[value] = nil @@ -275,6 +475,7 @@ local to_vim_value = { } --- Convert a Lua value to a vimoption_T value +--- @private local function convert_value_to_vim(name, info, value) if value == nil then return vim.NIL @@ -390,6 +591,7 @@ local to_lua_value = { } --- Converts a vimoption_T style value to a Lua value +--- @private local function convert_value_to_lua(info, option_value) return to_lua_value[info.metatype](info, option_value) end @@ -416,6 +618,7 @@ local prepend_methods = { } --- Handles the '^' operator +--- @private local function prepend_value(info, current, new) return prepend_methods[info.metatype]( convert_value_to_lua(info, current), @@ -445,6 +648,7 @@ local add_methods = { } --- Handles the '+' operator +--- @private local function add_value(info, current, new) return add_methods[info.metatype]( convert_value_to_lua(info, current), @@ -452,6 +656,7 @@ local function add_value(info, current, new) ) end +--- @private local function remove_one_item(t, val) if vim.tbl_islist(t) then local remove_index = nil @@ -495,13 +700,16 @@ local remove_methods = { } --- Handles the '-' operator +--- @private local function remove_value(info, current, new) return remove_methods[info.metatype](convert_value_to_lua(info, current), new) end +--- @private local function create_option_accessor(scope) local option_mt + --- @private local function make_option(name, value) local info = assert(options_info[name], 'Not a valid option name: ' .. name) @@ -570,6 +778,151 @@ local function create_option_accessor(scope) }) end +---@addtogroup lua-vimscript +---@brief
help
+---` `                                                                       *vim.opt_local*
+---                                                                       *vim.opt_global*
+---                                                                              *vim.opt*
+---
+---
+---A special interface |vim.opt| exists for conveniently interacting with list-
+---and map-style option from Lua: It allows accessing them as Lua tables and
+---offers object-oriented method for adding and removing entries.
+---
+---    Examples: ~
+---
+---    The following methods of setting a list-style option are equivalent:
+---        In Vimscript: >vim
+---            set wildignore=*.o,*.a,__pycache__
+---<
+---        In Lua using `vim.o`: >lua
+---            vim.o.wildignore = '*.o,*.a,__pycache__'
+---<
+---        In Lua using `vim.opt`: >lua
+---            vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
+---<
+---    To replicate the behavior of |:set+=|, use: >lua
+---
+---        vim.opt.wildignore:append { "*.pyc", "node_modules" }
+---<
+---    To replicate the behavior of |:set^=|, use: >lua
+---
+---        vim.opt.wildignore:prepend { "new_first_value" }
+---<
+---    To replicate the behavior of |:set-=|, use: >lua
+---
+---        vim.opt.wildignore:remove { "node_modules" }
+---<
+---    The following methods of setting a map-style option are equivalent:
+---        In Vimscript: >vim
+---            set listchars=space:_,tab:>~
+---<
+---        In Lua using `vim.o`: >lua
+---            vim.o.listchars = 'space:_,tab:>~'
+---<
+---        In Lua using `vim.opt`: >lua
+---            vim.opt.listchars = { space = '_', tab = '>~' }
+---<
+---
+---Note that |vim.opt| returns an `Option` object, not the value of the option,
+---which is accessed through |vim.opt:get()|:
+---
+---    Examples: ~
+---
+---    The following methods of getting a list-style option are equivalent:
+---        In Vimscript: >vim
+---            echo wildignore
+---<
+---        In Lua using `vim.o`: >lua
+---            print(vim.o.wildignore)
+---<
+---        In Lua using `vim.opt`: >lua
+---            vim.print(vim.opt.wildignore:get())
+---<
+---
+---In any of the above examples, to replicate the behavior |:setlocal|, use
+---`vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use
+---`vim.opt_global`.
+---
+ +--- @diagnostic disable-next-line:unused-local used for gen_vimdoc +local Option = {} + +---Returns a Lua-representation of the option. Boolean, number and string +---values will be returned in exactly the same fashion. +--- +---For values that are comma-separated lists, an array will be returned with +---the values as entries in the array:
lua
+---    vim.cmd [[set wildignore=*.pyc,*.o]]
+---
+---    vim.print(vim.opt.wildignore:get())
+---    -- { "*.pyc", "*.o", }
+---
+---    for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do
+---        print("Will ignore:", ignore_pattern)
+---    end
+---    -- Will ignore: *.pyc
+---    -- Will ignore: *.o
+---
+--- +---For values that are comma-separated maps, a table will be returned with +---the names as keys and the values as entries:
lua
+---    vim.cmd [[set listchars=space:_,tab:>~]]
+---
+---    vim.print(vim.opt.listchars:get())
+---    --  { space = "_", tab = ">~", }
+---
+---    for char, representation in pairs(vim.opt.listchars:get()) do
+---        print(char, "=>", representation)
+---    end
+---
+--- +---For values that are lists of flags, a set will be returned with the flags +---as keys and `true` as entries.
lua
+---    vim.cmd [[set formatoptions=njtcroql]]
+---
+---    vim.print(vim.opt.formatoptions:get())
+---    -- { n = true, j = true, c = true, ... }
+---
+---    local format_opts = vim.opt.formatoptions:get()
+---    if format_opts.j then
+---        print("J is enabled!")
+---    end
+---
+---@return string|integer|boolean|nil value of option +---@diagnostic disable-next-line:unused-local used for gen_vimdoc +function Option:get() end + +---Append a value to string-style options. See |:set+=| +--- +---These are equivalent:
lua
+---    vim.opt.formatoptions:append('j')
+---    vim.opt.formatoptions = vim.opt.formatoptions + 'j'
+---
+---@param value string Value to append +--- @diagnostic disable-next-line:unused-local used for gen_vimdoc +function Option:append(value) end + +---Prepend a value to string-style options. See |:set^=| +--- +---These are equivalent:
lua
+---    vim.opt.wildignore:prepend('*.o')
+---    vim.opt.wildignore = vim.opt.wildignore ^ '*.o'
+---
+---@param value string Value to prepend +---@diagnostic disable-next-line:unused-local used for gen_vimdoc +function Option:prepend(value) end + +---Remove a value from string-style options. See |:set-=| +--- +---These are equivalent:
lua
+---    vim.opt.wildignore:remove('*.pyc')
+---    vim.opt.wildignore = vim.opt.wildignore - '*.pyc'
+---
+---@param value string Value to remove +---@diagnostic disable-next-line:unused-local used for gen_vimdoc +function Option:remove(value) end + vim.opt = create_option_accessor() vim.opt_local = create_option_accessor('local') vim.opt_global = create_option_accessor('global') diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 86e1adb49e..97a5a1233f 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -1,3 +1,36 @@ +---@defgroup lua-highlight +--- +---@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}
+---
+--- +---
help
+---vim.highlight.priorities                            *vim.highlight.priorities*
+---
+---    Table with default priorities used for highlighting:
+---        • `syntax`: `50`, used for standard syntax highlighting
+---        • `treesitter`: `100`, used for tree-sitter-based highlighting
+---        • `semantic_tokens`: `125`, used for LSP semantic token highlighting
+---        • `diagnostics`: `150`, used for code analysis such as diagnostics
+---        • `user`: `200`, used for user-triggered highlights such as LSP document
+---          symbols or `on_yank` autocommands
+---
+ local api = vim.api local M = {} @@ -10,7 +43,7 @@ M.priorities = { user = 200, } ---- Highlight range between two positions +--- Apply highlight group to range of text. --- ---@param bufnr integer Buffer number to apply highlighting to ---@param ns integer Namespace to add highlight to @@ -18,9 +51,9 @@ M.priorities = { ---@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) +--- - 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) function M.range(bufnr, ns, higroup, start, finish, opts) opts = opts or {} local regtype = opts.regtype or 'v' @@ -46,22 +79,16 @@ end local yank_ns = api.nvim_create_namespace('hlyank') local yank_timer ---- Highlight the yanked region ---- ---- use from init.vim via ---- au TextYankPost * lua vim.highlight.on_yank() ---- customize highlight group and timeout via ---- au TextYankPost * lua vim.highlight.on_yank {higroup="IncSearch", timeout=150} ---- customize conditions (here: do not highlight a visual selection) via ---- au TextYankPost * lua vim.highlight.on_yank {on_visual=false} + +--- Highlight the yanked text --- --- @param opts table|nil Optional parameters --- - higroup highlight group for yanked region (default "IncSearch") --- - timeout time in ms before highlight is cleared (default 150) --- - on_macro highlight when executing macro (default false) --- - on_visual highlight when yanking visual selection (default true) --- - event event structure (default vim.v.event) --- - priority integer priority (default |vim.highlight.priorities|`.user`) +--- @param opts table|nil Optional parameters +--- - higroup highlight group for yanked region (default "IncSearch") +--- - timeout time in ms before highlight is cleared (default 150) +--- - on_macro highlight when executing macro (default false) +--- - on_visual highlight when yanking visual selection (default true) +--- - event event structure (default vim.v.event) +--- - priority integer priority (default |vim.highlight.priorities|`.user`) function M.on_yank(opts) vim.validate({ opts = { -- cgit From c2d7c2826ca77b0ca31bec511fdcdf1e4abaf946 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 17 Jul 2023 15:13:54 +0100 Subject: docs(lua): change *lua-foo* -> *vim.foo* --- runtime/lua/vim/_meta/builtin.lua | 2 +- runtime/lua/vim/_meta/json.lua | 4 ++-- runtime/lua/vim/_meta/mpack.lua | 4 ++-- runtime/lua/vim/_meta/regex.lua | 2 +- runtime/lua/vim/highlight.lua | 2 +- runtime/lua/vim/iter.lua | 4 ++-- runtime/lua/vim/version.lua | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 384dd4351d..a1786b2cdb 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -1,6 +1,6 @@ ---@meta ----@defgroup lua-builtin +---@defgroup vim.builtin --- ---@brief
help
 ---vim.api.{func}({...})                                                    *vim.api*
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
index 15e81d5004..2580b56870 100644
--- a/runtime/lua/vim/_meta/json.lua
+++ b/runtime/lua/vim/_meta/json.lua
@@ -1,8 +1,8 @@
 --- @meta
 
---- @defgroup lua-json
+--- @defgroup vim.json
 ---
---- @brief The \*vim.json\* module provides encoding and decoding of Lua objects to and
+--- This module provides encoding and decoding of Lua objects to and
 --- from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
 
 --- Decodes (or "unpacks") the JSON-encoded {str} to a Lua object.
diff --git a/runtime/lua/vim/_meta/mpack.lua b/runtime/lua/vim/_meta/mpack.lua
index 2764177e5c..1bcb6845b9 100644
--- a/runtime/lua/vim/_meta/mpack.lua
+++ b/runtime/lua/vim/_meta/mpack.lua
@@ -1,8 +1,8 @@
 --- @meta
 
---- @defgroup lua-mpack
+--- @defgroup vim.mpack
 ---
---- @brief The \*vim.mpack\* module provides encoding and decoding of Lua objects to and
+--- This module provides encoding and decoding of Lua objects to and
 --- from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
 
 --- Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object.
diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua
index afa78772da..0af1bccea5 100644
--- a/runtime/lua/vim/_meta/regex.lua
+++ b/runtime/lua/vim/_meta/regex.lua
@@ -1,6 +1,6 @@
 --- @meta
 
---- @defgroup lua-regex
+--- @defgroup vim.regex
 ---
 --- @brief Vim regexes can be used directly from Lua. Currently they only allow
 --- matching within a single line.
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 97a5a1233f..0eb782339c 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,4 +1,4 @@
----@defgroup lua-highlight
+---@defgroup vim.highlight
 ---
 ---@brief
 ---Nvim includes a function for highlighting a selection on yank.
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 245a33625e..6c1afcad91 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,6 +1,6 @@
----@defgroup lua-iter
+---@defgroup vim.iter
 ---
---- @brief The \*vim.iter\* module provides a generic interface for working with
+--- This module provides a generic interface for working with
 --- iterables: tables, lists, iterator functions, pair()/ipair()-like iterators,
 --- and \`vim.iter()\` objects.
 ---
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index cd28a9b54b..8ff8a19cb9 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -1,4 +1,4 @@
---- @defgroup lua-version
+--- @defgroup vim.version
 ---
 --- @brief The \`vim.version\` module provides functions for comparing versions and ranges
 --- conforming to the https://semver.org spec. Plugins, and plugin managers, can use this to check
-- 
cgit 


From 69d49727d7766d799aa1989bf67e763258b868e6 Mon Sep 17 00:00:00 2001
From: Lewis Russell 
Date: Mon, 17 Jul 2023 16:32:56 +0100
Subject: fix: luacheck

---
 runtime/lua/vim/_meta/builtin.lua | 2 ++
 runtime/lua/vim/_meta/diff.lua    | 2 ++
 runtime/lua/vim/_meta/json.lua    | 2 ++
 runtime/lua/vim/_meta/misc.lua    | 2 ++
 runtime/lua/vim/_meta/mpack.lua   | 2 ++
 runtime/lua/vim/_meta/regex.lua   | 4 +++-
 runtime/lua/vim/_meta/spell.lua   | 2 ++
 runtime/lua/vim/_options.lua      | 8 ++++----
 8 files changed, 19 insertions(+), 5 deletions(-)

(limited to 'runtime/lua/vim')

diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
index a1786b2cdb..1d7b1071b7 100644
--- a/runtime/lua/vim/_meta/builtin.lua
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -1,5 +1,7 @@
 ---@meta
 
+-- luacheck: no unused args
+
 ---@defgroup vim.builtin
 ---
 ---@brief 
help
diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua
index 8e8aaf8b64..246ac0c75a 100644
--- a/runtime/lua/vim/_meta/diff.lua
+++ b/runtime/lua/vim/_meta/diff.lua
@@ -1,5 +1,7 @@
 ---@meta
 
+-- luacheck: no unused args
+
 --- Run diff on strings {a} and {b}. Any indices returned by this function,
 --- either directly or via callback arguments, are 1-based.
 ---
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
index 2580b56870..76a6c7b733 100644
--- a/runtime/lua/vim/_meta/json.lua
+++ b/runtime/lua/vim/_meta/json.lua
@@ -1,5 +1,7 @@
 --- @meta
 
+-- luacheck: no unused args
+
 --- @defgroup vim.json
 ---
 --- This module provides encoding and decoding of Lua objects to and
diff --git a/runtime/lua/vim/_meta/misc.lua b/runtime/lua/vim/_meta/misc.lua
index 954e8b4675..8a76755962 100644
--- a/runtime/lua/vim/_meta/misc.lua
+++ b/runtime/lua/vim/_meta/misc.lua
@@ -1,5 +1,7 @@
 ---@meta
 
+-- luacheck: no unused args
+
 --- Invokes |vim-function| or |user-function| {func} with arguments {...}.
 --- See also |vim.fn|.
 --- Equivalent to:
diff --git a/runtime/lua/vim/_meta/mpack.lua b/runtime/lua/vim/_meta/mpack.lua
index 1bcb6845b9..54e097ad97 100644
--- a/runtime/lua/vim/_meta/mpack.lua
+++ b/runtime/lua/vim/_meta/mpack.lua
@@ -1,5 +1,7 @@
 --- @meta
 
+-- luacheck: no unused args
+
 --- @defgroup vim.mpack
 ---
 --- This module provides encoding and decoding of Lua objects to and
diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua
index 0af1bccea5..4bca67797a 100644
--- a/runtime/lua/vim/_meta/regex.lua
+++ b/runtime/lua/vim/_meta/regex.lua
@@ -1,5 +1,7 @@
 --- @meta
 
+-- luacheck: no unused args
+
 --- @defgroup vim.regex
 ---
 --- @brief Vim regexes can be used directly from Lua. Currently they only allow
@@ -13,7 +15,7 @@
 function vim.regex(re) end
 
 --- @class vim.regex
-local regex = {}
+local regex = {} -- luacheck: no unused
 
 --- Match the string against the regex. If the string should match the regex
 --- precisely, surround the regex with `^` and `$`. If the was a match, the
diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua
index 926c8a686d..d55867f769 100644
--- a/runtime/lua/vim/_meta/spell.lua
+++ b/runtime/lua/vim/_meta/spell.lua
@@ -1,5 +1,7 @@
 --- @meta
 
+-- luacheck: no unused args
+
 --- Check {str} for spelling errors. Similar to the Vimscript function
 --- |spellbadword()|.
 ---
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index d498ae0a2c..6dbe4cf64a 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -846,7 +846,7 @@ end
 ---
--- @diagnostic disable-next-line:unused-local used for gen_vimdoc -local Option = {} +local Option = {} -- luacheck: no unused ---Returns a Lua-representation of the option. Boolean, number and string ---values will be returned in exactly the same fashion. @@ -901,7 +901,7 @@ function Option:get() end ---
---@param value string Value to append --- @diagnostic disable-next-line:unused-local used for gen_vimdoc -function Option:append(value) end +function Option:append(value) end -- luacheck: no unused ---Prepend a value to string-style options. See |:set^=| --- @@ -911,7 +911,7 @@ function Option:append(value) end --- ---@param value string Value to prepend ---@diagnostic disable-next-line:unused-local used for gen_vimdoc -function Option:prepend(value) end +function Option:prepend(value) end -- luacheck: no unused ---Remove a value from string-style options. See |:set-=| --- @@ -921,7 +921,7 @@ function Option:prepend(value) end --- ---@param value string Value to remove ---@diagnostic disable-next-line:unused-local used for gen_vimdoc -function Option:remove(value) end +function Option:remove(value) end -- luacheck: no unused vim.opt = create_option_accessor() vim.opt_local = create_option_accessor('local') -- cgit From 6e9b204afbe5f16c44a2697aed07aafff36bf856 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 17 Jul 2023 16:39:57 +0100 Subject: fix: doc errors --- runtime/lua/vim/_meta/builtin.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 1d7b1071b7..980e40d61b 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -141,7 +141,7 @@ function vim.str_utfindex(str, index) end --- @param str string Text to convert --- @param from number Encoding of {str} --- @param to number Target encoding ---- @param opts? table +--- @param opts? table --- @return string|nil Converted string if conversion succeeds, `nil` otherwise. function vim.iconv(str, from, to, opts) end -- cgit From 1b9ccd38a12f8fdbdff51ef0b3ff363540f745ec Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Mon, 17 Jul 2023 18:27:16 +0200 Subject: feat(lsp)!: rename vim.lsp.get_active_clients to get_clients (#24113) --- runtime/lua/vim/lsp.lua | 45 ++++++++++++++++++++++++------------------ runtime/lua/vim/lsp/buf.lua | 11 +++++------ runtime/lua/vim/lsp/health.lua | 2 +- runtime/lua/vim/lsp/util.lua | 8 ++++---- 4 files changed, 36 insertions(+), 30 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index e9a1423d2d..97aab45f2c 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -612,7 +612,7 @@ do ---@private function changetracking.send_changes(bufnr, firstline, lastline, new_lastline) local groups = {} ---@type table - for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do local group = get_group(client) groups[group_key(group)] = group end @@ -734,7 +734,7 @@ end -- FIXME: DOC: Shouldn't need to use a dummy function -- --- LSP client object. You can get an active client object via ---- |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|. +--- |vim.lsp.get_client_by_id()| or |vim.lsp.get_clients()|. --- --- - Methods: --- @@ -880,7 +880,7 @@ function lsp.start(config, opts) if bufnr == nil or bufnr == 0 then bufnr = api.nvim_get_current_buf() end - for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do + 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) @@ -903,7 +903,7 @@ end function lsp.status() local percentage = nil local messages = {} - for _, client in ipairs(vim.lsp.get_active_clients()) do + for _, client in ipairs(vim.lsp.get_clients()) do for progress in client.progress do local value = progress.value if type(value) == 'table' and value.kind then @@ -1755,7 +1755,7 @@ local function text_document_did_save_handler(bufnr) bufnr = resolve_bufnr(bufnr) local uri = vim.uri_from_bufnr(bufnr) local text = once(buf_get_full_text) - for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do local name = api.nvim_buf_get_name(bufnr) local old_name = changetracking._get_and_set_name(client, bufnr, name) if old_name and name ~= old_name then @@ -1823,7 +1823,7 @@ function lsp.buf_attach_client(bufnr, client_id) buffer = bufnr, desc = 'vim.lsp: textDocument/willSave', callback = function(ctx) - for _, client in ipairs(lsp.get_active_clients({ bufnr = ctx.buf })) do + for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do local params = { textDocument = { uri = uri, @@ -1858,7 +1858,7 @@ function lsp.buf_attach_client(bufnr, client_id) on_lines = text_document_did_change_handler, on_reload = function() local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do + 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('textDocument/didClose', params) @@ -1868,7 +1868,7 @@ function lsp.buf_attach_client(bufnr, client_id) end, on_detach = function() local params = { textDocument = { uri = uri } } - for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do + 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('textDocument/didClose', params) @@ -1985,7 +1985,7 @@ end --- You can also use the `stop()` function on a |vim.lsp.client| object. --- To stop all clients: ---
lua
---- vim.lsp.stop_client(vim.lsp.get_active_clients())
+--- vim.lsp.stop_client(vim.lsp.get_clients())
 --- 
--- --- By default asks the server to shutdown, unless stop was requested @@ -2006,7 +2006,7 @@ function lsp.stop_client(client_id, force) end end ----@class vim.lsp.get_active_clients.filter +---@class vim.lsp.get_clients.filter ---@field id integer|nil Match clients by id ---@field bufnr integer|nil match clients attached to the given buffer ---@field name string|nil match clients by name @@ -2014,7 +2014,7 @@ end --- Get active clients. --- ----@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with +---@param filter vim.lsp.get_clients.filter|nil (table|nil) A table with --- key-value pairs used to filter the returned clients. --- The available keys are: --- - id (number): Only return clients with the given id @@ -2022,7 +2022,7 @@ end --- - name (string): Only return clients with the given name --- - method (string): Only return clients supporting the given method ---@return lsp.Client[]: List of |vim.lsp.client| objects -function lsp.get_active_clients(filter) +function lsp.get_clients(filter) validate({ filter = { filter, 't', true } }) filter = filter or {} @@ -2045,6 +2045,13 @@ function lsp.get_active_clients(filter) return clients end +---@private +---@deprecated +function lsp.get_active_clients(filter) + -- TODO: add vim.deprecate call after 0.10 is out for removal in 0.12 + return lsp.get_clients(filter) +end + api.nvim_create_autocmd('VimLeavePre', { desc = 'vim.lsp: exit handler', callback = function() @@ -2125,7 +2132,7 @@ function lsp.buf_request(bufnr, method, params, handler) bufnr = resolve_bufnr(bufnr) local method_supported = false - local clients = lsp.get_active_clients({ bufnr = bufnr }) + local clients = lsp.get_clients({ bufnr = bufnr }) local client_request_ids = {} for _, client in ipairs(clients) do if client.supports_method(method, { bufnr = bufnr }) then @@ -2173,7 +2180,7 @@ function lsp.buf_request_all(bufnr, method, params, handler) local expected_result_count = 0 local set_expected_result_count = once(function() - for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do if client.supports_method(method, { bufnr = bufnr }) then expected_result_count = expected_result_count + 1 end @@ -2240,7 +2247,7 @@ function lsp.buf_notify(bufnr, method, params) method = { method, 's' }, }) local resp = false - for _, client in ipairs(lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do if client.rpc.notify(method, params) then resp = true end @@ -2367,7 +2374,7 @@ function lsp.formatexpr(opts) return 0 end local bufnr = api.nvim_get_current_buf() - for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do if client.supports_method('textDocument/rangeFormatting') then local params = util.make_formatting_params() local end_line = vim.fn.getline(end_lnum) --[[@as string]] @@ -2424,10 +2431,10 @@ end --- ---@param bufnr (integer|nil): Buffer handle, or 0 for current ---@return table result is table of (client_id, client) pairs ----@deprecated Use |vim.lsp.get_active_clients()| instead. +---@deprecated Use |vim.lsp.get_clients()| instead. function lsp.buf_get_clients(bufnr) local result = {} - for _, client in ipairs(lsp.get_active_clients({ bufnr = resolve_bufnr(bufnr) })) do + for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do result[client.id] = client end return result @@ -2478,7 +2485,7 @@ end --- vim.print(client) --- end) --- ----@deprecated use lsp.get_active_clients({ bufnr = bufnr }) with regular loop +---@deprecated use lsp.get_clients({ bufnr = bufnr }) with regular loop function lsp.for_each_buffer_client(bufnr, fn) return for_each_buffer_client(bufnr, fn) end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index b238b5c221..2140d3ae37 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -197,7 +197,6 @@ end function M.format(options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local mode = api.nvim_get_mode().mode local range = options.range if not range and mode == 'v' or mode == 'V' then @@ -205,7 +204,7 @@ function M.format(options) end local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' - local clients = vim.lsp.get_active_clients({ + local clients = vim.lsp.get_clients({ id = options.id, bufnr = bufnr, name = options.name, @@ -271,7 +270,7 @@ end function M.rename(new_name, options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() - local clients = vim.lsp.get_active_clients({ + local clients = vim.lsp.get_clients({ bufnr = bufnr, name = options.name, -- Clients must at least support rename, prepareRename is optional @@ -468,7 +467,7 @@ end --- function M.list_workspace_folders() local workspace_folders = {} - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do for _, folder in pairs(client.workspace_folders or {}) do table.insert(workspace_folders, folder.name) end @@ -493,7 +492,7 @@ function M.add_workspace_folder(workspace_folder) { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }, {} ) - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do local found = false for _, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then @@ -526,7 +525,7 @@ function M.remove_workspace_folder(workspace_folder) { {} }, { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } ) - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do + for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do for idx, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 8817bb71de..023b1c26be 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -28,7 +28,7 @@ function M.check() local report_fn = (log_size / 1000000 > 100 and report_warn or report_info) report_fn(string.format('Log size: %d KB', log_size / 1000)) - local clients = vim.lsp.get_active_clients() + local clients = vim.lsp.get_clients() vim.health.start('vim.lsp: Active Clients') if next(clients) then for _, client in pairs(clients) do diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0da88f800e..59b9916f64 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -360,7 +360,7 @@ function M.get_progress_messages() local new_messages = {} local progress_remove = {} - for _, client in ipairs(vim.lsp.get_active_clients()) do + for _, client in ipairs(vim.lsp.get_clients()) do local groups = {} for progress in client.progress do local value = progress.value @@ -1841,7 +1841,7 @@ function M.locations_to_items(locations, offset_encoding) 'locations_to_items must be called with valid offset encoding', vim.log.levels.WARN ) - offset_encoding = vim.lsp.get_active_clients({ bufnr = 0 })[1].offset_encoding + offset_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding end local items = {} @@ -2036,7 +2036,7 @@ function M._get_offset_encoding(bufnr) local offset_encoding - for _, client in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do + for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do if client.offset_encoding == nil then vim.notify_once( string.format( @@ -2183,7 +2183,7 @@ function M.character_offset(buf, row, col, offset_encoding) 'character_offset must be called with valid offset encoding', vim.log.levels.WARN ) - offset_encoding = vim.lsp.get_active_clients({ bufnr = buf })[1].offset_encoding + offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding end -- If the col is past the EOL, use the line length. if col > #line then -- cgit From e4da418ba8388e94bb186e3f9a2004ee1e96f1e5 Mon Sep 17 00:00:00 2001 From: Mike <4576770+mike325@users.noreply.github.com> Date: Tue, 18 Jul 2023 08:36:04 +0200 Subject: fix(fs.lua): normalize slash truncation (#23753) Preserve last slash in windows' root drive directories --- runtime/lua/vim/fs.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 48d76e5d7e..d06dcb87cf 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -348,7 +348,11 @@ function M.normalize(path, opts) path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) end - return (path:gsub('\\', '/'):gsub('/+', '/'):gsub('(.)/$', '%1')) + path = path:gsub('\\', '/'):gsub('/+', '/') + if iswin and path:match('^%w:/$') then + return path + end + return (path:gsub('(.)/$', '%1')) end return M -- cgit From be74807eef13ff8c90d55cf8b22b01d6d33b1641 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 18 Jul 2023 15:42:30 +0100 Subject: docs(lua): more improvements (#24387) * docs(lua): teach lua2dox how to table * docs(lua): teach gen_vimdoc.py about local functions No more need to mark local functions with @private * docs(lua): mention @nodoc and @meta in dev-lua-doc * fixup! Co-authored-by: Justin M. Keyes --------- Co-authored-by: Justin M. Keyes --- runtime/lua/vim/_editor.lua | 20 ++-- runtime/lua/vim/_inspector.lua | 6 -- runtime/lua/vim/_options.lua | 142 ++++++++++++---------------- runtime/lua/vim/_system.lua | 3 - runtime/lua/vim/_watch.lua | 3 - runtime/lua/vim/diagnostic.lua | 24 ----- runtime/lua/vim/filetype.lua | 5 - runtime/lua/vim/fs.lua | 1 - runtime/lua/vim/highlight.lua | 19 ++-- runtime/lua/vim/iter.lua | 12 --- runtime/lua/vim/loader.lua | 8 +- runtime/lua/vim/lsp.lua | 44 +++------ runtime/lua/vim/lsp/_watchfiles.lua | 1 - runtime/lua/vim/lsp/buf.lua | 14 --- runtime/lua/vim/lsp/codelens.lua | 4 - runtime/lua/vim/lsp/diagnostic.lua | 9 +- runtime/lua/vim/lsp/handlers.lua | 3 - runtime/lua/vim/lsp/inlay_hint.lua | 9 -- runtime/lua/vim/lsp/log.lua | 17 ++-- runtime/lua/vim/lsp/rpc.lua | 68 ++++++------- runtime/lua/vim/lsp/semantic_tokens.lua | 7 -- runtime/lua/vim/lsp/sync.lua | 7 -- runtime/lua/vim/lsp/tagfunc.lua | 4 - runtime/lua/vim/lsp/util.lua | 25 +---- runtime/lua/vim/secure.lua | 2 - runtime/lua/vim/shared.lua | 83 ++++++++-------- runtime/lua/vim/treesitter.lua | 8 +- runtime/lua/vim/treesitter/dev.lua | 2 - runtime/lua/vim/treesitter/highlighter.lua | 2 +- runtime/lua/vim/treesitter/language.lua | 1 - runtime/lua/vim/treesitter/languagetree.lua | 5 - runtime/lua/vim/treesitter/query.lua | 9 -- runtime/lua/vim/uri.lua | 24 ++--- runtime/lua/vim/version.lua | 3 - 34 files changed, 178 insertions(+), 416 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index bb0aa97a0d..4372aef7b3 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -54,6 +54,7 @@ vim._extra = { inspect_pos = true, } +--- @private vim.log = { levels = { TRACE = 0, @@ -187,9 +188,7 @@ end ---@see |vim.print()| ---@see https://github.com/kikito/inspect.lua ---@see https://github.com/mpeterv/vinspect -local function inspect(object, options) -- luacheck: no unused - error(object, options) -- Stub for gen_vimdoc.py -end +vim.inspect = vim.inspect do local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false @@ -328,6 +327,7 @@ function vim.schedule_wrap(cb) end -- vim.fn.{func}(...) +---@private vim.fn = setmetatable({}, { __index = function(t, key) local _fn @@ -345,10 +345,13 @@ vim.fn = setmetatable({}, { end, }) +--- @private vim.funcref = function(viml_func_name) return vim.fn[viml_func_name] end +local VIM_CMD_ARG_MAX = 20 + --- Execute Vim script commands. --- --- Note that `vim.cmd` can be indexed with a command name to return a callable function to the @@ -389,12 +392,6 @@ end --- If a table, executes a single command. In this case, it is an alias --- to |nvim_cmd()| where `opts` is empty. ---@see |ex-cmd-index| -function vim.cmd(command) -- luacheck: no unused - error(command) -- Stub for gen_vimdoc.py -end - -local VIM_CMD_ARG_MAX = 20 - vim.cmd = setmetatable({}, { __call = function(_, command) if type(command) == 'table' then @@ -435,7 +432,6 @@ vim.cmd = setmetatable({}, { do local validate = vim.validate - ---@private local function make_dict_accessor(scope, handle) validate({ scope = { scope, 's' }, @@ -745,7 +741,6 @@ function vim._expand_pat(pat, env) end local keys = {} - ---@private local function insert_keys(obj) for k, _ in pairs(obj) do if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then @@ -1008,7 +1003,6 @@ end function vim._init_default_mappings() -- mappings - ---@private local function region_chunks(region) local chunks = {} local maxcol = vim.v.maxcol @@ -1020,7 +1014,6 @@ function vim._init_default_mappings() return chunks end - ---@private local function _visual_search(cmd) assert(cmd == '/' or cmd == '?') vim.api.nvim_feedkeys('\27', 'nx', true) -- Escape visual mode. @@ -1037,7 +1030,6 @@ function vim._init_default_mappings() vim.api.nvim_feedkeys(search_cmd, 'nx', true) end - ---@private local function map(mode, lhs, rhs) vim.keymap.set(mode, lhs, rhs, { desc = 'Nvim builtin' }) end diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index ecd39c35bc..3f7b9d2c23 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -56,7 +56,6 @@ function vim.inspect_pos(bufnr, row, col, filter) } -- resolve hl links - ---@private local function resolve_hl(data) if data.hl_group then local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group) @@ -91,7 +90,6 @@ function vim.inspect_pos(bufnr, row, col, filter) end --- Convert an extmark tuple into a table - --- @private local function to_map(extmark) extmark = { id = extmark[1], @@ -107,7 +105,6 @@ function vim.inspect_pos(bufnr, row, col, filter) end --- Check if an extmark overlaps this position - --- @private local function is_here(extmark) return (row >= extmark.row and row <= extmark.end_row) -- within the rows of the extmark and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col @@ -148,17 +145,14 @@ function vim.show_pos(bufnr, row, col, filter) local lines = { {} } - ---@private local function append(str, hl) table.insert(lines[#lines], { str, hl }) end - ---@private local function nl() table.insert(lines, {}) end - ---@private local function item(data, comment) append(' - ') append(data.hl_group, data.hl_group) diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index 6dbe4cf64a..d54e8b447c 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -92,15 +92,6 @@ ---vim.v *vim.v* --- |v:| variables. --- Invalid or unset key returns `nil`. ---- ----vim.env *vim.env* ---- Environment variables defined in the editor session. ---- See |expand-env| and |:let-environment| for the Vimscript behavior. ---- Invalid or unset key returns `nil`. ---- Example: >lua ---- vim.env.FOO = 'bar' ---- print(vim.env.TERM) ----< --- local api = vim.api @@ -118,7 +109,6 @@ local key_value_options = { --- Convert a vimoption_T style dictionary to the correct OptionType associated with it. ---@return string ----@private local function get_option_metatype(name, info) if info.type == 'string' then if info.flaglist then @@ -143,6 +133,14 @@ local options_info = setmetatable({}, { end, }) +---Environment variables defined in the editor session. +---See |expand-env| and |:let-environment| for the Vimscript behavior. +---Invalid or unset key returns `nil`. +---Example:
lua
+---    vim.env.FOO = 'bar'
+---    print(vim.env.TERM)
+---
+---@param var string vim.env = setmetatable({}, { __index = function(_, k) local v = vim.fn.getenv(k) @@ -157,7 +155,6 @@ vim.env = setmetatable({}, { end, }) ----@private local function opt_validate(option_name, target_scope) local scope = options_info[option_name].scope if scope ~= target_scope then @@ -174,7 +171,6 @@ local function opt_validate(option_name, target_scope) end end ----@private local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) @@ -192,7 +188,6 @@ local function new_buf_opt_accessor(bufnr) }) end ----@private local function new_win_opt_accessor(winid, bufnr) return setmetatable({}, { __index = function(_, k) @@ -253,19 +248,15 @@ end ---global value of a |global-local| option, see |:setglobal|. --- ----@addtogroup lua-vimscript ----@brief
help
----vim.o                                                                  *vim.o*
----    Get or set |options|. Like `:set`. Invalid key is an error.
+---Get or set |options|. Like `:set`. Invalid key is an error.
 ---
----    Note: this works on both buffer-scoped and window-scoped options using the
----    current buffer and window.
+---Note: this works on both buffer-scoped and window-scoped options using the
+---current buffer and window.
 ---
----    Example: >lua
----        vim.o.cmdheight = 4
----        print(vim.o.columns)
----        print(vim.o.foo)     -- error: invalid key
----<
+---Example: 
lua
+---    vim.o.cmdheight = 4
+---    print(vim.o.columns)
+---    print(vim.o.foo)     -- error: invalid key
 ---
vim.o = setmetatable({}, { __index = function(_, k) @@ -276,21 +267,17 @@ vim.o = setmetatable({}, { end, }) ----@addtogroup lua-vimscript ----@brief
help
----vim.go                                                                *vim.go*
----    Get or set global |options|. Like `:setglobal`. Invalid key is
----    an error.
----
----    Note: this is different from |vim.o| because this accesses the global
----    option value and thus is mostly useful for use with |global-local|
----    options.
----
----    Example: >lua
----        vim.go.cmdheight = 4
----        print(vim.go.columns)
----        print(vim.go.bar)     -- error: invalid key
----<
+---Get or set global |options|. Like `:setglobal`. Invalid key is
+---an error.
+---
+---Note: this is different from |vim.o| because this accesses the global
+---option value and thus is mostly useful for use with |global-local|
+---options.
+---
+---Example: 
lua
+---    vim.go.cmdheight = 4
+---    print(vim.go.columns)
+---    print(vim.go.bar)     -- error: invalid key
 ---
vim.go = setmetatable({}, { __index = function(_, k) @@ -301,41 +288,36 @@ vim.go = setmetatable({}, { end, }) ----@addtogroup lua-vimscript ----@brief
help
----vim.bo[{bufnr}]                                                                *vim.bo*
----    Get or set buffer-scoped |options| for the buffer with number {bufnr}.
----    Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
----    buffer is used. Invalid {bufnr} or key is an error.
----
----    Note: this is equivalent to both `:set` and `:setlocal`.
----
----    Example: >lua
----        local bufnr = vim.api.nvim_get_current_buf()
----        vim.bo[bufnr].buflisted = true    -- same as vim.bo.buflisted = true
----        print(vim.bo.comments)
----        print(vim.bo.baz)                 -- error: invalid key
+---Get or set buffer-scoped |options| for the buffer with number {bufnr}.
+---Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
+---buffer is used. Invalid {bufnr} or key is an error.
+---
+---Note: this is equivalent to both `:set` and `:setlocal`.
+---
+---Example: 
lua
+---    local bufnr = vim.api.nvim_get_current_buf()
+---    vim.bo[bufnr].buflisted = true    -- same as vim.bo.buflisted = true
+---    print(vim.bo.comments)
+---    print(vim.bo.baz)                 -- error: invalid key
 ---
+---@param bufnr integer|nil vim.bo = new_buf_opt_accessor() ----@addtogroup lua-vimscript ----@brief
help
----vim.wo[{winid}][{bufnr}]                                                       *vim.wo*
----    Get or set window-scoped |options| for the window with handle {winid} and
----    buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
----    `:set` otherwise. If [{winid}] is omitted then the current window is
----    used. Invalid {winid}, {bufnr} or key is an error.
----
----    Note: only {bufnr} with value `0` (the current buffer in the window) is
----    supported.
----
----    Example: >lua
----        local winid = vim.api.nvim_get_current_win()
----        vim.wo[winid].number = true    -- same as vim.wo.number = true
----        print(vim.wo.foldmarker)
----        print(vim.wo.quux)             -- error: invalid key
----        vim.wo[winid][0].spell = false -- like ':setlocal nospell'
----<
+---Get or set window-scoped |options| for the window with handle {winid} and
+---buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
+---`:set` otherwise. If [{winid}] is omitted then the current window is
+---used. Invalid {winid}, {bufnr} or key is an error.
+---
+---Note: only {bufnr} with value `0` (the current buffer in the window) is
+---supported.
+---
+---Example: 
lua
+---    local winid = vim.api.nvim_get_current_win()
+---    vim.wo[winid].number = true    -- same as vim.wo.number = true
+---    print(vim.wo.foldmarker)
+---    print(vim.wo.quux)             -- error: invalid key
+---    vim.wo[winid][0].spell = false -- like ':setlocal nospell'
+---
 ---
vim.wo = new_win_opt_accessor() @@ -348,7 +330,6 @@ vim.wo = new_win_opt_accessor() ---@brief ]] --- Preserves the order and does not mutate the original list ---- @private local function remove_duplicate_values(t) local result, seen = {}, {} for _, v in ipairs(t) do @@ -364,7 +345,6 @@ end -- Check whether the OptionTypes is allowed for vim.opt -- If it does not match, throw an error which indicates which option causes the error. ---- @private local function assert_valid_value(name, value, types) local type_of_value = type(value) for _, valid_type in ipairs(types) do @@ -383,17 +363,14 @@ local function assert_valid_value(name, value, types) ) end ---- @private local function passthrough(_, x) return x end ---- @private local function tbl_merge(left, right) return vim.tbl_extend('force', left, right) end ---- @private local function tbl_remove(t, value) if type(value) == 'string' then t[value] = nil @@ -475,7 +452,6 @@ local to_vim_value = { } --- Convert a Lua value to a vimoption_T value ---- @private local function convert_value_to_vim(name, info, value) if value == nil then return vim.NIL @@ -591,7 +567,6 @@ local to_lua_value = { } --- Converts a vimoption_T style value to a Lua value ---- @private local function convert_value_to_lua(info, option_value) return to_lua_value[info.metatype](info, option_value) end @@ -618,7 +593,6 @@ local prepend_methods = { } --- Handles the '^' operator ---- @private local function prepend_value(info, current, new) return prepend_methods[info.metatype]( convert_value_to_lua(info, current), @@ -648,7 +622,6 @@ local add_methods = { } --- Handles the '+' operator ---- @private local function add_value(info, current, new) return add_methods[info.metatype]( convert_value_to_lua(info, current), @@ -656,7 +629,6 @@ local function add_value(info, current, new) ) end ---- @private local function remove_one_item(t, val) if vim.tbl_islist(t) then local remove_index = nil @@ -700,16 +672,13 @@ local remove_methods = { } --- Handles the '-' operator ---- @private local function remove_value(info, current, new) return remove_methods[info.metatype](convert_value_to_lua(info, current), new) end ---- @private local function create_option_accessor(scope) local option_mt - --- @private local function make_option(name, value) local info = assert(options_info[name], 'Not a valid option name: ' .. name) @@ -923,6 +892,11 @@ function Option:prepend(value) end -- luacheck: no unused ---@diagnostic disable-next-line:unused-local used for gen_vimdoc function Option:remove(value) end -- luacheck: no unused +---@private vim.opt = create_option_accessor() + +---@private vim.opt_local = create_option_accessor('local') + +---@private vim.opt_global = create_option_accessor('global') diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua index ff566866c0..e6dab90425 100644 --- a/runtime/lua/vim/_system.lua +++ b/runtime/lua/vim/_system.lua @@ -30,7 +30,6 @@ local uv = vim.uv --- @field cmd string[] --- @field result? SystemCompleted ----@private ---@param state SystemState local function close_handles(state) for _, handle in pairs({ state.handle, state.stdin, state.stdout, state.stderr }) do @@ -128,7 +127,6 @@ function SystemObj:is_closing() return handle == nil or handle:is_closing() end ----@private ---@param output function|'false' ---@return uv_stream_t? ---@return function? Handler @@ -145,7 +143,6 @@ local function setup_output(output) return nil, nil end ----@private ---@param input string|string[]|true|nil ---@return uv_stream_t? ---@return string|string[]? diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 00b7416098..dc5f59f38b 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -7,7 +7,6 @@ M.FileChangeType = vim.tbl_add_reverse_lookup({ Deleted = 3, }) ----@private --- Joins filepath elements by static '/' separator --- ---@param ... (string) The path elements. @@ -16,7 +15,6 @@ local function filepath_join(...) return table.concat({ ... }, '/') end ----@private --- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle --- ---@param handle (uv_fs_event_t|uv_fs_poll_t) The handle to stop @@ -88,7 +86,6 @@ local default_poll_interval_ms = 2000 --- @field include_pattern? userdata --- @field exclude_pattern? userdata ----@private --- Implementation for poll, hiding internally-used parameters. --- ---@param path string diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 093bfb631e..f0adc4104e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -72,7 +72,6 @@ local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) local all_namespaces = {} ----@private local function to_severity(severity) if type(severity) == 'string' then return assert( @@ -83,7 +82,6 @@ local function to_severity(severity) return severity end ----@private local function filter_by_severity(severity, diagnostics) if not severity then return diagnostics @@ -104,7 +102,6 @@ local function filter_by_severity(severity, diagnostics) end, diagnostics) end ----@private local function count_sources(bufnr) local seen = {} local count = 0 @@ -119,7 +116,6 @@ local function count_sources(bufnr) return count end ----@private local function prefix_source(diagnostics) return vim.tbl_map(function(d) if not d.source then @@ -132,7 +128,6 @@ local function prefix_source(diagnostics) end, diagnostics) end ----@private local function reformat_diagnostics(format, diagnostics) vim.validate({ format = { format, 'f' }, @@ -146,7 +141,6 @@ local function reformat_diagnostics(format, diagnostics) return formatted end ----@private local function enabled_value(option, namespace) local ns = namespace and M.get_namespace(namespace) or {} if ns.opts and type(ns.opts[option]) == 'table' then @@ -160,7 +154,6 @@ local function enabled_value(option, namespace) return {} end ----@private local function resolve_optional_value(option, value, namespace, bufnr) if not value then return false @@ -180,7 +173,6 @@ local function resolve_optional_value(option, value, namespace, bufnr) end end ----@private local function get_resolved_options(opts, namespace, bufnr) local ns = namespace and M.get_namespace(namespace) or {} -- Do not use tbl_deep_extend so that an empty table can be used to reset to default values @@ -202,7 +194,6 @@ local diagnostic_severities = { } -- Make a map from DiagnosticSeverity -> Highlight Name ----@private local function make_highlight_map(base_name) local result = {} for k in pairs(diagnostic_severities) do @@ -243,7 +234,6 @@ local define_default_signs = (function() end end)() ----@private local function get_bufnr(bufnr) if not bufnr or bufnr == 0 then return api.nvim_get_current_buf() @@ -251,7 +241,6 @@ local function get_bufnr(bufnr) return bufnr end ----@private local function diagnostic_lines(diagnostics) if not diagnostics then return {} @@ -269,7 +258,6 @@ local function diagnostic_lines(diagnostics) return diagnostics_by_line end ----@private local function set_diagnostic_cache(namespace, bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do assert(diagnostic.lnum, 'Diagnostic line number is required') @@ -284,7 +272,6 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics) diagnostic_cache[bufnr][namespace] = diagnostics end ----@private local function restore_extmarks(bufnr, last) for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true }) @@ -306,7 +293,6 @@ local function restore_extmarks(bufnr, last) end end ----@private local function save_extmarks(namespace, bufnr) bufnr = get_bufnr(bufnr) if not diagnostic_attached_buffers[bufnr] then @@ -326,13 +312,11 @@ end local registered_autocmds = {} ----@private local function make_augroup_key(namespace, bufnr) local ns = M.get_namespace(namespace) return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name) end ----@private local function execute_scheduled_display(namespace, bufnr) local args = bufs_waiting_to_update[bufnr][namespace] if not args then @@ -348,7 +332,6 @@ end --- Table of autocmd events to fire the update for displaying new diagnostic information local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' } ----@private local function schedule_display(namespace, bufnr, args) bufs_waiting_to_update[bufnr][namespace] = args @@ -367,7 +350,6 @@ local function schedule_display(namespace, bufnr, args) end end ----@private local function clear_scheduled_display(namespace, bufnr) local key = make_augroup_key(namespace, bufnr) @@ -377,7 +359,6 @@ local function clear_scheduled_display(namespace, bufnr) end end ----@private local function get_diagnostics(bufnr, opts, clamp) opts = opts or {} @@ -392,7 +373,6 @@ local function get_diagnostics(bufnr, opts, clamp) end, }) - ---@private local function add(b, d) if not opts.lnum or d.lnum == opts.lnum then if clamp and api.nvim_buf_is_loaded(b) then @@ -416,7 +396,6 @@ local function get_diagnostics(bufnr, opts, clamp) end end - ---@private local function add_all_diags(buf, diags) for _, diagnostic in pairs(diags) do add(buf, diagnostic) @@ -450,7 +429,6 @@ local function get_diagnostics(bufnr, opts, clamp) return diagnostics end ----@private local function set_list(loclist, opts) opts = opts or {} local open = vim.F.if_nil(opts.open, true) @@ -474,7 +452,6 @@ local function set_list(loclist, opts) end end ----@private local function next_diagnostic(position, search_forward, bufnr, opts, namespace) position[1] = position[1] - 1 bufnr = get_bufnr(bufnr) @@ -525,7 +502,6 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) end end ----@private local function diagnostic_move_pos(opts, pos) opts = opts or {} diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 33d451315d..40f72a217d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2,7 +2,6 @@ local api = vim.api local M = {} ----@private local function starsetf(ft, opts) return { function(path, bufnr) @@ -2317,7 +2316,6 @@ local pattern = { -- luacheck: pop -- luacheck: pop ----@private local function sort_by_priority(t) local sorted = {} for k, v in pairs(t) do @@ -2341,7 +2339,6 @@ end local pattern_sorted = sort_by_priority(pattern) ----@private local function normalize_path(path, as_pattern) local normal = path:gsub('\\', '/') if normal:find('^~') then @@ -2456,7 +2453,6 @@ function M.add(filetypes) end end ----@private local function dispatch(ft, path, bufnr, ...) local on_detect if type(ft) == 'function' then @@ -2483,7 +2479,6 @@ end -- Lookup table/cache for patterns that contain an environment variable pattern, e.g. ${SOME_VAR}. local expand_env_lookup = {} ----@private local function match_pattern(name, path, tail, pat) if expand_env_lookup[pat] == nil then expand_env_lookup[pat] = pat:find('%${') ~= nil diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index d06dcb87cf..842767098c 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -218,7 +218,6 @@ function M.find(names, opts) local matches = {} - ---@private local function add(match) matches[#matches + 1] = M.normalize(match) if #matches == limit then diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 0eb782339c..14b0e71312 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -18,23 +18,18 @@ ---
vim
 ---    au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false}
 ---
---- ----
help
----vim.highlight.priorities                            *vim.highlight.priorities*
----
----    Table with default priorities used for highlighting:
----        • `syntax`: `50`, used for standard syntax highlighting
----        • `treesitter`: `100`, used for tree-sitter-based highlighting
----        • `semantic_tokens`: `125`, used for LSP semantic token highlighting
----        • `diagnostics`: `150`, used for code analysis such as diagnostics
----        • `user`: `200`, used for user-triggered highlights such as LSP document
----          symbols or `on_yank` autocommands
----
local api = vim.api local M = {} +--- Table with default priorities used for highlighting: +--- - `syntax`: `50`, used for standard syntax highlighting +--- - `treesitter`: `100`, used for tree-sitter-based highlighting +--- - `semantic_tokens`: `125`, used for LSP semantic token highlighting +--- - `diagnostics`: `150`, used for code analysis such as diagnostics +--- - `user`: `200`, used for user-triggered highlights such as LSP document +--- symbols or `on_yank` autocommands M.priorities = { syntax = 50, treesitter = 100, diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 6c1afcad91..7bffcc9c20 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -85,7 +85,6 @@ end --- Packed tables use this as their metatable local packedmt = {} ----@private local function unpack(t) if type(t) == 'table' and getmetatable(t) == packedmt then return _G.unpack(t, 1, t.n) @@ -93,7 +92,6 @@ local function unpack(t) return t end ----@private local function pack(...) local n = select('#', ...) if n > 1 then @@ -102,7 +100,6 @@ local function pack(...) return ... end ----@private local function sanitize(t) if type(t) == 'table' and getmetatable(t) == packedmt then -- Remove length tag @@ -120,7 +117,6 @@ end ---@param ... any Function arguments. ---@return boolean True if the iterator stage should continue, false otherwise ---@return any Function arguments. ----@private local function continue(...) if select('#', ...) > 0 then return false, ... @@ -137,7 +133,6 @@ end ---@param ... any Arguments to apply to f ---@return boolean True if the iterator pipeline should continue, false otherwise ---@return any Return values of f ----@private local function apply(f, ...) if select('#', ...) > 0 then return continue(f(...)) @@ -230,7 +225,6 @@ function Iter.map(self, f) --- values passed. ---@param ... any Values to return if cont is false. ---@return any - ---@private local function fn(cont, ...) if cont then return fn(apply(f, next(self))) @@ -270,7 +264,6 @@ end --- Takes all of the values returned by the previous stage --- in the pipeline as arguments. function Iter.each(self, f) - ---@private local function fn(...) if select('#', ...) > 0 then f(...) @@ -383,7 +376,6 @@ function Iter.fold(self, init, f) local acc = init --- Use a closure to handle var args returned from iterator - ---@private local function fn(...) if select(1, ...) ~= nil then acc = f(acc, ...) @@ -525,7 +517,6 @@ function Iter.find(self, f) local result = nil --- Use a closure to handle var args returned from iterator - ---@private local function fn(...) if select(1, ...) ~= nil then if f(...) then @@ -768,7 +759,6 @@ function Iter.any(self, pred) local any = false --- Use a closure to handle var args returned from iterator - ---@private local function fn(...) if select(1, ...) ~= nil then if pred(...) then @@ -792,7 +782,6 @@ end function Iter.all(self, pred) local all = true - ---@private local function fn(...) if select(1, ...) ~= nil then if not pred(...) then @@ -929,7 +918,6 @@ function Iter.new(src, ...) local s, var = ... --- Use a closure to handle var args returned from iterator - ---@private local function fn(...) if select(1, ...) ~= nil then var = select(1, ...) diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index f340dc85b5..4f4722b0c3 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -21,7 +21,10 @@ local M = {} ---@alias LoaderStats table +---@nodoc M.path = vim.fn.stdpath('cache') .. '/luac' + +---@nodoc M.enabled = false ---@class Loader @@ -58,7 +61,6 @@ function Loader.get_hash(path) return Loader._hashes[path] end ----@private local function normalize(path) return vim.fs.normalize(path, { expand_env = false }) end @@ -122,7 +124,6 @@ end --- @param path string --- @param mode integer --- @return string? data ---- @private local function readfile(path, mode) local f = uv.fs_open(path, 'r', mode) if f then @@ -310,7 +311,6 @@ function M.find(modname, opts) local results = {} -- Only continue if we haven't found anything yet or we want to find all - ---@private local function continue() return #results == 0 or opts.all end @@ -318,7 +318,6 @@ function M.find(modname, opts) -- Checks if the given paths contain the top-level module. -- If so, it tries to find the module path for the given module name. ---@param paths string[] - ---@private local function _find(paths) for _, path in ipairs(paths) do if topmod == '*' then @@ -504,7 +503,6 @@ end ---@private function M._inspect(opts) if opts and opts.print then - ---@private local function ms(nsec) return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms' end diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 97aab45f2c..35e4bc9dd8 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -66,7 +66,6 @@ lsp._request_name_to_capability = { -- TODO improve handling of scratch buffers with LSP attached. ----@private --- Concatenates and writes a list of strings to the Vim error buffer. --- ---@param ... string List to write to the buffer @@ -75,7 +74,6 @@ local function err_message(...) nvim_command('redraw') end ----@private --- Returns the buffer number for the given {bufnr}. --- ---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer @@ -101,7 +99,6 @@ function lsp._unsupported_method(method) return msg end ----@private --- Checks whether a given path is a directory. --- ---@param filename (string) path to check @@ -132,7 +129,6 @@ local format_line_ending = { ['mac'] = '\r', } ----@private ---@param bufnr (number) ---@return string local function buf_get_line_ending(bufnr) @@ -140,7 +136,6 @@ local function buf_get_line_ending(bufnr) end local client_index = 0 ----@private --- Returns a new, unused client id. --- ---@return integer client_id @@ -153,7 +148,6 @@ local active_clients = {} --- @type table local all_buffer_active_clients = {} --- @type table> local uninitialized_clients = {} --- @type table ----@private ---@param bufnr? integer ---@param fn fun(client: lsp.Client, client_id: integer, bufnr: integer) local function for_each_buffer_client(bufnr, fn, restrict_client_ids) @@ -185,9 +179,10 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids) end end --- Error codes to be used with `on_error` from |vim.lsp.start_client|. --- Can be used to look up the string from a the number or the number --- from the string. +--- Error codes to be used with `on_error` from |vim.lsp.start_client|. +--- Can be used to look up the string from a the number or the number +--- from the string. +--- @nodoc lsp.client_errors = tbl_extend( 'error', lsp_rpc.client_errors, @@ -196,7 +191,6 @@ lsp.client_errors = tbl_extend( }) ) ----@private --- Normalizes {encoding} to valid LSP encoding names. --- ---@param encoding (string) Encoding to normalize @@ -243,7 +237,6 @@ function lsp._cmd_parts(input) return cmd, cmd_args end ----@private --- Augments a validator function with support for optional (nil) values. --- ---@param fn (fun(v): boolean) The original validator function; should return a @@ -256,7 +249,6 @@ local function optional_validator(fn) end end ----@private --- Validates a client configuration as given to |vim.lsp.start_client()|. --- ---@param config (lsp.ClientConfig) @@ -308,7 +300,6 @@ local function validate_client_config(config) return cmd, cmd_args, offset_encoding end ----@private --- Returns full text of buffer {bufnr} as a string. --- ---@param bufnr (number) Buffer handle, or 0 for current. @@ -322,7 +313,6 @@ local function buf_get_full_text(bufnr) return text end ----@private --- Memoizes a function. On first run, the function return value is saved and --- immediately returned on subsequent runs. If the function returns a multival, --- only the first returned value will be memoized and returned. The function will only be run once, @@ -382,7 +372,6 @@ do --- @field debounce integer debounce duration in ms --- @field clients table clients using this state. {client_id, client} - ---@private ---@param group CTGroup ---@return string local function group_key(group) @@ -403,7 +392,6 @@ do end, }) - ---@private ---@return CTGroup local function get_group(client) local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true) @@ -419,7 +407,6 @@ do } end - ---@private ---@param state CTBufferState local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline) local prev_lines = state.lines @@ -543,8 +530,6 @@ do end end - ---@private - -- -- Adjust debounce time by taking time of last didChange notification into -- consideration. If the last didChange happened more than `debounce` time ago, -- debounce can be skipped and otherwise maybe reduced. @@ -567,7 +552,6 @@ do return math.max(debounce - ms_since_last_flush, 0) end - ---@private ---@param bufnr integer ---@param sync_kind integer protocol.TextDocumentSyncKind ---@param state CTGroupState @@ -694,7 +678,6 @@ do end end ----@private --- Default handler for the 'textDocument/didOpen' LSP notification. --- ---@param bufnr integer Number of the buffer, or 0 for current @@ -924,7 +907,6 @@ function lsp.status() return message end ----@private -- Determines whether the given option can be set by `set_defaults`. local function is_empty_or_default(bufnr, option) if vim.bo[bufnr][option] == '' then @@ -1138,7 +1120,6 @@ function lsp.start_client(config) local dispatch = {} - ---@private --- Returns the handler associated with an LSP method. --- Returns the default handler if the user hasn't set a custom one. --- @@ -1208,7 +1189,6 @@ function lsp.start_client(config) end end - ---@private --- Reset defaults set by `set_defaults`. --- Must only be called if the last client attached to a buffer exits. local function reset_defaults(bufnr) @@ -1337,7 +1317,6 @@ function lsp.start_client(config) -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes. uninitialized_clients[client_id] = client - ---@private local function initialize() local valid_traces = { off = 'off', @@ -1749,7 +1728,6 @@ do end end ----@private ---Buffer lifecycle handler for textDocument/didSave local function text_document_did_save_handler(bufnr) bufnr = resolve_bufnr(bufnr) @@ -2082,7 +2060,6 @@ api.nvim_create_autocmd('VimLeavePre', { local poll_time = 50 - ---@private local function check_clients_closed() for client_id, timeout in pairs(timeouts) do timeouts[client_id] = timeout - poll_time @@ -2440,12 +2417,13 @@ function lsp.buf_get_clients(bufnr) return result end --- Log level dictionary with reverse lookup as well. --- --- Can be used to lookup the number from the name or the --- name from the number. --- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" --- Level numbers begin with "TRACE" at 0 +--- Log level dictionary with reverse lookup as well. +--- +--- Can be used to lookup the number from the name or the +--- name from the number. +--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" +--- Level numbers begin with "TRACE" at 0 +--- @nodoc lsp.log_levels = log.levels --- Sets the global log level for LSP logging. diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 87938fe4d5..b66f2f6f32 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -5,7 +5,6 @@ local lpeg = vim.lpeg local M = {} ----@private --- Parses the raw pattern into an |lpeg| pattern. LPeg patterns natively support the "this" or "that" --- alternative constructions described in the LSP spec that cannot be expressed in a standard Lua pattern. --- diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 2140d3ae37..46f582dc7e 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -5,7 +5,6 @@ local npcall = vim.F.npcall local M = {} ----@private --- Sends an async request to all active clients attached to the current --- buffer. --- @@ -45,7 +44,6 @@ function M.hover() request('textDocument/hover', params) end ----@private local function request_with_options(name, params, options) local req_handler if options then @@ -120,7 +118,6 @@ function M.completion(context) return request('textDocument/completion', params) end ----@private ---@param bufnr integer ---@param mode "v"|"V" ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing @@ -218,7 +215,6 @@ function M.format(options) vim.notify('[LSP] Format request failed, no matching language servers.') end - ---@private local function set_range(client, params) if range then local range_params = @@ -289,7 +285,6 @@ function M.rename(new_name, options) -- Compute early to account for cursor movements after going async local cword = vim.fn.expand('') - ---@private local function get_text_at_range(range, offset_encoding) return api.nvim_buf_get_text( bufnr, @@ -307,7 +302,6 @@ function M.rename(new_name, options) return end - ---@private local function rename(name) local params = util.make_position_params(win, client.offset_encoding) params.newName = name @@ -408,7 +402,6 @@ function M.document_symbol(options) request_with_options('textDocument/documentSymbol', params, options) end ----@private local function pick_call_hierarchy_item(call_hierarchy_items) if not call_hierarchy_items then return @@ -428,7 +421,6 @@ local function pick_call_hierarchy_item(call_hierarchy_items) return choice end ----@private local function call_hierarchy(method) local params = util.make_position_params() request('textDocument/prepareCallHierarchy', params, function(err, result, ctx) @@ -579,8 +571,6 @@ function M.clear_references() util.buf_clear_references() end ----@private --- --- This is not public because the main extension point is --- vim.ui.select which can be overridden independently. --- @@ -592,7 +582,6 @@ end local function on_code_action_results(results, ctx, options) local action_tuples = {} - ---@private local function action_filter(a) -- filter by specified action kind if options and options.context and options.context.only then @@ -632,7 +621,6 @@ local function on_code_action_results(results, ctx, options) return end - ---@private local function apply_action(action, client) if action.edit then util.apply_workspace_edit(action.edit, client.offset_encoding) @@ -643,7 +631,6 @@ local function on_code_action_results(results, ctx, options) end end - ---@private local function on_user_choice(action_tuple) if not action_tuple then return @@ -701,7 +688,6 @@ end --- Requests code actions from all clients and calls the handler exactly once --- with all aggregated results ----@private local function code_action_request(params, options) local bufnr = api.nvim_get_current_buf() local method = 'textDocument/codeAction' diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index 67104cc40b..a516238ae0 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -26,7 +26,6 @@ local namespaces = setmetatable({}, { ---@private M.__namespaces = namespaces ----@private local function execute_lens(lens, bufnr, client_id) local line = lens.range.start.line api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1) @@ -89,7 +88,6 @@ function M.run() end end ----@private local function resolve_bufnr(bufnr) return bufnr == 0 and api.nvim_get_current_buf() or bufnr end @@ -186,7 +184,6 @@ function M.save(lenses, bufnr, client_id) lenses_by_client[client_id] = lenses end ----@private local function resolve_lenses(lenses, bufnr, client_id, callback) lenses = lenses or {} local num_lens = vim.tbl_count(lenses) @@ -195,7 +192,6 @@ local function resolve_lenses(lenses, bufnr, client_id, callback) return end - ---@private local function countdown() num_lens = num_lens - 1 if num_lens == 0 then diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 3efa5c51ff..c2cf7c6ba5 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -5,7 +5,7 @@ local protocol = require('vim.lsp.protocol') local M = {} local DEFAULT_CLIENT_ID = -1 ----@private + local function get_client_id(client_id) if client_id == nil then client_id = DEFAULT_CLIENT_ID @@ -14,7 +14,6 @@ local function get_client_id(client_id) return client_id end ----@private ---@param severity lsp.DiagnosticSeverity local function severity_lsp_to_vim(severity) if type(severity) == 'string' then @@ -23,7 +22,6 @@ local function severity_lsp_to_vim(severity) return severity end ----@private ---@return lsp.DiagnosticSeverity local function severity_vim_to_lsp(severity) if type(severity) == 'string' then @@ -32,7 +30,6 @@ local function severity_vim_to_lsp(severity) return severity end ----@private ---@return integer local function line_byte_from_position(lines, lnum, col, offset_encoding) if not lines or offset_encoding == 'utf-8' then @@ -48,7 +45,6 @@ local function line_byte_from_position(lines, lnum, col, offset_encoding) return col end ----@private local function get_buf_lines(bufnr) if vim.api.nvim_buf_is_loaded(bufnr) then return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) @@ -73,7 +69,6 @@ local function get_buf_lines(bufnr) return lines end ---- @private --- @param diagnostic lsp.Diagnostic --- @param client_id integer --- @return table? @@ -96,7 +91,6 @@ local function tags_lsp_to_vim(diagnostic, client_id) return tags end ----@private ---@param diagnostics lsp.Diagnostic[] ---@param bufnr integer ---@param client_id integer @@ -133,7 +127,6 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) end, diagnostics) end ---- @private --- @param diagnostics Diagnostic[] --- @return lsp.Diagnostic[] local function diagnostic_vim_to_lsp(diagnostics) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 20c4d7458b..ce3db68618 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -7,7 +7,6 @@ local M = {} -- FIXME: DOC: Expose in vimdocs ----@private --- Writes to error buffer. ---@param ... string Will be concatenated before being written local function err_message(...) @@ -246,7 +245,6 @@ M['textDocument/references'] = function(_, result, ctx, config) end end ----@private --- 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 @@ -380,7 +378,6 @@ end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover M['textDocument/hover'] = M.hover ----@private --- Jumps to a location. Used as a handler for multiple LSP methods. ---@param _ nil not used ---@param result (table) result of LSP method; a location or a list of locations. diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 6426b0c039..bec6f33e93 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -17,7 +17,6 @@ local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) --- Reset the request debounce timer of a buffer ----@private local function reset_timer(reset_bufnr) local timer = bufstates[reset_bufnr].timer if timer then @@ -63,7 +62,6 @@ function M.on_inlayhint(err, result, ctx, _) end local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false) - ---@private local function pos_to_byte(position) local col = position.character if col > 0 then @@ -89,7 +87,6 @@ function M.on_inlayhint(err, result, ctx, _) api.nvim__buf_redraw_range(bufnr, 0, -1) end ----@private local function resolve_bufnr(bufnr) return bufnr > 0 and bufnr or api.nvim_get_current_buf() end @@ -100,7 +97,6 @@ end --- - bufnr (integer, default: 0): Buffer whose hints to refresh --- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer --- ----@private local function refresh(opts) opts = opts or {} local bufnr = resolve_bufnr(opts.bufnr or 0) @@ -159,7 +155,6 @@ end --- Clear inlay hints ---@param bufnr (integer) Buffer handle, or 0 for current ----@private local function clear(bufnr) bufnr = resolve_bufnr(bufnr) if not bufstates[bufnr] then @@ -178,7 +173,6 @@ local function clear(bufnr) api.nvim__buf_redraw_range(bufnr, 0, -1) end ----@private local function make_request(request_bufnr) reset_timer(request_bufnr) refresh({ bufnr = request_bufnr }) @@ -186,7 +180,6 @@ end --- Enable inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current ----@private local function enable(bufnr) bufnr = resolve_bufnr(bufnr) local bufstate = bufstates[bufnr] @@ -228,7 +221,6 @@ end --- Disable inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current ----@private local function disable(bufnr) bufnr = resolve_bufnr(bufnr) if bufstates[bufnr] and bufstates[bufnr].enabled then @@ -240,7 +232,6 @@ end --- Toggle inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current ----@private local function toggle(bufnr) bufnr = resolve_bufnr(bufnr) local bufstate = bufstates[bufnr] diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 033f93bd6e..6d2e0bc292 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -2,14 +2,12 @@ local log = {} --- FIXME: DOC --- Should be exposed in the vim docs. --- --- Log level dictionary with reverse lookup as well. --- --- Can be used to lookup the number from the name or the name from the number. --- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" --- Level numbers begin with "TRACE" at 0 +--- Log level dictionary with reverse lookup as well. +--- +--- Can be used to lookup the number from the name or the name from the number. +--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF" +--- Level numbers begin with "TRACE" at 0 +--- @nodoc log.levels = vim.deepcopy(vim.log.levels) -- Default log level is warn. @@ -20,7 +18,6 @@ local format_func = function(arg) end do - ---@private local function notify(msg, level) if vim.in_fast_event() then vim.schedule(function() @@ -32,7 +29,6 @@ do end local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/' - ---@private local function path_join(...) return table.concat(vim.tbl_flatten({ ... }), path_sep) end @@ -50,7 +46,6 @@ do end local logfile, openerr - ---@private --- Opens log file. Returns true if file is open, false on error local function open_logfile() -- Try to open file only once diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index c110c3a67c..a77af937b3 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -5,7 +5,6 @@ local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedu local is_win = uv.os_uname().version:find('Windows') ----@private --- Checks whether a given path exists and is a directory. ---@param filename (string) path to check ---@return boolean @@ -14,7 +13,6 @@ local function is_dir(filename) return stat and stat.type == 'directory' or false end ----@private --- Embeds the given string into a table and correctly computes `Content-Length`. --- ---@param encoded_message (string) @@ -28,7 +26,6 @@ local function format_message_with_content_length(encoded_message) }) end ----@private --- Parses an LSP Message's header --- ---@param header string: The header to parse. @@ -60,7 +57,6 @@ local header_start_pattern = ('content'):gsub('%w', function(c) return '[' .. c .. c:upper() .. ']' end) ----@private --- The actual workhorse. local function request_parser_loop() local buffer = '' -- only for header part @@ -115,8 +111,11 @@ local function request_parser_loop() end end +local M = {} + --- Mapping of error codes used by the client -local client_errors = { +--- @nodoc +M.client_errors = { INVALID_SERVER_MESSAGE = 1, INVALID_SERVER_JSON = 2, NO_RESULT_CALLBACK_FOUND = 3, @@ -126,13 +125,13 @@ local client_errors = { SERVER_RESULT_CALLBACK_ERROR = 7, } -client_errors = vim.tbl_add_reverse_lookup(client_errors) +M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors) --- Constructs an error message from an LSP error object. --- ---@param err (table) The error object ---@returns (string) The formatted error message -local function format_rpc_error(err) +function M.format_rpc_error(err) validate({ err = { err, 't' }, }) @@ -163,7 +162,7 @@ end ---@param code integer RPC error code defined in `vim.lsp.protocol.ErrorCodes` ---@param message string|nil arbitrary message to send to server ---@param data any|nil arbitrary data to send to server -local function rpc_response_error(code, message, data) +function M.rpc_response_error(code, message, data) -- TODO should this error or just pick a sane error (like InternalError)? local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code') return setmetatable({ @@ -171,7 +170,7 @@ local function rpc_response_error(code, message, data) message = message or code_name, data = data, }, { - __tostring = format_rpc_error, + __tostring = M.format_rpc_error, }) end @@ -185,6 +184,7 @@ local default_dispatchers = {} function default_dispatchers.notification(method, params) local _ = log.debug() and log.debug('notification', method, params) end + ---@private --- Default dispatcher for requests sent to an LSP server. --- @@ -194,8 +194,9 @@ end ---@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound` function default_dispatchers.server_request(method, params) local _ = log.debug() and log.debug('server_request', method, params) - return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound) + return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound) end + ---@private --- Default dispatcher for when a client exits. --- @@ -205,6 +206,7 @@ end function default_dispatchers.on_exit(code, signal) local _ = log.info() and log.info('client_exit', { code = code, signal = signal }) end + ---@private --- Default dispatcher for client errors. --- @@ -212,11 +214,11 @@ end ---@param err (any): Details about the error ---any) function default_dispatchers.on_error(code, err) - local _ = log.error() and log.error('client_error:', client_errors[code], err) + local _ = log.error() and log.error('client_error:', M.client_errors[code], err) end ---@private -local function create_read_loop(handle_body, on_no_chunk, on_error) +function M.create_read_loop(handle_body, on_no_chunk, on_error) local parse_chunk = coroutine.wrap(request_parser_loop) parse_chunk() return function(err, chunk) @@ -329,7 +331,7 @@ end ---@private function Client:on_error(errkind, ...) - assert(client_errors[errkind]) + assert(M.client_errors[errkind]) -- TODO what to do if this fails? pcall(self.dispatchers.on_error, errkind, ...) end @@ -356,7 +358,7 @@ end function Client:handle_body(body) local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) if not ok then - self:on_error(client_errors.INVALID_SERVER_JSON, decoded) + self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded) return end local _ = log.debug() and log.debug('rpc.receive', decoded) @@ -369,7 +371,7 @@ function Client:handle_body(body) coroutine.wrap(function() local status, result status, result, err = self:try_call( - client_errors.SERVER_REQUEST_HANDLER_ERROR, + M.client_errors.SERVER_REQUEST_HANDLER_ERROR, self.dispatchers.server_request, decoded.method, decoded.params @@ -401,7 +403,7 @@ function Client:handle_body(body) end else -- On an exception, result will contain the error message. - err = rpc_response_error(protocol.ErrorCodes.InternalError, result) + err = M.rpc_response_error(protocol.ErrorCodes.InternalError, result) result = nil end self:send_response(decoded.id, err, result) @@ -454,34 +456,33 @@ function Client:handle_body(body) }) if decoded.error then decoded.error = setmetatable(decoded.error, { - __tostring = format_rpc_error, + __tostring = M.format_rpc_error, }) end self:try_call( - client_errors.SERVER_RESULT_CALLBACK_ERROR, + M.client_errors.SERVER_RESULT_CALLBACK_ERROR, callback, decoded.error, decoded.result ) else - self:on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded) + self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded) local _ = log.error() and log.error('No callback found for server response id ' .. result_id) end elseif type(decoded.method) == 'string' then -- Notification self:try_call( - client_errors.NOTIFICATION_HANDLER_ERROR, + M.client_errors.NOTIFICATION_HANDLER_ERROR, self.dispatchers.notification, decoded.method, decoded.params ) else -- Invalid server message - self:on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) + self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded) end end ----@private ---@return RpcClient local function new_client(dispatchers, transport) local state = { @@ -494,7 +495,6 @@ local function new_client(dispatchers, transport) return setmetatable(state, { __index = Client }) end ----@private ---@param client RpcClient local function public_client(client) local result = {} @@ -531,7 +531,6 @@ local function public_client(client) return result end ----@private local function merge_dispatchers(dispatchers) if dispatchers then local user_dispatchers = dispatchers @@ -565,7 +564,7 @@ end ---@param host string ---@param port integer ---@return function -local function connect(host, port) +function M.connect(host, port) return function(dispatchers) dispatchers = merge_dispatchers(dispatchers) local tcp = uv.new_tcp() @@ -600,8 +599,8 @@ local function connect(host, port) local handle_body = function(body) client:handle_body(body) end - tcp:read_start(create_read_loop(handle_body, transport.terminate, function(read_err) - client:on_error(client_errors.READ_ERROR, read_err) + 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) @@ -630,7 +629,7 @@ end --- - `request()` |vim.lsp.rpc.request()| --- - `is_closing()` returns a boolean indicating if the RPC is closing. --- - `terminate()` terminates the RPC client. -local function start(cmd, cmd_args, dispatchers, extra_spawn_params) +function M.start(cmd, cmd_args, dispatchers, extra_spawn_params) if log.info() then log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params }) end @@ -667,8 +666,8 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) client:handle_body(body) end - local stdout_handler = create_read_loop(handle_body, nil, function(err) - client:on_error(client_errors.READ_ERROR, err) + local stdout_handler = M.create_read_loop(handle_body, nil, function(err) + client:on_error(M.client_errors.READ_ERROR, err) end) local stderr_handler = function(_, chunk) @@ -714,11 +713,4 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) return public_client(client) end -return { - start = start, - connect = connect, - rpc_response_error = rpc_response_error, - format_rpc_error = format_rpc_error, - client_errors = client_errors, - create_read_loop = create_read_loop, -} +return M diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 191cc7b400..84723bbc05 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -41,8 +41,6 @@ local STHighlighter = { active = {} } --- --- Return the index i in range such that tokens[j].line < line for all j < i, and --- tokens[j].line >= line for all j >= i, or return hi if no such index is found. ---- ----@private local function lower_bound(tokens, line, lo, hi) while lo < hi do local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2). @@ -59,8 +57,6 @@ end --- --- Return the index i in range such that tokens[j].line <= line for all j < i, and --- tokens[j].line > line for all j >= i, or return hi if no such index is found. ---- ----@private local function upper_bound(tokens, line, lo, hi) while lo < hi do local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2). @@ -75,7 +71,6 @@ end --- Extracts modifier strings from the encoded number in the token array --- ----@private ---@return table local function modifiers_from_number(x, modifiers_table) local modifiers = {} @@ -93,7 +88,6 @@ end --- Converts a raw token list to a list of highlight ranges used by the on_win callback --- ----@private ---@return STTokenRange[] local function tokens_to_ranges(data, bufnr, client, request) local legend = client.server_capabilities.semanticTokensProvider.legend @@ -137,7 +131,6 @@ local function tokens_to_ranges(data, bufnr, client, request) local token_type = token_types[data[i + 3] + 1] local modifiers = modifiers_from_number(data[i + 4], token_modifiers) - ---@private local function _get_byte_pos(col) if col > 0 then local buf_line = lines[line + 1] or '' diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index 350c096b47..9c1bbf3892 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -48,7 +48,6 @@ local str_utfindex = vim.str_utfindex local str_utf_start = vim.str_utf_start local str_utf_end = vim.str_utf_end ----@private -- Given a line, byte idx, and offset_encoding convert to the -- utf-8, utf-16, or utf-32 index. ---@param line string the line to index into @@ -74,7 +73,6 @@ local function byte_to_utf(line, byte, offset_encoding) return utf_idx + 1 end ----@private local function compute_line_length(line, offset_encoding) local length local _ @@ -88,7 +86,6 @@ local function compute_line_length(line, offset_encoding) return length end ----@private -- Given a line, byte idx, alignment, and offset_encoding convert to the aligned -- utf-8 index and either the utf-16, or utf-32 index. ---@param line string the line to index into @@ -122,7 +119,6 @@ local function align_end_position(line, byte, offset_encoding) return byte, char end ----@private --- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint. ---@param prev_lines table list of lines from previous buffer ---@param curr_lines table list of lines from current buffer @@ -198,7 +194,6 @@ local function compute_start_range( return { line_idx = firstline, byte_idx = byte_idx, char_idx = char_idx } end ----@private --- Finds the last line and byte index of the differences between prev and current buffer. --- Normalized to the next codepoint. --- prev_end_range is the text range sent to the server representing the changed region. @@ -307,7 +302,6 @@ local function compute_end_range( return prev_end_range, curr_end_range end ----@private --- Get the text of the range defined by start and end line/column ---@param lines table list of lines ---@param start_range table table returned by first_difference @@ -343,7 +337,6 @@ local function extract_text(lines, start_range, end_range, line_ending) end end ----@private -- rangelength depends on the offset encoding -- bytes for utf-8 (clangd with extension) -- codepoints for utf-16 diff --git a/runtime/lua/vim/lsp/tagfunc.lua b/runtime/lua/vim/lsp/tagfunc.lua index eb25b67db9..c70eb573c2 100644 --- a/runtime/lua/vim/lsp/tagfunc.lua +++ b/runtime/lua/vim/lsp/tagfunc.lua @@ -1,7 +1,6 @@ local lsp = vim.lsp local util = lsp.util ----@private local function mk_tag_item(name, range, uri, offset_encoding) local bufnr = vim.uri_to_bufnr(uri) -- This is get_line_byte_from_position is 0-indexed, call cursor expects a 1-indexed position @@ -13,7 +12,6 @@ local function mk_tag_item(name, range, uri, offset_encoding) } end ----@private local function query_definition(pattern) local params = util.make_position_params() local results_by_client, err = lsp.buf_request_sync(0, 'textDocument/definition', params, 1000) @@ -42,7 +40,6 @@ local function query_definition(pattern) return results end ----@private local function query_workspace_symbols(pattern) local results_by_client, err = lsp.buf_request_sync(0, 'workspace/symbol', { query = pattern }, 1000) @@ -62,7 +59,6 @@ local function query_workspace_symbols(pattern) return results end ----@private local function tagfunc(pattern, flags) local matches if string.match(flags, 'c') then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 59b9916f64..9a6114c35b 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -22,7 +22,6 @@ local default_border = { { ' ', 'NormalFloat' }, } ----@private --- Check the border given by opts or the default border for the additional --- size it adds to a float. ---@param opts table optional options for the floating window @@ -60,7 +59,6 @@ local function get_border_size(opts) ) ) end - ---@private local function border_width(id) id = (id - 1) % #border + 1 if type(border[id]) == 'table' then @@ -77,7 +75,6 @@ local function get_border_size(opts) ) ) end - ---@private local function border_height(id) id = (id - 1) % #border + 1 if type(border[id]) == 'table' then @@ -103,13 +100,11 @@ local function get_border_size(opts) return { height = height, width = width } end ----@private local function split_lines(value) value = string.gsub(value, '\r\n?', '\n') return split(value, '\n', { plain = true }) end ----@private local function create_window_without_focus() local prev = vim.api.nvim_get_current_win() vim.cmd.new() @@ -219,7 +214,6 @@ function M.set_lines(lines, A, B, new_lines) return lines end ----@private local function sort_by_key(fn) return function(a, b) local ka, kb = fn(a), fn(b) @@ -234,7 +228,6 @@ local function sort_by_key(fn) end end ----@private --- Gets the zero-indexed lines from the given buffer. --- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. --- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. @@ -250,7 +243,6 @@ local function get_lines(bufnr, rows) bufnr = api.nvim_get_current_buf() end - ---@private local function buf_lines() local lines = {} for _, row in ipairs(rows) do @@ -316,7 +308,6 @@ local function get_lines(bufnr, rows) return lines end ----@private --- Gets the zero-indexed line from the given buffer. --- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. --- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. @@ -328,7 +319,6 @@ local function get_line(bufnr, row) return get_lines(bufnr, { row })[row] end ----@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to ---@param offset_encoding string|nil utf-8|utf-16|utf-32 @@ -670,7 +660,6 @@ function M.parse_snippet(input) return parsed end ----@private --- Sorts by CompletionItem.sortText. --- --see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -680,7 +669,6 @@ local function sort_completion_items(items) end) end ----@private --- Returns text that should be inserted when selecting completion item. The --- precedence is as follows: textEdit.newText > insertText > label --see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -703,7 +691,6 @@ local function get_completion_word(item) return item.label end ----@private --- Some language servers return complementary candidates whose prefixes do not --- match are also returned. So we exclude completion candidates whose prefix --- does not match. @@ -784,7 +771,6 @@ function M.text_document_completion_list_to_complete_items(result, prefix) return matches end ----@private --- Like vim.fn.bufwinid except it works across tabpages. local function bufwinid(bufnr) for _, win in ipairs(api.nvim_list_wins()) do @@ -795,7 +781,6 @@ local function bufwinid(bufnr) end --- Get list of buffers for a directory ----@private local function get_dir_bufs(path) path = path:gsub('([^%w])', '%%%1') local buffers = {} @@ -855,7 +840,6 @@ function M.rename(old_fname, new_fname, opts) end end ----@private local function create_file(change) local opts = change.options or {} -- from spec: Overwrite wins over `ignoreIfExists` @@ -870,7 +854,6 @@ local function create_file(change) vim.fn.bufadd(fname) end ----@private local function delete_file(change) local opts = change.options or {} local fname = vim.uri_to_fname(change.uri) @@ -1266,7 +1249,6 @@ function M.preview_location(location, opts) return M.open_floating_preview(contents, syntax, opts) end ----@private local function find_window_by_var(name, value) for _, win in ipairs(api.nvim_list_wins()) do if npcall(api.nvim_win_get_var, win, name) == value then @@ -1305,7 +1287,6 @@ end --- Generates a table mapping markdown code block lang to vim syntax, --- based on g:markdown_fenced_languages ---@return table table of lang -> syntax mappings ----@private local function get_markdown_fences() local fences = {} for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do @@ -1460,7 +1441,6 @@ function M.stylize_markdown(bufnr, contents, opts) api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped) local idx = 1 - ---@private -- keep track of syntaxes we already included. -- no need to include the same syntax more than once local langs = {} @@ -1521,7 +1501,6 @@ function M.stylize_markdown(bufnr, contents, opts) return stripped end ----@private --- Closes the preview window --- ---@param winnr integer window id of preview window @@ -1539,7 +1518,6 @@ local function close_preview_window(winnr, bufnrs) end) end ----@private --- Creates autocommands to close a preview window when events happen. --- ---@param events table list of events @@ -1905,7 +1883,6 @@ end --- ---@param symbols table DocumentSymbol[] or SymbolInformation[] function M.symbols_to_items(symbols, bufnr) - ---@private local function _symbols_to_items(_symbols, _items, _bufnr) for _, symbol in ipairs(_symbols) do if symbol.location then -- SymbolInformation type @@ -1991,7 +1968,6 @@ function M.try_trim_markdown_code_blocks(lines) return 'markdown' end ----@private ---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` local function make_position_param(window, offset_encoding) @@ -2209,6 +2185,7 @@ end M._get_line_byte_from_position = get_line_byte_from_position +---@nodoc M.buf_versions = {} return M diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 1a04e11231..837738c041 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -1,6 +1,5 @@ local M = {} ----@private --- Reads trust database from $XDG_STATE_HOME/nvim/trust. --- ---@return (table) Contents of trust database, if it exists. Empty table otherwise. @@ -22,7 +21,6 @@ local function read_trust() return trust end ----@private --- Writes provided {trust} table to trust database at --- $XDG_STATE_HOME/nvim/trust. --- diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index dd05299295..291b003d87 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -8,6 +8,45 @@ vim = vim or {} +local function _id(v) + return v +end + +local deepcopy + +local deepcopy_funcs = { + table = function(orig, cache) + if cache[orig] then + return cache[orig] + end + local copy = {} + + cache[orig] = copy + local mt = getmetatable(orig) + for k, v in pairs(orig) do + copy[deepcopy(k, cache)] = deepcopy(v, cache) + end + return setmetatable(copy, mt) + end, + number = _id, + string = _id, + ['nil'] = _id, + boolean = _id, + ['function'] = _id, +} + +deepcopy = function(orig, _cache) + local f = deepcopy_funcs[type(orig)] + if f then + return f(orig, _cache or {}) + else + if type(orig) == 'userdata' and orig == vim.NIL then + return vim.NIL + end + error('Cannot deepcopy object of type ' .. type(orig)) + end +end + --- Returns a deep copy of the given object. Non-table objects are copied as --- in a typical Lua assignment, whereas table objects are copied recursively. --- Functions are naively copied, so functions in the copied table point to the @@ -17,45 +56,9 @@ vim = vim or {} ---@generic T: table ---@param orig T Table to copy ---@return T Table of copied keys and (nested) values. -function vim.deepcopy(orig) end -- luacheck: no unused -vim.deepcopy = (function() - local function _id(v) - return v - end - - local deepcopy_funcs = { - table = function(orig, cache) - if cache[orig] then - return cache[orig] - end - local copy = {} - - cache[orig] = copy - local mt = getmetatable(orig) - for k, v in pairs(orig) do - copy[vim.deepcopy(k, cache)] = vim.deepcopy(v, cache) - end - return setmetatable(copy, mt) - end, - number = _id, - string = _id, - ['nil'] = _id, - boolean = _id, - ['function'] = _id, - } - - return function(orig, cache) - local f = deepcopy_funcs[type(orig)] - if f then - return f(orig, cache or {}) - else - if type(orig) == 'userdata' and orig == vim.NIL then - return vim.NIL - end - error('Cannot deepcopy object of type ' .. type(orig)) - end - end -end)() +function vim.deepcopy(orig) + return deepcopy(orig) +end --- Splits a string at each instance of a separator. --- @@ -318,7 +321,6 @@ function vim.tbl_isempty(t) end --- We only merge empty tables or tables that are not an array (indexed by integers) ----@private local function can_merge(v) return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_isarray(v)) end @@ -770,7 +772,6 @@ do return type(val) == t or (t == 'callable' and vim.is_callable(val)) end - ---@private local function is_valid(opt) if type(opt) ~= 'table' then return false, string.format('opt: expected table, got %s', type(opt)) diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 5526c9858c..8bd85d3734 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -39,7 +39,10 @@ local M = setmetatable({}, { end, }) +--- @nodoc M.language_version = vim._ts_get_language_version() + +--- @nodoc M.minimum_language_version = vim._ts_get_minimum_language_version() --- Creates a new parser @@ -60,12 +63,10 @@ function M._create_parser(bufnr, lang, opts) local self = LanguageTree.new(bufnr, lang, opts) - ---@private local function bytes_cb(_, ...) self:_on_bytes(...) end - ---@private local function detach_cb(_, ...) if parsers[bufnr] == self then parsers[bufnr] = nil @@ -73,7 +74,6 @@ function M._create_parser(bufnr, lang, opts) self:_on_detach(...) end - ---@private local function reload_cb(_) self:_on_reload() end @@ -91,7 +91,6 @@ function M._create_parser(bufnr, lang, opts) return self end ---- @private local function valid_lang(lang) return lang and lang ~= '' end @@ -205,7 +204,6 @@ function M.get_range(node, source, metadata) return { node:range(true) } end ----@private ---@param buf integer ---@param range Range ---@returns string diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 99cd147658..1bb5a08205 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -42,7 +42,6 @@ local TSTreeView = {} ---@param injections table Mapping of node ids to root nodes of injected language trees (see --- explanation above) ---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree ----@private local function traverse(node, depth, lang, injections, tree) local injection = injections[node:id()] if injection then @@ -141,7 +140,6 @@ end local decor_ns = api.nvim_create_namespace('ts.dev') ----@private ---@param lnum integer ---@param col integer ---@param end_lnum integer diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d4db6bc404..f8ec5b175d 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -17,6 +17,7 @@ local query = vim.treesitter.query local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter +--- @nodoc TSHighlighter.active = TSHighlighter.active or {} ---@class TSHighlighterQuery @@ -205,7 +206,6 @@ function TSHighlighter:get_query(lang) return self._queries[lang] end ----@private ---@param self TSHighlighter ---@param buf integer ---@param line integer diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 08c297c9ad..9695e2c41c 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -104,7 +104,6 @@ function M.add(lang, opts) vim._ts_add_language(path, lang, symbol_name) end ---- @private --- @param x string|string[] --- @return string[] local function ensure_list(x) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index bf6333aaa4..0d4a1a54dd 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -164,7 +164,6 @@ function LanguageTree:_set_logger() self._parser:_set_logger(log_lex, log_parse, self._logger) end ----@private ---Measure execution time of a function ---@generic R1, R2, R3 ---@param f fun(): R1, R2, R2 @@ -444,7 +443,6 @@ function LanguageTree:destroy() end end ----@private ---@param region Range6[] local function region_tostr(region) if #region == 0 then @@ -560,7 +558,6 @@ function LanguageTree:included_regions() return regions end ----@private ---@param node TSNode ---@param source string|integer ---@param metadata TSMetadata @@ -600,7 +597,6 @@ end ---@alias TSInjection table> ----@private ---@param t table ---@param tree_index integer ---@param pattern integer @@ -963,7 +959,6 @@ function LanguageTree:register_cbs(cbs, recursive) end end ----@private ---@param tree TSTree ---@param range Range ---@return boolean diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7610ef7b7f..08186468a5 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -15,7 +15,6 @@ Query.__index = Query ---@class TSQueryModule local M = {} ----@private ---@param files string[] ---@return string[] local function dedupe_files(files) @@ -33,7 +32,6 @@ local function dedupe_files(files) return result end ----@private local function safe_read(filename, read_quantifier) local file, err = io.open(filename, 'r') if not file then @@ -44,7 +42,6 @@ local function safe_read(filename, read_quantifier) return content end ----@private --- Adds {ilang} to {base_langs}, only if {ilang} is different than {lang} --- ---@return boolean true If lang == ilang @@ -153,7 +150,6 @@ function M.get_files(lang, query_name, is_included) return query_files end ----@private ---@param filenames string[] ---@return string local function read_query_files(filenames) @@ -335,7 +331,6 @@ local predicate_handlers = { ['match?'] = (function() local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true } - ---@private local function check_magic(str) if string.len(str) < 2 or magic_prefixes[string.sub(str, 1, 2)] then return str @@ -624,12 +619,10 @@ function M.list_predicates() return vim.tbl_keys(predicate_handlers) end ----@private local function xor(x, y) return (x or y) and not (x and y) end ----@private local function is_directive(name) return string.sub(name, -1) == '!' end @@ -700,7 +693,6 @@ 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. ----@private ---@param start integer ---@param stop integer ---@param node TSNode @@ -750,7 +742,6 @@ 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) - ---@private local function iter() local capture, captured_node, match = raw_iter() local metadata = {} diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index dec7840eb0..eaa64a6fad 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -8,7 +8,6 @@ do local schar = string.char --- Convert hex to char - ---@private local function hex_to_char(hex) return schar(tonumber(hex, 16)) end @@ -33,7 +32,6 @@ do local sbyte = string.byte local tohex = require('bit').tohex - ---@private local function percent_encode_char(char) return '%' .. tohex(sbyte(char), 2) end @@ -46,15 +44,16 @@ do end end ----@private local function is_windows_file_uri(uri) return uri:match('^file:/+[a-zA-Z]:') ~= nil end +local M = {} + --- Get a URI from a file path. ---@param path string Path to file ---@return string URI -local function uri_from_fname(path) +function M.uri_from_fname(path) local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') local is_windows = volume_path ~= nil if is_windows then @@ -76,7 +75,7 @@ local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*' --- Get a URI from a bufnr ---@param bufnr integer ---@return string URI -local function uri_from_bufnr(bufnr) +function M.uri_from_bufnr(bufnr) local fname = vim.api.nvim_buf_get_name(bufnr) local volume_path = fname:match('^([a-zA-Z]:).*') local is_windows = volume_path ~= nil @@ -90,14 +89,14 @@ local function uri_from_bufnr(bufnr) if scheme then return fname else - return uri_from_fname(fname) + return M.uri_from_fname(fname) end end --- Get a filename from a URI ---@param uri string ---@return string filename or unchanged URI for non-file URIs -local function uri_to_fname(uri) +function M.uri_to_fname(uri) local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri) if scheme ~= 'file' then return uri @@ -118,13 +117,8 @@ end -- ---@param uri string ---@return integer bufnr -local function uri_to_bufnr(uri) - return vim.fn.bufadd(uri_to_fname(uri)) +function M.uri_to_bufnr(uri) + return vim.fn.bufadd(M.uri_to_fname(uri)) end -return { - uri_from_fname = uri_from_fname, - uri_from_bufnr = uri_from_bufnr, - uri_to_fname = uri_to_fname, - uri_to_bufnr = uri_to_bufnr, -} +return M diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 8ff8a19cb9..96889438eb 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -65,8 +65,6 @@ local M = {} local Version = {} Version.__index = Version ---- @private ---- --- Compares prerelease strings: per semver, number parts must be must be treated as numbers: --- "pre1.10" is greater than "pre1.2". https://semver.org/#spec-item-11 local function cmp_prerel(prerel1, prerel2) @@ -332,7 +330,6 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim end end ----@private ---@param v string|Version ---@return string local function create_err_msg(v) -- cgit From 2f22ed6a00db10c4852a8fa232b8782f8b6a6646 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Wed, 19 Jul 2023 07:10:11 +0200 Subject: feat(lsp): handle multiple clients in omnifunc (#24381) Also fixes https://github.com/neovim/neovim/issues/24369 by adding an extra `vim.schedule` to ensure the `vim.fn.complete` call happens outside of a luv callback --- runtime/lua/vim/lsp.lua | 88 ++++++++++++++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 34 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 35e4bc9dd8..65cce6af47 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -2268,8 +2268,9 @@ function lsp.omnifunc(findstart, base) end local bufnr = resolve_bufnr() - local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {}) - if not has_buffer_clients then + local clients = lsp.get_clients({ bufnr = bufnr, method = 'textDocument/completion' }) + local remaining = #clients + if remaining == 0 then return findstart == 1 and -1 or {} end @@ -2278,47 +2279,66 @@ function lsp.omnifunc(findstart, base) log.info('base ', base) end - local pos = api.nvim_win_get_cursor(0) + local win = api.nvim_get_current_win() + local pos = api.nvim_win_get_cursor(win) local line = api.nvim_get_current_line() local line_to_cursor = line:sub(1, pos[2]) local _ = log.trace() and log.trace('omnifunc.line', pos, line) -- Get the start position of the current keyword - local textMatch = vim.fn.match(line_to_cursor, '\\k*$') + local match_pos = vim.fn.match(line_to_cursor, '\\k*$') + 1 + local items = {} - local params = util.make_position_params() + local startbyte - local items = {} - lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx) - if err or not result or vim.fn.mode() ~= 'i' then - return + ---@private + local function on_done() + local mode = api.nvim_get_mode()['mode'] + if mode == 'i' or mode == 'ic' then + vim.fn.complete(startbyte or match_pos, items) end + end - -- Completion response items may be relative to a position different than `textMatch`. - -- Concrete example, with sumneko/lua-language-server: - -- - -- require('plenary.asy| - -- ▲ ▲ ▲ - -- │ │ └── cursor_pos: 20 - -- │ └────── textMatch: 17 - -- └────────────── textEdit.range.start.character: 9 - -- .newText = 'plenary.async' - -- ^^^ - -- prefix (We'd remove everything not starting with `asy`, - -- so we'd eliminate the `plenary.async` result - -- - -- `adjust_start_col` is used to prefer the language server boundary. - -- - local client = lsp.get_client_by_id(ctx.client_id) - local encoding = client and client.offset_encoding or 'utf-16' - local candidates = util.extract_completion_items(result) - local startbyte = adjust_start_col(pos[1], line, candidates, encoding) or textMatch - local prefix = line:sub(startbyte + 1, pos[2]) - local matches = util.text_document_completion_list_to_complete_items(result, prefix) - -- TODO(ashkan): is this the best way to do this? - vim.list_extend(items, matches) - vim.fn.complete(startbyte + 1, items) - end) + for _, client in ipairs(clients) do + local params = util.make_position_params(win, client.offset_encoding) + client.request('textDocument/completion', params, function(err, result) + if err then + log.warn(err.message) + end + if result and vim.fn.mode() == 'i' then + -- Completion response items may be relative to a position different than `textMatch`. + -- Concrete example, with sumneko/lua-language-server: + -- + -- require('plenary.asy| + -- ▲ ▲ ▲ + -- │ │ └── cursor_pos: 20 + -- │ └────── textMatch: 17 + -- └────────────── textEdit.range.start.character: 9 + -- .newText = 'plenary.async' + -- ^^^ + -- prefix (We'd remove everything not starting with `asy`, + -- so we'd eliminate the `plenary.async` result + -- + -- `adjust_start_col` is used to prefer the language server boundary. + -- + local encoding = client.offset_encoding + local candidates = util.extract_completion_items(result) + local curstartbyte = adjust_start_col(pos[1], line, candidates, encoding) + if startbyte == nil then + startbyte = curstartbyte + elseif curstartbyte ~= nil and curstartbyte ~= startbyte then + startbyte = match_pos + end + local prefix = startbyte and line:sub(startbyte + 1) or line_to_cursor:sub(match_pos) + local matches = util.text_document_completion_list_to_complete_items(result, prefix) + vim.list_extend(items, matches) + end + remaining = remaining - 1 + if remaining == 0 then + vim.schedule(on_done) + end + end, bufnr) + end -- Return -2 to signal that we should continue completion so that we can -- async complete. -- cgit From 86ce3878d662c1dbfec61a5ad8e7c16c4283ed5c Mon Sep 17 00:00:00 2001 From: futsuuu <105504350+futsuuu@users.noreply.github.com> Date: Thu, 20 Jul 2023 01:55:35 +0900 Subject: docs(lua): clarify fs.find() documentation #24394 --- runtime/lua/vim/fs.lua | 54 ++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 28 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index 842767098c..92966523c0 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -2,7 +2,7 @@ local M = {} local iswin = vim.uv.os_uname().sysname == 'Windows_NT' ---- Iterate over all the parents of the given file or directory. +--- Iterate over all the parents of the given path. --- --- Example: ---
lua
@@ -19,7 +19,7 @@ local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
 --- end
 --- 
--- ----@param start (string) Initial file or directory. +---@param start (string) Initial path. ---@return function Iterator function M.parents(start) return function(_, dir) @@ -34,9 +34,9 @@ function M.parents(start) start end ---- Return the parent directory of the given file or directory +--- Return the parent directory of the given path --- ----@param file (string) File or directory +---@param file (string) Path ---@return string|nil Parent directory of {file} function M.dirname(file) if file == nil then @@ -57,9 +57,9 @@ function M.dirname(file) return (dir:gsub('\\', '/')) end ---- Return the basename of the given file or directory +--- Return the basename of the given path --- ----@param file string File or directory +---@param file string Path ---@return string|nil Basename of {file} function M.basename(file) if file == nil then @@ -83,7 +83,7 @@ end ---@alias Iterator fun(): string?, string? ---- Return an iterator over the files and directories located in {path} +--- Return an iterator over the items located in {path} --- ---@param path (string) An absolute or relative path to the directory to iterate --- over. The path is first normalized |vim.fs.normalize()|. @@ -93,9 +93,10 @@ end --- to control traversal. Return false to stop searching the current directory. --- Only useful when depth > 1 --- ----@return Iterator over files and directories in {path}. Each iteration yields ---- two values: name and type. Each "name" is the basename of the file or ---- directory relative to {path}. Type is one of "file" or "directory". +---@return Iterator over items in {path}. Each iteration yields two values: "name" and "type". +--- "name" is the basename of the item relative to {path}. +--- "type" is one of the following: +--- "file", "directory", "link", "fifo", "socket", "char", "block", "unknown". function M.dir(path, opts) opts = opts or {} @@ -142,16 +143,16 @@ function M.dir(path, opts) end) end ---- Find files or directories in the given path. +--- Find files or directories (or other items as specified by `opts.type`) in the given path. --- ---- Finds any files or directories given in {names} starting from {path}. If ---- {upward} is "true" then the search traverses upward through parent ---- directories; otherwise, the search traverses downward. Note that downward ---- searches are recursive and may search through many directories! If {stop} ---- is non-nil, then the search stops when the directory given in {stop} is ---- reached. The search terminates when {limit} (default 1) matches are found. ---- The search can be narrowed to find only files or only directories by ---- specifying {type} to be "file" or "directory", respectively. +--- Finds items given in {names} starting from {path}. If {upward} is "true" +--- then the search traverses upward through parent directories; otherwise, +--- the search traverses downward. Note that downward searches are recursive +--- and may search through many directories! If {stop} is non-nil, then the +--- search stops when the directory given in {stop} is reached. The search +--- terminates when {limit} (default 1) matches are found. You can set {type} +--- to "file", "directory", "link", "socket", "char", "block", or "fifo" +--- to narrow the search to find only that type. --- --- Examples: ---
lua
@@ -174,13 +175,12 @@ end
 --- end, {limit = math.huge, type = 'file'})
 --- 
--- ----@param names (string|table|fun(name: string, path: string): boolean) Names of the files ---- and directories to find. +---@param names (string|table|fun(name: string, path: string): boolean) Names of the items to find. --- Must be base names, paths and globs are not supported when {names} is a string or a table. ---- If {names} is a function, it is called for each traversed file and directory with args: +--- If {names} is a function, it is called for each traversed item with args: --- - name: base name of the current item --- - path: full path of the current item ---- The function should return `true` if the given file or directory is considered a match. +--- The function should return `true` if the given item is considered a match. --- ---@param opts (table) Optional keyword arguments: --- - path (string): Path to begin searching from. If @@ -191,14 +191,12 @@ end --- (recursively). --- - stop (string): Stop searching when this directory is --- reached. The directory itself is not searched. ---- - type (string): Find only files ("file") or ---- directories ("directory"). If omitted, both ---- files and directories that match {names} are ---- included. +--- - type (string): Find only items of the given type. +--- If omitted, all items that match {names} are included. --- - limit (number, default 1): Stop the search after --- finding this many matches. Use `math.huge` to --- place no limit on the number of matches. ----@return (table) Normalized paths |vim.fs.normalize()| of all matching files or directories +---@return (table) Normalized paths |vim.fs.normalize()| of all matching items function M.find(names, opts) opts = opts or {} vim.validate({ -- cgit From 63b3408551561127f7845470eb51404bcd6f547b Mon Sep 17 00:00:00 2001 From: Chris AtLee Date: Thu, 20 Jul 2023 03:03:48 -0400 Subject: feat(lsp): implement textDocument/diagnostic (#24128) --- runtime/lua/vim/lsp.lua | 22 ++++- runtime/lua/vim/lsp/diagnostic.lua | 187 ++++++++++++++++++++++++++++++++++--- runtime/lua/vim/lsp/handlers.lua | 4 + runtime/lua/vim/lsp/inlay_hint.lua | 77 ++++----------- runtime/lua/vim/lsp/protocol.lua | 3 + runtime/lua/vim/lsp/util.lua | 40 ++++++++ 6 files changed, 263 insertions(+), 70 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 65cce6af47..fa2a888a54 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -61,6 +61,7 @@ lsp._request_name_to_capability = { ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' }, ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' }, ['textDocument/inlayHint'] = { 'inlayHintProvider' }, + ['textDocument/diagnostic'] = { 'diagnosticProvider' }, ['inlayHint/resolve'] = { 'inlayHintProvider', 'resolveProvider' }, } @@ -954,6 +955,9 @@ function lsp._set_defaults(client, bufnr) vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr }) end end) + if client.supports_method('textDocument/diagnostic') then + lsp.diagnostic._enable(bufnr) + end end --- @class lsp.ClientConfig @@ -1567,7 +1571,23 @@ function lsp.start_client(config) if method ~= 'textDocument/didChange' then changetracking.flush(client) end - return rpc.notify(method, params) + + local result = rpc.notify(method, params) + + if result then + vim.schedule(function() + nvim_exec_autocmds('LspNotify', { + modeline = false, + data = { + client_id = client.id, + method = method, + params = params, + }, + }) + end) + end + + return result end ---@private diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index c2cf7c6ba5..34be13096d 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -1,9 +1,14 @@ ---@brief lsp-diagnostic +local util = require('vim.lsp.util') local protocol = require('vim.lsp.protocol') +local api = vim.api + local M = {} +local augroup = api.nvim_create_augroup('vim_lsp_diagnostic', {}) + local DEFAULT_CLIENT_ID = -1 local function get_client_id(client_id) @@ -154,19 +159,43 @@ local function diagnostic_vim_to_lsp(diagnostics) end ---@type table -local _client_namespaces = {} +local _client_push_namespaces = {} +---@type table +local _client_pull_namespaces = {} ---- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|. +--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics --- ---@param client_id integer The id of the LSP client -function M.get_namespace(client_id) +---@param is_pull boolean Whether the namespace is for a pull or push client +function M.get_namespace(client_id, is_pull) vim.validate({ client_id = { client_id, 'n' } }) - if not _client_namespaces[client_id] then - local client = vim.lsp.get_client_by_id(client_id) - local name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id) - _client_namespaces[client_id] = vim.api.nvim_create_namespace(name) + + local namespace_table + local key + local name + local client = vim.lsp.get_client_by_id(client_id) + + if is_pull then + namespace_table = _client_pull_namespaces + local server_id = vim.tbl_get(client.server_capabilities, 'diagnosticProvider', 'identifier') + key = string.format('%d:%s', client_id, server_id or 'nil') + name = string.format( + 'vim.lsp.%s.%d.%s', + client and client.name or 'unknown', + client_id, + server_id or 'nil' + ) + else + namespace_table = _client_push_namespaces + key = client_id + name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id) end - return _client_namespaces[client_id] + + if not namespace_table[key] then + namespace_table[key] = api.nvim_create_namespace(name) + end + + return namespace_table[key] end --- |lsp-handler| for the method "textDocument/publishDiagnostics" @@ -209,7 +238,7 @@ function M.on_publish_diagnostics(_, result, ctx, config) end client_id = get_client_id(client_id) - local namespace = M.get_namespace(client_id) + local namespace = M.get_namespace(client_id, false) if config then for _, opt in pairs(config) do @@ -229,7 +258,75 @@ function M.on_publish_diagnostics(_, result, ctx, config) vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) end ---- Clear diagnostics and diagnostic cache. +--- |lsp-handler| for the method "textDocument/diagnostic" +--- +--- See |vim.diagnostic.config()| for configuration options. Handler-specific +--- configuration can be set using |vim.lsp.with()|: +---
lua
+--- vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with(
+---   vim.lsp.diagnostic.on_diagnostic, {
+---     -- Enable underline, use default values
+---     underline = true,
+---     -- Enable virtual text, override spacing to 4
+---     virtual_text = {
+---       spacing = 4,
+---     },
+---     -- Use a function to dynamically turn signs off
+---     -- and on, using buffer local variables
+---     signs = function(namespace, bufnr)
+---       return vim.b[bufnr].show_signs == true
+---     end,
+---     -- Disable a feature
+---     update_in_insert = false,
+---   }
+--- )
+--- 
+--- +---@param config table Configuration table (see |vim.diagnostic.config()|). +function M.on_diagnostic(_, result, ctx, config) + local client_id = ctx.client_id + local uri = ctx.params.textDocument.uri + local fname = vim.uri_to_fname(uri) + + if result == nil then + return + end + + if result.kind == 'unchanged' then + return + end + + local diagnostics = result.items + if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then + return + end + local bufnr = vim.fn.bufadd(fname) + + if not bufnr then + return + end + + client_id = get_client_id(client_id) + + local namespace = M.get_namespace(client_id, true) + + if config then + for _, opt in pairs(config) do + if type(opt) == 'table' and not opt.severity and opt.severity_limit then + opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) } + end + end + + -- Persist configuration to ensure buffer reloads use the same + -- configuration. To make lsp.with configuration work (See :help + -- lsp-handler-configuration) + vim.diagnostic.config(config, namespace) + end + + vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) +end + +--- Clear push diagnostics and diagnostic cache. --- --- Diagnostic producers should prefer |vim.diagnostic.reset()|. However, --- this method signature is still used internally in some parts of the LSP @@ -243,7 +340,7 @@ function M.reset(client_id, buffer_client_map) vim.schedule(function() for bufnr, client_ids in pairs(buffer_client_map) do if client_ids[client_id] then - local namespace = M.get_namespace(client_id) + local namespace = M.get_namespace(client_id, false) vim.diagnostic.reset(namespace, bufnr) end end @@ -275,7 +372,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) end if client_id then - opts.namespace = M.get_namespace(client_id) + opts.namespace = M.get_namespace(client_id, false) end if not line_nr then @@ -287,4 +384,70 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts)) end +--- Clear diagnostics from pull based clients +--- @private +local function clear(bufnr) + for _, namespace in pairs(_client_pull_namespaces) do + vim.diagnostic.reset(namespace, bufnr) + end +end + +--- autocmd ids for LspNotify handlers per buffer +--- @private +--- @type table +local _autocmd_ids = {} + +--- Disable pull diagnostics for a buffer +--- @private +local function disable(bufnr) + if not _autocmd_ids[bufnr] then + return + end + api.nvim_del_autocmd(_autocmd_ids[bufnr]) + _autocmd_ids[bufnr] = nil + clear(bufnr) +end + +--- Enable pull diagnostics for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +---@private +function M._enable(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end + + if _autocmd_ids[bufnr] then + return + end + + _autocmd_ids[bufnr] = api.nvim_create_autocmd('LspNotify', { + buffer = bufnr, + callback = function(opts) + if opts.data.method ~= 'textDocument/didChange' then + return + end + util._refresh('textDocument/diagnostic', { bufnr = bufnr, only_visible = true }) + end, + group = augroup, + }) + + api.nvim_buf_attach(bufnr, false, { + on_reload = function() + util._refresh('textDocument/diagnostic', { bufnr = bufnr }) + end, + on_detach = function() + disable(bufnr) + end, + }) + + api.nvim_create_autocmd('LspDetach', { + buffer = bufnr, + callback = function() + disable(bufnr) + end, + once = true, + group = augroup, + }) +end + return M diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index ce3db68618..d887183972 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -214,6 +214,10 @@ M['textDocument/publishDiagnostics'] = function(...) return require('vim.lsp.diagnostic').on_publish_diagnostics(...) end +M['textDocument/diagnostic'] = function(...) + return require('vim.lsp.diagnostic').on_diagnostic(...) +end + M['textDocument/codeLens'] = function(...) return require('vim.lsp.codelens').on_codelens(...) end diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index bec6f33e93..1c29e8a866 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -87,54 +87,6 @@ function M.on_inlayhint(err, result, ctx, _) api.nvim__buf_redraw_range(bufnr, 0, -1) end -local function resolve_bufnr(bufnr) - return bufnr > 0 and bufnr or api.nvim_get_current_buf() -end - ---- Refresh inlay hints for a buffer ---- ----@param opts (nil|table) Optional arguments ---- - bufnr (integer, default: 0): Buffer whose hints to refresh ---- - only_visible (boolean, default: false): Whether to only refresh hints for the visible regions of the buffer ---- -local function refresh(opts) - opts = opts or {} - local bufnr = resolve_bufnr(opts.bufnr or 0) - local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.enabled) then - return - end - local only_visible = opts.only_visible or false - local buffer_windows = {} - for _, winid in ipairs(api.nvim_list_wins()) do - if api.nvim_win_get_buf(winid) == bufnr then - table.insert(buffer_windows, winid) - end - end - for _, window in ipairs(buffer_windows) do - local first = vim.fn.line('w0', window) - local last = vim.fn.line('w$', window) - local params = { - textDocument = util.make_text_document_params(bufnr), - range = { - start = { line = first - 1, character = 0 }, - ['end'] = { line = last, character = 0 }, - }, - } - vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) - end - if not only_visible then - local params = { - textDocument = util.make_text_document_params(bufnr), - range = { - start = { line = 0, character = 0 }, - ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, - }, - } - vim.lsp.buf_request(bufnr, 'textDocument/inlayHint', params) - end -end - --- |lsp-handler| for the method `textDocument/inlayHint/refresh` ---@private function M.on_refresh(err, _, ctx, _) @@ -144,8 +96,11 @@ 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 - refresh({ bufnr = bufnr }) - break + local bufstate = bufstates[bufnr] + if bufstate and bufstate.enabled then + util._refresh('textDocument/inlayHint', { bufnr = bufnr }) + break + end end end end @@ -156,7 +111,9 @@ end --- Clear inlay hints ---@param bufnr (integer) Buffer handle, or 0 for current local function clear(bufnr) - bufnr = resolve_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end if not bufstates[bufnr] then return end @@ -175,17 +132,19 @@ end local function make_request(request_bufnr) reset_timer(request_bufnr) - refresh({ bufnr = request_bufnr }) + util._refresh('textDocument/inlayHint', { bufnr = request_bufnr }) end --- Enable inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current local function enable(bufnr) - bufnr = resolve_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end local bufstate = bufstates[bufnr] if not (bufstate and bufstate.enabled) then bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } - refresh({ bufnr = bufnr }) + util._refresh('textDocument/inlayHint', { bufnr = bufnr }) api.nvim_buf_attach(bufnr, true, { on_lines = function(_, cb_bufnr) if not bufstates[cb_bufnr].enabled then @@ -201,7 +160,7 @@ local function enable(bufnr) if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then bufstates[cb_bufnr] = { enabled = true, applied = {} } end - refresh({ bufnr = cb_bufnr }) + util._refresh('textDocument/inlayHint', { bufnr = cb_bufnr }) end, on_detach = function(_, cb_bufnr) clear(cb_bufnr) @@ -222,7 +181,9 @@ end --- Disable inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current local function disable(bufnr) - bufnr = resolve_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end if bufstates[bufnr] and bufstates[bufnr].enabled then clear(bufnr) bufstates[bufnr].enabled = nil @@ -233,7 +194,9 @@ end --- Toggle inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current local function toggle(bufnr) - bufnr = resolve_bufnr(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end local bufstate = bufstates[bufnr] if bufstate and bufstate.enabled then disable(bufnr) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 5bc0baf241..537a5eda39 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -641,6 +641,9 @@ function protocol.make_client_capabilities() }, }, textDocument = { + diagnostic = { + dynamicRegistration = false, + }, inlayHint = { dynamicRegistration = true, resolveSupport = { diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 9a6114c35b..0b06d2bbb5 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2183,6 +2183,46 @@ function M.lookup_section(settings, section) return settings end +---@private +--- Request updated LSP information for a buffer. +--- +---@param method string LSP method to call +---@param opts (nil|table) Optional arguments +--- - bufnr (integer, default: 0): Buffer to refresh +--- - only_visible (boolean, default: false): Whether to only refresh for the visible regions of the buffer +function M._refresh(method, opts) + opts = opts or {} + local bufnr = opts.bufnr + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end + local only_visible = opts.only_visible or false + for _, window in ipairs(api.nvim_list_wins()) do + if api.nvim_win_get_buf(window) == bufnr then + local first = vim.fn.line('w0', window) + local last = vim.fn.line('w$', window) + local params = { + textDocument = M.make_text_document_params(bufnr), + range = { + start = { line = first - 1, character = 0 }, + ['end'] = { line = last, character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, method, params) + end + end + if not only_visible then + local params = { + textDocument = M.make_text_document_params(bufnr), + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 }, + }, + } + vim.lsp.buf_request(bufnr, method, params) + end +end + M._get_line_byte_from_position = get_line_byte_from_position ---@nodoc -- cgit From 519b9929e94c94965b73ac4b04aedb03fd2708ca Mon Sep 17 00:00:00 2001 From: marshmallow Date: Wed, 19 Jul 2023 10:06:20 +1000 Subject: fix(ui.open): some URLs fail on Windows Problem: On Windows, `explorer.exe` fails to open some URLs, for example: :lua vim.ui.open('https://devdocs.io/#q=lua%20lua_call') https://github.com/neovim/neovim/pull/23401#issuecomment-1641015704 Solution: Use rundll32 instead. --- runtime/lua/vim/ui.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index fd06611da2..87b52787a0 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -136,7 +136,11 @@ function M.open(path) if vim.fn.has('mac') == 1 then cmd = { 'open', path } elseif vim.fn.has('win32') == 1 then - cmd = { 'explorer', path } + if vim.fn.executable('rundll32') == 1 then + cmd = { 'rundll32', 'url.dll,FileProtocolHandler', 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('xdg-open') == 1 then -- cgit From cfcda91827d73dda740e372b237383c19e63dab9 Mon Sep 17 00:00:00 2001 From: Gnik Date: Sat, 22 Jul 2023 13:27:25 +0545 Subject: docs(lua): add missing word in docs for vim.empty_dict (#24401) --- runtime/lua/vim/_meta/builtin.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 980e40d61b..88f07f2a51 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -69,10 +69,10 @@ --- to other restrictions such as |textlock|). function vim.in_fast_event() end ---- Creates a special empty table (marked with a metatable), which Nvim to an ---- empty dictionary when translating Lua values to Vimscript or API types. ---- Nvim by default converts an empty table `{}` without this metatable to an ---- list/array. +--- Creates a special empty table (marked with a metatable), which Nvim +--- converts to an empty dictionary when translating Lua values to Vimscript +--- or API types. Nvim by default converts an empty table `{}` without this +--- metatable to an list/array. --- --- Note: If numeric keys are present in the table, Nvim ignores the metatable --- marker and converts the dict to a list/array anyway. -- cgit From 24e3ee9d07e1433cb13b4d96ec20999f5f02b204 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Sat, 22 Jul 2023 09:52:13 +0100 Subject: fix(api/options): validate buf and win Fixes #24398 --- runtime/lua/vim/_options.lua | 36 +++++++----------------------------- 1 file changed, 7 insertions(+), 29 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index d54e8b447c..e1c125baf2 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -124,14 +124,12 @@ local function get_option_metatype(name, info) return info.type end -local options_info = setmetatable({}, { - __index = function(t, k) - local info = api.nvim_get_option_info(k) - info.metatype = get_option_metatype(k, info) - rawset(t, k, info) - return rawget(t, k) - end, -}) +--- @param name string +local function get_options_info(name) + local info = api.nvim_get_option_info(name) + info.metatype = get_option_metatype(name, info) + return info +end ---Environment variables defined in the editor session. ---See |expand-env| and |:let-environment| for the Vimscript behavior. @@ -155,34 +153,16 @@ vim.env = setmetatable({}, { end, }) -local function opt_validate(option_name, target_scope) - local scope = options_info[option_name].scope - if scope ~= target_scope then - local scope_to_string = { buf = 'buffer', win = 'window' } - error( - string.format( - [['%s' is a %s option, not a %s option. See ":help %s"]], - option_name, - scope_to_string[scope] or scope, - scope_to_string[target_scope] or target_scope, - option_name - ) - ) - end -end - local function new_buf_opt_accessor(bufnr) return setmetatable({}, { __index = function(_, k) if bufnr == nil and type(k) == 'number' then return new_buf_opt_accessor(k) end - opt_validate(k, 'buf') return api.nvim_get_option_value(k, { buf = bufnr or 0 }) end, __newindex = function(_, k, v) - opt_validate(k, 'buf') return api.nvim_set_option_value(k, v, { buf = bufnr or 0 }) end, }) @@ -203,7 +183,6 @@ local function new_win_opt_accessor(winid, bufnr) error('only bufnr=0 is supported') end - opt_validate(k, 'win') -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value return api.nvim_get_option_value(k, { scope = bufnr and 'local' or nil, @@ -212,7 +191,6 @@ local function new_win_opt_accessor(winid, bufnr) end, __newindex = function(_, k, v) - opt_validate(k, 'win') -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value return api.nvim_set_option_value(k, v, { scope = bufnr and 'local' or nil, @@ -680,7 +658,7 @@ local function create_option_accessor(scope) local option_mt local function make_option(name, value) - local info = assert(options_info[name], 'Not a valid option name: ' .. name) + local info = assert(get_options_info(name), 'Not a valid option name: ' .. name) if type(value) == 'table' and getmetatable(value) == option_mt then assert(name == value._name, "must be the same value, otherwise that's weird.") -- cgit From 4b57ff77febbe6073bc4c5c3a45b0ad0d5d40e6c Mon Sep 17 00:00:00 2001 From: Chris AtLee Date: Sat, 22 Jul 2023 05:00:17 -0400 Subject: refactor(lsp): use LspNotify for inlay_hint (#24411) --- runtime/lua/vim/lsp.lua | 6 +-- runtime/lua/vim/lsp/inlay_hint.lua | 86 +++++++++++++++----------------------- 2 files changed, 37 insertions(+), 55 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fa2a888a54..78b5f53723 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1572,9 +1572,9 @@ function lsp.start_client(config) changetracking.flush(client) end - local result = rpc.notify(method, params) + local client_active = rpc.notify(method, params) - if result then + if client_active then vim.schedule(function() nvim_exec_autocmds('LspNotify', { modeline = false, @@ -1587,7 +1587,7 @@ function lsp.start_client(config) end) end - return result + return client_active end ---@private diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 1c29e8a866..912ce6898e 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -6,28 +6,14 @@ local M = {} ---@class lsp._inlay_hint.bufstate ---@field version integer ---@field client_hint table> client_id -> (lnum -> hints) ----@field enabled boolean Whether inlay hints are enabled for the buffer ----@field timer uv.uv_timer_t? Debounce timer associated with the buffer ---@field applied table Last version of hints applied to this line - +---@field autocmd_id integer The autocmd id for the buffer ---@type table local bufstates = {} local namespace = api.nvim_create_namespace('vim_lsp_inlayhint') local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {}) ---- Reset the request debounce timer of a buffer -local function reset_timer(reset_bufnr) - local timer = bufstates[reset_bufnr].timer - if timer then - bufstates[reset_bufnr].timer = nil - if not timer:is_closing() then - timer:stop() - timer:close() - end - end -end - --- |lsp-handler| for the method `textDocument/inlayHint` --- Store hints for a specific buffer and client ---@private @@ -97,7 +83,7 @@ function M.on_refresh(err, _, ctx, _) for _, winid in ipairs(api.nvim_list_wins()) do if api.nvim_win_get_buf(winid) == bufnr then local bufstate = bufstates[bufnr] - if bufstate and bufstate.enabled then + if bufstate then util._refresh('textDocument/inlayHint', { bufnr = bufnr }) break end @@ -117,7 +103,6 @@ local function clear(bufnr) if not bufstates[bufnr] then return end - reset_timer(bufnr) local bufstate = bufstates[bufnr] local client_lens = (bufstate or {}).client_hint or {} local client_ids = vim.tbl_keys(client_lens) @@ -130,9 +115,17 @@ local function clear(bufnr) api.nvim__buf_redraw_range(bufnr, 0, -1) end -local function make_request(request_bufnr) - reset_timer(request_bufnr) - util._refresh('textDocument/inlayHint', { bufnr = request_bufnr }) +--- Disable inlay hints for a buffer +---@param bufnr (integer) Buffer handle, or 0 for current +local function disable(bufnr) + if bufnr == nil or bufnr == 0 then + bufnr = api.nvim_get_current_buf() + end + clear(bufnr) + if bufstates[bufnr] and bufstates[bufnr].autocmd_id then + api.nvim_del_autocmd(bufstates[bufnr].autocmd_id) + end + bufstates[bufnr] = nil end --- Enable inlay hints for a buffer @@ -142,35 +135,37 @@ local function enable(bufnr) bufnr = api.nvim_get_current_buf() end local bufstate = bufstates[bufnr] - if not (bufstate and bufstate.enabled) then - bufstates[bufnr] = { enabled = true, timer = nil, applied = {} } - util._refresh('textDocument/inlayHint', { bufnr = bufnr }) - api.nvim_buf_attach(bufnr, true, { - on_lines = function(_, cb_bufnr) - if not bufstates[cb_bufnr].enabled then - return true + if not bufstate then + bufstates[bufnr] = { applied = {} } + bufstates[bufnr].autocmd_id = api.nvim_create_autocmd('LspNotify', { + buffer = bufnr, + callback = function(opts) + if opts.data.method ~= 'textDocument/didChange' then + return + end + if bufstates[bufnr] then + util._refresh('textDocument/inlayHint', { bufnr = bufnr }) end - reset_timer(cb_bufnr) - bufstates[cb_bufnr].timer = vim.defer_fn(function() - make_request(cb_bufnr) - end, 200) end, + group = augroup, + }) + util._refresh('textDocument/inlayHint', { bufnr = bufnr }) + api.nvim_buf_attach(bufnr, true, { on_reload = function(_, cb_bufnr) clear(cb_bufnr) - if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then - bufstates[cb_bufnr] = { enabled = true, applied = {} } + if bufstates[cb_bufnr] then + bufstates[cb_bufnr].applied = {} + util._refresh('textDocument/inlayHint', { bufnr = cb_bufnr }) end - util._refresh('textDocument/inlayHint', { bufnr = cb_bufnr }) end, on_detach = function(_, cb_bufnr) - clear(cb_bufnr) - bufstates[cb_bufnr] = nil + disable(cb_bufnr) end, }) api.nvim_create_autocmd('LspDetach', { buffer = bufnr, - callback = function(opts) - clear(opts.buf) + callback = function() + disable(bufnr) end, once = true, group = augroup, @@ -178,19 +173,6 @@ local function enable(bufnr) end end ---- Disable inlay hints for a buffer ----@param bufnr (integer) Buffer handle, or 0 for current -local function disable(bufnr) - if bufnr == nil or bufnr == 0 then - bufnr = api.nvim_get_current_buf() - end - if bufstates[bufnr] and bufstates[bufnr].enabled then - clear(bufnr) - bufstates[bufnr].enabled = nil - bufstates[bufnr].timer = nil - end -end - --- Toggle inlay hints for a buffer ---@param bufnr (integer) Buffer handle, or 0 for current local function toggle(bufnr) @@ -198,7 +180,7 @@ local function toggle(bufnr) bufnr = api.nvim_get_current_buf() end local bufstate = bufstates[bufnr] - if bufstate and bufstate.enabled then + if bufstate then disable(bufnr) else enable(bufnr) -- cgit From 48085e40bb0e4435ddb2eb7f14b4531b943e0c89 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Mon, 24 Jul 2023 10:26:38 -0400 Subject: fix(treesitter): stop() should treat 0 as current buffer #24450 --- runtime/lua/vim/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 8bd85d3734..04420c81e3 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -462,7 +462,7 @@ end --- ---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer) function M.stop(bufnr) - bufnr = bufnr or api.nvim_get_current_buf() + bufnr = (bufnr and bufnr ~= 0) and bufnr or api.nvim_get_current_buf() if M.highlighter.active[bufnr] then M.highlighter.active[bufnr]:destroy() -- cgit From 6a486c44e66f05ae11137ad7a192b89989192566 Mon Sep 17 00:00:00 2001 From: marshmallow Date: Tue, 25 Jul 2023 01:35:19 +1000 Subject: fix(gx): move to to _init_default_mappings #24420 Problem: netrw may conflict with the Nvim default "gx" mapping. Solution: Initialize keymapping earlier by moving it to vim._init_default_mappings(). That also avoids needing to check maparg(). --- runtime/lua/vim/_editor.lua | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 4372aef7b3..87f6d6581c 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1048,6 +1048,33 @@ function vim._init_default_mappings() -- Use : instead of so that ranges are supported. #19365 map('n', '&', ':&&') + -- gx + + -- TODO: use vim.region() when it lands... #13896 #16843 + local function get_visual_selection() + local save_a = vim.fn.getreginfo('a') + vim.cmd([[norm! "ay]]) + local selection = vim.fn.getreg('a', 1) + vim.fn.setreg('a', save_a) + return selection + end + + local function do_open(uri) + local _, err = vim.ui.open(uri) + if err then + vim.notify(err, vim.log.levels.ERROR) + end + 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('')) + end, { desc = gx_desc }) + vim.keymap.set({ 'x' }, 'gx', function() + do_open(get_visual_selection()) + end, { desc = gx_desc }) + -- menus -- TODO VimScript, no l10n -- cgit From add7e106d59b8e3822310846a850b3ed3fb5db0e Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Mon, 24 Jul 2023 08:58:59 -0700 Subject: fix(lsp): noisy warning about offset_encodings #24441 In the case you hit this warning in a buffer (like with C++ and clangd), this message potentially fires over and over again making it difficult to use the editor at all. --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 0b06d2bbb5..4ff420cf48 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2026,7 +2026,7 @@ function M._get_offset_encoding(bufnr) if not offset_encoding then offset_encoding = this_offset_encoding elseif offset_encoding ~= this_offset_encoding then - vim.notify( + vim.notify_once( 'warning: multiple different client offset_encodings detected for buffer, this is not supported yet', vim.log.levels.WARN ) -- cgit From a37d568082ad2a6fd479bc0584f1088b51019b7f Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Mon, 24 Jul 2023 12:09:53 -0400 Subject: fix(lsp): send empty "added" list when removing workspace folder #24440 When adding `workspace/didChangeWorkspaceFolders` support to my [language server](https://github.com/elixir-tools/next-ls), I noticed that when neovim removes a workspace, it sends an empty table (which is serialized to an empty JSON array) for the value in the `added` field. This does not follow the spec; the `added` table should just be empty. The following error led me to this discovery. Note the payload includes `"added" => [[]]`: ``` 22:46:48.476 [error] LSP Exited. Last message received: handle_notification %{"jsonrpc" => "2.0", "method" => "workspace/didChangeWorkspaceFolders", "params" => %{"event" => %{"added" => [[]], "removed" => [%{"name" => "/Users/mitchell/src/gen_lsp", "uri" => "file:///Users/mitchell/src/gen_lsp"}]}}} ** (MatchError) no match of right hand side value: {:error, %{"params" => %{"event" => %{"added" => [error: "expected a map"]}}}} (gen_lsp 0.4.0) lib/gen_lsp.ex:265: anonymous fn/4 in GenLSP.loop/3 (gen_lsp 0.4.0) lib/gen_lsp.ex:292: GenLSP.attempt/3 (stdlib 5.0.2) proc_lib.erl:241: :proc_lib.init_p_do_apply/3 ``` --- runtime/lua/vim/lsp/buf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 46f582dc7e..02d8d0fa7b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -514,7 +514,7 @@ function M.remove_workspace_folder(workspace_folder) return end local params = util.make_workspace_params( - { {} }, + {}, { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } ) for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do -- cgit From 966eb8e0b3be1b409e491d5cf1e32e82e806a134 Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 24 Jul 2023 19:26:17 +0200 Subject: fix(lsp): announce publishDiagnostics.dataSupport (#24442) Neovim already passed `data` element from published diagnostic to code action, but failed to announce it in client capabilities. Here is the test that shows that `data` element is returned by `vim.lsp.diagnostic.get_line_diagnostics()`: https://github.com/neovim/neovim/blob/f56c1848091bb64c63b5bc25ec74bcbd2f52bdde/test/functional/plugin/lsp/diagnostic_spec.lua#L103-L115 and then `get_line_diagnostics()` is used to construct the context for code action request: https://github.com/neovim/neovim/blob/f56c1848091bb64c63b5bc25ec74bcbd2f52bdde/runtime/lua/vim/lsp/buf.lua#L742 --- runtime/lua/vim/lsp/protocol.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 537a5eda39..31cc071d18 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -828,6 +828,7 @@ function protocol.make_client_capabilities() return res end)(), }, + dataSupport = true, }, callHierarchy = { dynamicRegistration = false, -- cgit From 7668f89d5be6d463bf6ab0c2d3a0393e3ec26e7f Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Mon, 24 Jul 2023 20:21:35 +0200 Subject: fix(lsp): replace @private with @nodoc for public client functions (#24415) * fix(lsp): replace @private with @nodoc for public client functions To prevent lua-ls warnings in plugins which use the functions. * fix(lsp): remove duplicate type annotations/class definitions These annotations became duplicate with https://github.com/neovim/neovim/pull/23750 --- runtime/lua/vim/lsp.lua | 10 +++--- runtime/lua/vim/lsp/types.lua | 79 ++----------------------------------------- 2 files changed, 7 insertions(+), 82 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 78b5f53723..1a1dc684ba 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1455,7 +1455,7 @@ function lsp.start_client(config) end) end - ---@private + ---@nodoc --- Sends a request to the server. --- --- This is a thin wrapper around {client.rpc.request} with some additional @@ -1559,7 +1559,7 @@ function lsp.start_client(config) return request_result end - ---@private + ---@nodoc --- Sends a notification to an LSP server. --- ---@param method string LSP method name. @@ -1590,7 +1590,7 @@ function lsp.start_client(config) return client_active end - ---@private + ---@nodoc --- Cancels a request with a given request id. --- ---@param id (integer) id of request to cancel @@ -1613,7 +1613,8 @@ function lsp.start_client(config) -- Track this so that we can escalate automatically if we've already tried a -- graceful shutdown local graceful_shutdown_failed = false - ---@private + + ---@nodoc --- Stops a client, optionally with force. --- ---By default, it will just ask the - server to shutdown without force. If @@ -2311,7 +2312,6 @@ function lsp.omnifunc(findstart, base) local startbyte - ---@private local function on_done() local mode = api.nvim_get_mode()['mode'] if mode == 'i' or mode == 'ic' then diff --git a/runtime/lua/vim/lsp/types.lua b/runtime/lua/vim/lsp/types.lua index cdfbcfb11b..98e948c945 100644 --- a/runtime/lua/vim/lsp/types.lua +++ b/runtime/lua/vim/lsp/types.lua @@ -5,92 +5,17 @@ ---@class lsp.HandlerContext ---@field method string ---@field client_id integer ----@field bufnr integer ----@field params any +---@field bufnr? integer +---@field params? any ---@class lsp.ResponseError ---@field code integer ---@field message string ---@field data string|number|boolean|table[]|table|nil ----@class lsp.ShowMessageRequestParams ----@field type lsp.MessageType ----@field message string ----@field actions nil|lsp.MessageActionItem[] - ----@class lsp.MessageActionItem ----@field title string - ----@class lsp.FileEvent ----@field uri string ----@field type lsp.FileChangeType - ----@class lsp.Position ----@field line integer ----@field character integer - ----@class lsp.Range ----@field start lsp.Position ----@field end lsp.Position - ----@class lsp.Diagnostic ----@field range lsp.Range ----@field message string ----@field severity? lsp.DiagnosticSeverity ----@field code integer | string ----@field source string ----@field tags? lsp.DiagnosticTag[] ----@field relatedInformation DiagnosticRelatedInformation[] - --- @class lsp.DocumentFilter --- @field language? string --- @field scheme? string --- @field pattern? string ---- @alias lsp.DocumentSelector lsp.DocumentFilter[] - --- @alias lsp.RegisterOptions any | lsp.StaticRegistrationOptions | lsp.TextDocumentRegistrationOptions - ---- @class lsp.Registration ---- @field id string ---- @field method string ---- @field registerOptions? lsp.RegisterOptions - ---- @alias lsp.RegistrationParams {registrations: lsp.Registration[]} - ---- @class lsp.StaticRegistrationOptions ---- @field id? string - ---- @class lsp.TextDocumentRegistrationOptions ---- @field documentSelector? lsp.DocumentSelector - ---- @class lsp.Unregistration ---- @field id string ---- @field method string - ---- @alias lsp.UnregistrationParams {unregisterations: lsp.Unregistration[]} - ----@class lsp.Location ----@field uri string ----@field range lsp.Range - ----@class lsp.MarkupContent ----@field kind string ----@field value string - ----@class lsp.InlayHintLabelPart ----@field value string ----@field tooltip? string | lsp.MarkupContent ----@field location? lsp.Location - ----@class lsp.TextEdit ----@field range lsp.Range ----@field newText string - ----@class lsp.InlayHint ----@field position lsp.Position ----@field label string | lsp.InlayHintLabelPart[] ----@field kind? integer ----@field textEdits? lsp.TextEdit[] ----@field paddingLeft? boolean ----@field paddingRight? boolean -- cgit From 4d0f4c3de9cbdcf85e606b5aaf9488820b95b679 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 25 Jul 2023 19:38:48 +0800 Subject: fix(lsp): E403 if doc contains multiple codeblocks #24458 Problem: Content that has codeblocks with different languages, results in multiple calls to: syntax include vim syntax/vim.vim which raises error: E403: syntax sync: line continuations pattern specified twice Before ba8f19ebb67ca27d746f4b1cd902ab3d807eace3, this was avoided by using pcall() to ignore the error. Solution: Restore the use of pcall() to ignore the error. We plan to replace this logic with a treesitter approach, so this is good enough for now. Fix #24431 --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4ff420cf48..4d3071fd68 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1466,7 +1466,7 @@ function M.stylize_markdown(bufnr, contents, opts) if #api.nvim_get_runtime_file(('syntax/%s.vim'):format(ft), true) == 0 then return end - vim.cmd(string.format('syntax include %s syntax/%s.vim', lang, ft)) + pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft)) langs[lang] = true end vim.cmd( -- cgit From 20c331915f4e317c615c7cfea469a9baedd2e4f7 Mon Sep 17 00:00:00 2001 From: Christoph Hasse Date: Tue, 25 Jul 2023 08:40:13 -0400 Subject: fix(lsp): SignatureHelp docstring is not escaped #16702 Problem: Nvim LSP client always treats signature.documentation as markdown, even if the server returns a plain string. Per https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#signatureInformation in a SignatureInformation response, the documentation field can be either "string" or "MarkupContent". Solution: If signature.documentation is a string, treat it as "plaintext". Closes #16563 --- runtime/lua/vim/lsp/util.lua | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 4d3071fd68..738e23ff28 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1002,6 +1002,12 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers end list_extend(contents, split(label, '\n', { plain = true })) if signature.documentation then + -- if LSP returns plain string, we treat it as plaintext. This avoids + -- special characters like underscore or similar from being interpreted + -- as markdown font modifiers + if type(signature.documentation) == 'string' then + signature.documentation = { kind = 'plaintext', value = signature.documentation } + end M.convert_input_to_markdown_lines(signature.documentation, contents) end if signature.parameters and #signature.parameters > 0 then -- cgit From 74bd4aba57d2f1b224abe46a6de82911d14ef6c1 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 25 Jul 2023 16:57:19 +0200 Subject: fix(lsp): fix multi client handling workspace_folder methods (#18839) `buf_notify` sends the notification to all clients of a buffer, calling that inside a loop over clients multiplies the amount of notifications. --- runtime/lua/vim/lsp/buf.lua | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 02d8d0fa7b..5e0e429021 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -480,11 +480,13 @@ function M.add_workspace_folder(workspace_folder) print(workspace_folder, ' is not a valid directory') return end - local params = util.make_workspace_params( - { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }, - {} - ) - for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do + 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() + 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 @@ -494,11 +496,11 @@ function M.add_workspace_folder(workspace_folder) end end if not found then - vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.notify('workspace/didChangeWorkspaceFolders', params) if not client.workspace_folders then client.workspace_folders = {} end - table.insert(client.workspace_folders, params.event.added[1]) + table.insert(client.workspace_folders, new_workspace) end end end @@ -513,14 +515,16 @@ function M.remove_workspace_folder(workspace_folder) if not (workspace_folder and #workspace_folder > 0) then return end - local params = util.make_workspace_params( - {}, - { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } } - ) - for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do - for idx, folder in pairs(client.workspace_folders or {}) do + 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() + 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 - vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) + client.notify('workspace/didChangeWorkspaceFolders', params) client.workspace_folders[idx] = nil return end -- cgit From fd089c8e50c211d7beae15dbc9492ae5a1a5f2e2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Wed, 26 Jul 2023 09:50:54 +0100 Subject: feat(lua): typing for vim.fn.* (#24473) Problem: No LSP information for `vim.fn.*` Solution: Add meta file for `vim.fn.*`. --- runtime/lua/vim/_meta/vimfn.lua | 11162 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 11162 insertions(+) create mode 100644 runtime/lua/vim/_meta/vimfn.lua (limited to 'runtime/lua/vim') diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua new file mode 100644 index 0000000000..b70e361e48 --- /dev/null +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -0,0 +1,11162 @@ +--- @meta +-- THIS FILE IS GENERATED +-- DO NOT EDIT + +--- Return the absolute value of {expr}. When {expr} evaluates to +--- a |Float| abs() returns a |Float|. When {expr} can be +--- converted to a |Number| abs() returns a |Number|. Otherwise +--- abs() gives an error message and returns -1. +--- Examples: > +--- echo abs(1.456) +--- < 1.456 > +--- echo abs(-5.456) +--- < 5.456 > +--- echo abs(-4) +--- < 4 +--- +--- Can also be used as a |method|: > +--- Compute()->abs() +--- +--- @param expr any +--- @return any +function vim.fn.abs(expr) end + +--- Return the arc cosine of {expr} measured in radians, as a +--- |Float| in the range of [0, pi]. +--- {expr} must evaluate to a |Float| or a |Number| in the range +--- [-1, 1]. +--- Returns NaN if {expr} is outside the range [-1, 1]. Returns +--- 0.0 if {expr} is not a |Float| or a |Number|. +--- Examples: > +--- :echo acos(0) +--- < 1.570796 > +--- :echo acos(-0.5) +--- < 2.094395 +--- +--- Can also be used as a |method|: > +--- Compute()->acos() +--- +--- @param expr any +--- @return number +function vim.fn.acos(expr) end + +--- Append the item {expr} to |List| or |Blob| {object}. Returns +--- the resulting |List| or |Blob|. Examples: > +--- :let alist = add([1, 2, 3], item) +--- :call add(mylist, "woodstock") +--- +--- mylist->add(val1)->add(val2) +--- +--- @param object any +--- @param expr any +--- @return any +function vim.fn.add(object, expr) end + +--- Bitwise AND on the two arguments. The arguments are converted +--- to a number. A List, Dict or Float argument causes an error. +--- Also see `or()` and `xor()`. +--- Example: > +--- :let flag = and(bits, 0x80) +--- +--- :let flag = bits->and(0x80) +--- +--- @param expr any +--- @param expr1 any +--- @return any +vim.fn['and'] = function(expr, expr1) end + +--- Returns Dictionary of |api-metadata|. +--- +--- View it in a nice human-readable format: > +--- :lua vim.print(vim.fn.api_info()) +--- +--- @return any +function vim.fn.api_info() end + +--- When {text} is a |List|: Append each item of the |List| as a +--- text line below line {lnum} in the current buffer. +--- Otherwise append {text} as one text line below line {lnum} in +--- the current buffer. +--- Any type of item is accepted and converted to a String. +--- {lnum} can be zero to insert a line before the first one. +--- {lnum} is used like with |getline()|. +--- Returns 1 for failure ({lnum} out of range or out of memory), +--- 0 for success. Example: > +--- :let failed = append(line('$'), "# THE END") +--- :let failed = append(0, ["Chapter 1", "the beginning"]) +--- +--- +--- mylist->append(lnum) +--- +--- @param lnum integer +--- @param text any +--- @return any +function vim.fn.append(lnum, text) end + +--- Like |append()| but append the text in buffer {expr}. +--- +--- This function works only for loaded buffers. First call +--- |bufload()| if needed. +--- +--- For the use of {buf}, see |bufname()|. +--- +--- {lnum} is the line number to append below. Note that using +--- |line()| would use the current buffer, not the one appending +--- to. Use "$" to append at the end of the buffer. Other string +--- values are not supported. +--- +--- On success 0 is returned, on failure 1 is returned. +--- +--- If {buf} is not a valid buffer or {lnum} is not valid, an +--- error message is given. Example: > +--- :let failed = appendbufline(13, 0, "# THE START") +--- < +--- Can also be used as a |method| after a List: > +--- mylist->appendbufline(buf, lnum) +--- +--- @param buf any +--- @param lnum integer +--- @param text any +--- @return any +function vim.fn.appendbufline(buf, lnum, text) end + +--- The result is the number of files in the argument list. See +--- |arglist|. +--- If {winid} is not supplied, the argument list of the current +--- window is used. +--- If {winid} is -1, the global argument list is used. +--- Otherwise {winid} specifies the window of which the argument +--- list is used: either the window number or the window ID. +--- Returns -1 if the {winid} argument is invalid. +--- +--- *argidx()* +--- @param winid? integer +--- @return integer +function vim.fn.argc(winid) end + +--- the first file. argc() - 1 is the last one. See |arglist|. +--- +--- *arglistid()* +--- @return integer +function vim.fn.argidx() end + +--- Return the argument list ID. This is a number which +--- identifies the argument list being used. Zero is used for the +--- global argument list. See |arglist|. +--- Returns -1 if the arguments are invalid. +--- +--- Without arguments use the current window. +--- With {winnr} only use this window in the current tab page. +--- With {winnr} and {tabnr} use the window in the specified tab +--- page. +--- {winnr} can be the window number or the |window-ID|. +--- +--- *argv()* +--- @param winnr? integer +--- @param tabnr? integer +--- @return integer +function vim.fn.arglistid(winnr, tabnr) end + +--- The result is the {nr}th file in the argument list. See +--- |arglist|. "argv(0)" is the first one. Example: > +--- :let i = 0 +--- :while i < argc() +--- : let f = escape(fnameescape(argv(i)), '.') +--- : exe 'amenu Arg.' .. f .. ' :e ' .. f .. '' +--- : let i = i + 1 +--- :endwhile +--- +--- :echo asin(0.8) +--- < 0.927295 > +--- :echo asin(-0.5) +--- < -0.523599 +--- +--- Can also be used as a |method|: > +--- Compute()->asin() +--- +--- +--- assert_ functions are documented here: |assert-functions-details| +--- +--- +--- @param expr any +--- @return any +function vim.fn.asin(expr) end + +--- Run {cmd} and add an error message to |v:errors| if it does +--- NOT produce a beep or visual bell. +--- Also see |assert_fails()|, |assert_nobeep()| and +--- |assert-return|. +--- +--- Can also be used as a |method|: > +--- GetCmd()->assert_beeps() +--- < +--- *assert_equal()* +--- @param cmd any +--- @return 0|1 +function vim.fn.assert_beeps(cmd) end + +--- When {expected} and {actual} are not equal an error message is +--- added to |v:errors| and 1 is returned. Otherwise zero is +--- returned. |assert-return| +--- The error is in the form "Expected {expected} but got +--- {actual}". When {msg} is present it is prefixed to that. +--- +--- There is no automatic conversion, the String "4" is different +--- from the Number 4. And the number 4 is different from the +--- Float 4.0. The value of 'ignorecase' is not used here, case +--- always matters. +--- Example: > +--- assert_equal('foo', 'bar') +--- +--- mylist->assert_equal([1, 2, 3]) +--- +--- < *assert_equalfile()* +--- @param expected any +--- @param actual any +--- @param msg? any +--- @return 0|1 +function vim.fn.assert_equal(expected, actual, msg) end + +--- When the files {fname-one} and {fname-two} do not contain +--- exactly the same text an error message is added to |v:errors|. +--- Also see |assert-return|. +--- When {fname-one} or {fname-two} does not exist the error will +--- mention that. +--- +--- Can also be used as a |method|: > +--- GetLog()->assert_equalfile('expected.log') +--- +--- @return any +function vim.fn.assert_equalfile() end + +--- When v:exception does not contain the string {error} an error +--- message is added to |v:errors|. Also see |assert-return|. +--- This can be used to assert that a command throws an exception. +--- Using the error number, followed by a colon, avoids problems +--- with translations: > +--- try +--- commandthatfails +--- call assert_false(1, 'command should have failed') +--- catch +--- call assert_exception('E492:') +--- endtry +--- < +--- *assert_fails()* +--- @param error any +--- @param msg? any +--- @return 0|1 +function vim.fn.assert_exception(error, msg) end + +--- Run {cmd} and add an error message to |v:errors| if it does +--- NOT produce an error or when {error} is not found in the +--- error message. Also see |assert-return|. +--- +--- When {error} is a string it must be found literally in the +--- first reported error. Most often this will be the error code, +--- including the colon, e.g. "E123:". > +--- assert_fails('bad cmd', 'E987:') +--- < +--- When {error} is a |List| with one or two strings, these are +--- used as patterns. The first pattern is matched against the +--- first reported error: > +--- assert_fails('cmd', ['E987:.*expected bool']) +--- +--- assert_fails('cmd', ['', 'E987:']) +--- < +--- If {msg} is empty then it is not used. Do this to get the +--- default message when passing the {lnum} argument. +--- +--- When {lnum} is present and not negative, and the {error} +--- argument is present and matches, then this is compared with +--- the line number at which the error was reported. That can be +--- the line number in a function or in a script. +--- +--- When {context} is present it is used as a pattern and matched +--- against the context (script name or function name) where +--- {lnum} is located in. +--- +--- Note that beeping is not considered an error, and some failing +--- commands only beep. Use |assert_beeps()| for those. +--- +--- Can also be used as a |method|: > +--- GetCmd()->assert_fails('E99:') +--- +--- @param cmd any +--- @param error? any +--- @param msg? any +--- @param lnum? integer +--- @param context? any +--- @return 0|1 +function vim.fn.assert_fails(cmd, error, msg, lnum, context) end + +--- When {actual} is not false an error message is added to +--- |v:errors|, like with |assert_equal()|. +--- The error is in the form "Expected False but got {actual}". +--- When {msg} is present it is prepended to that. +--- Also see |assert-return|. +--- +--- A value is false when it is zero. When {actual} is not a +--- number the assert fails. +--- +--- Can also be used as a |method|: > +--- GetResult()->assert_false() +--- +--- @param actual any +--- @param msg? any +--- @return 0|1 +function vim.fn.assert_false(actual, msg) end + +--- This asserts number and |Float| values. When {actual} is lower +--- than {lower} or higher than {upper} an error message is added +--- to |v:errors|. Also see |assert-return|. +--- The error is in the form "Expected range {lower} - {upper}, +--- but got {actual}". When {msg} is present it is prefixed to +--- that. +--- +--- *assert_match()* +--- @param lower any +--- @param upper any +--- @param actual any +--- @param msg? any +--- @return 0|1 +function vim.fn.assert_inrange(lower, upper, actual, msg) end + +--- When {pattern} does not match {actual} an error message is +--- added to |v:errors|. Also see |assert-return|. +--- The error is in the form "Pattern {pattern} does not match +--- {actual}". When {msg} is present it is prefixed to that. +--- +--- {pattern} is used as with |expr-=~|: The matching is always done +--- like 'magic' was set and 'cpoptions' is empty, no matter what +--- the actual value of 'magic' or 'cpoptions' is. +--- +--- {actual} is used as a string, automatic conversion applies. +--- Use "^" and "$" to match with the start and end of the text. +--- Use both to match the whole text. +--- +--- Example: > +--- assert_match('^f.*o$', 'foobar') +--- +--- getFile()->assert_match('foo.*') +--- < +--- @param pattern any +--- @param actual any +--- @param msg? any +--- @return 0|1 +function vim.fn.assert_match(pattern, actual, msg) end + +--- Run {cmd} and add an error message to |v:errors| if it +--- produces a beep or visual bell. +--- Also see |assert_beeps()|. +--- +--- Can also be used as a |method|: > +--- GetCmd()->assert_nobeep() +--- < +--- *assert_notequal()* +--- @param cmd any +--- @return 0|1 +function vim.fn.assert_nobeep(cmd) end + +--- The opposite of `assert_equal()`: add an error message to +--- |v:errors| when {expected} and {actual} are equal. +--- Also see |assert-return|. +--- +--- Can also be used as a |method|: > +--- mylist->assert_notequal([1, 2, 3]) +--- +--- < *assert_notmatch()* +--- @param expected any +--- @param actual any +--- @param msg? any +--- @return any +function vim.fn.assert_notequal(expected, actual, msg) end + +--- The opposite of `assert_match()`: add an error message to +--- |v:errors| when {pattern} matches {actual}. +--- Also see |assert-return|. +--- +--- Can also be used as a |method|: > +--- getFile()->assert_notmatch('bar.*') +--- +--- +--- @param pattern any +--- @param actual any +--- @param msg? any +--- @return any +function vim.fn.assert_notmatch(pattern, actual, msg) end + +--- Report a test failure directly, using String {msg}. +--- Always returns one. +--- +--- Can also be used as a |method|: > +--- GetMessage()->assert_report() +--- +--- +--- @param msg any +--- @return any +function vim.fn.assert_report(msg) end + +--- When {actual} is not true an error message is added to +--- |v:errors|, like with |assert_equal()|. +--- Also see |assert-return|. +--- A value is |TRUE| when it is a non-zero number or |v:true|. +--- When {actual} is not a number or |v:true| the assert fails. +--- When {msg} is given it precedes the default message. +--- +--- Can also be used as a |method|: > +--- GetResult()->assert_true() +--- < +--- +--- @param actual any +--- @param msg? any +--- @return any +function vim.fn.assert_true(actual, msg) end + +--- Return the principal value of the arc tangent of {expr}, in +--- the range [-pi/2, +pi/2] radians, as a |Float|. +--- {expr} must evaluate to a |Float| or a |Number|. +--- Returns 0.0 if {expr} is not a |Float| or a |Number|. +--- Examples: > +--- :echo atan(100) +--- < 1.560797 > +--- :echo atan(-4.01) +--- < -1.326405 +--- +--- Can also be used as a |method|: > +--- Compute()->atan() +--- +--- @param expr any +--- @return number +function vim.fn.atan(expr) end + +--- Return the arc tangent of {expr1} / {expr2}, measured in +--- radians, as a |Float| in the range [-pi, pi]. +--- {expr1} and {expr2} must evaluate to a |Float| or a |Number|. +--- Returns 0.0 if {expr1} or {expr2} is not a |Float| or a +--- |Number|. +--- Examples: > +--- :echo atan2(-1, 1) +--- < -0.785398 > +--- :echo atan2(1, -1) +--- < 2.356194 +--- +--- Can also be used as a |method|: > +--- Compute()->atan2(1) +--- +--- @param expr1 any +--- @param expr2 any +--- @return number +function vim.fn.atan2(expr1, expr2) end + +--- Return a List containing the number value of each byte in Blob +--- {blob}. Examples: > +--- blob2list(0z0102.0304) returns [1, 2, 3, 4] +--- blob2list(0z) returns [] +--- +--- GetBlob()->blob2list() +--- < +--- *browse()* +--- @param blob any +--- @return any +function vim.fn.blob2list(blob) end + +--- Put up a file requester. This only works when "has("browse")" +--- returns |TRUE| (only in some GUI versions). +--- The input fields are: +--- {save} when |TRUE|, select file to write +--- {title} title for the requester +--- {initdir} directory to start browsing in +--- {default} default file name +--- An empty string is returned when the "Cancel" button is hit, +--- something went wrong, or browsing is not possible. +--- +--- *browsedir()* +--- @param save any +--- @param title any +--- @param initdir any +--- @param default any +--- @return any +function vim.fn.browse(save, title, initdir, default) end + +--- Put up a directory requester. This only works when +--- "has("browse")" returns |TRUE| (only in some GUI versions). +--- On systems where a directory browser is not supported a file +--- browser is used. In that case: select a file in the directory +--- to be used. +--- The input fields are: +--- {title} title for the requester +--- {initdir} directory to start browsing in +--- When the "Cancel" button is hit, something went wrong, or +--- browsing is not possible, an empty string is returned. +--- +--- @param title any +--- @param initdir any +--- @return any +function vim.fn.browsedir(title, initdir) end + +--- Add a buffer to the buffer list with name {name} (must be a +--- String). +--- If a buffer for file {name} already exists, return that buffer +--- number. Otherwise return the buffer number of the newly +--- created buffer. When {name} is an empty string then a new +--- buffer is always created. +--- The buffer will not have 'buflisted' set and not be loaded +--- yet. To add some text to the buffer use this: > +--- let bufnr = bufadd('someName') +--- call bufload(bufnr) +--- call setbufline(bufnr, 1, ['some', 'text']) +--- +--- let bufnr = 'somename'->bufadd() +--- +--- @param name string +--- @return integer +function vim.fn.bufadd(name) end + +--- The result is a Number, which is |TRUE| if a buffer called +--- {buf} exists. +--- If the {buf} argument is a number, buffer numbers are used. +--- Number zero is the alternate buffer for the current window. +--- +--- If the {buf} argument is a string it must match a buffer name +--- exactly. The name can be: +--- - Relative to the current directory. +--- - A full path. +--- - The name of a buffer with 'buftype' set to "nofile". +--- - A URL name. +--- Unlisted buffers will be found. +--- Note that help files are listed by their short name in the +--- output of |:buffers|, but bufexists() requires using their +--- long name to be able to find them. +--- bufexists() may report a buffer exists, but to use the name +--- with a |:buffer| command you may need to use |expand()|. Esp +--- for MS-Windows 8.3 names in the form "c:\DOCUME~1" +--- Use "bufexists(0)" to test for the existence of an alternate +--- file name. +--- +--- Can also be used as a |method|: > +--- let exists = 'somename'->bufexists() +--- +--- @param buf any +--- @return 0|1 +function vim.fn.bufexists(buf) end + +--- @deprecated +--- Obsolete name for |bufexists()|. +function vim.fn.buffer_exists(...) end + +--- @deprecated +--- Obsolete name for |bufname()|. +function vim.fn.buffer_name(...) end + +--- @deprecated +--- Obsolete name for |bufnr()|. +function vim.fn.buffer_number(...) end + +--- The result is a Number, which is |TRUE| if a buffer called +--- {buf} exists and is listed (has the 'buflisted' option set). +--- The {buf} argument is used like with |bufexists()|. +--- +--- Can also be used as a |method|: > +--- let listed = 'somename'->buflisted() +--- +--- @param buf any +--- @return 0|1 +function vim.fn.buflisted(buf) end + +--- Ensure the buffer {buf} is loaded. When the buffer name +--- refers to an existing file then the file is read. Otherwise +--- the buffer will be empty. If the buffer was already loaded +--- then there is no change. If the buffer is not related to a +--- file the no file is read (e.g., when 'buftype' is "nofile"). +--- If there is an existing swap file for the file of the buffer, +--- there will be no dialog, the buffer will be loaded anyway. +--- The {buf} argument is used like with |bufexists()|. +--- +--- Can also be used as a |method|: > +--- eval 'somename'->bufload() +--- +--- @param buf any +function vim.fn.bufload(buf) end + +--- The result is a Number, which is |TRUE| if a buffer called +--- {buf} exists and is loaded (shown in a window or hidden). +--- The {buf} argument is used like with |bufexists()|. +--- +--- Can also be used as a |method|: > +--- let loaded = 'somename'->bufloaded() +--- +--- @param buf any +--- @return 0|1 +function vim.fn.bufloaded(buf) end + +--- The result is the name of a buffer. Mostly as it is displayed +--- by the `:ls` command, but not using special names such as +--- "[No Name]". +--- If {buf} is omitted the current buffer is used. +--- If {buf} is a Number, that buffer number's name is given. +--- Number zero is the alternate buffer for the current window. +--- If {buf} is a String, it is used as a |file-pattern| to match +--- with the buffer names. This is always done like 'magic' is +--- set and 'cpoptions' is empty. When there is more than one +--- match an empty string is returned. +--- "" or "%" can be used for the current buffer, "#" for the +--- alternate buffer. +--- A full match is preferred, otherwise a match at the start, end +--- or middle of the buffer name is accepted. If you only want a +--- full match then put "^" at the start and "$" at the end of the +--- pattern. +--- Listed buffers are found first. If there is a single match +--- with a listed buffer, that one is returned. Next unlisted +--- buffers are searched for. +--- If the {buf} is a String, but you want to use it as a buffer +--- number, force it to be a Number by adding zero to it: > +--- :echo bufname("3" + 0) +--- +--- echo bufnr->bufname() +--- +--- +--- bufname("#") alternate buffer name +--- bufname(3) name of buffer 3 +--- bufname("%") name of current buffer +--- bufname("file2") name of buffer where "file2" matches. +--- < +--- *bufnr()* +--- @param buf? any +--- @return string +function vim.fn.bufname(buf) end + +--- The result is the number of a buffer, as it is displayed by +--- the `:ls` command. For the use of {buf}, see |bufname()| +--- above. +--- If the buffer doesn't exist, -1 is returned. Or, if the +--- {create} argument is present and TRUE, a new, unlisted, +--- buffer is created and its number is returned. +--- bufnr("$") is the last buffer: > +--- :let last_buffer = bufnr("$") +--- +--- echo bufref->bufnr() +--- +--- @param buf? any +--- @param create? any +--- @return integer +function vim.fn.bufnr(buf, create) end + +--- The result is a Number, which is the |window-ID| of the first +--- window associated with buffer {buf}. For the use of {buf}, +--- see |bufname()| above. If buffer {buf} doesn't exist or +--- there is no such window, -1 is returned. Example: > +--- +--- echo "A window containing buffer 1 is " .. (bufwinid(1)) +--- < +--- Only deals with the current tab page. See |win_findbuf()| for +--- finding more. +--- +--- Can also be used as a |method|: > +--- FindBuffer()->bufwinid() +--- +--- @param buf any +--- @return integer +function vim.fn.bufwinid(buf) end + +--- Like |bufwinid()| but return the window number instead of the +--- |window-ID|. +--- If buffer {buf} doesn't exist or there is no such window, -1 +--- is returned. Example: > +--- +--- echo "A window containing buffer 1 is " .. (bufwinnr(1)) +--- +--- +--- FindBuffer()->bufwinnr() +--- +--- @param buf any +--- @return integer +function vim.fn.bufwinnr(buf) end + +--- Return the line number that contains the character at byte +--- count {byte} in the current buffer. This includes the +--- end-of-line character, depending on the 'fileformat' option +--- for the current buffer. The first character has byte count +--- one. +--- Also see |line2byte()|, |go| and |:goto|. +--- +--- Returns -1 if the {byte} value is invalid. +--- +--- Can also be used as a |method|: > +--- GetOffset()->byte2line() +--- +--- @param byte any +--- @return integer +function vim.fn.byte2line(byte) end + +--- Return byte index of the {nr}th character in the String +--- {expr}. Use zero for the first character, it then returns +--- zero. +--- If there are no multibyte characters the returned value is +--- equal to {nr}. +--- Composing characters are not counted separately, their byte +--- length is added to the preceding base character. See +--- |byteidxcomp()| below for counting composing characters +--- separately. +--- When {utf16} is present and TRUE, {nr} is used as the UTF-16 +--- index in the String {expr} instead of as the character index. +--- The UTF-16 index is the index in the string when it is encoded +--- with 16-bit words. If the specified UTF-16 index is in the +--- middle of a character (e.g. in a 4-byte character), then the +--- byte index of the first byte in the character is returned. +--- Refer to |string-offset-encoding| for more information. +--- Example : > +--- echo matchstr(str, ".", byteidx(str, 3)) +--- +--- let s = strpart(str, byteidx(str, 3)) +--- echo strpart(s, 0, byteidx(s, 1)) +--- +--- echo byteidx('a😊😊', 2) returns 5 +--- echo byteidx('a😊😊', 2, 1) returns 1 +--- echo byteidx('a😊😊', 3, 1) returns 5 +--- < +--- Can also be used as a |method|: > +--- GetName()->byteidx(idx) +--- +--- @param expr any +--- @param nr integer +--- @param utf16? any +--- @return integer +function vim.fn.byteidx(expr, nr, utf16) end + +--- Like byteidx(), except that a composing character is counted +--- as a separate character. Example: > +--- let s = 'e' .. nr2char(0x301) +--- echo byteidx(s, 1) +--- echo byteidxcomp(s, 1) +--- echo byteidxcomp(s, 2) +--- +--- GetName()->byteidxcomp(idx) +--- +--- @param expr any +--- @param nr integer +--- @param utf16? any +--- @return integer +function vim.fn.byteidxcomp(expr, nr, utf16) end + +--- Call function {func} with the items in |List| {arglist} as +--- arguments. +--- {func} can either be a |Funcref| or the name of a function. +--- a:firstline and a:lastline are set to the cursor line. +--- Returns the return value of the called function. +--- {dict} is for functions with the "dict" attribute. It will be +--- used to set the local variable "self". |Dictionary-function| +--- +--- Can also be used as a |method|: > +--- GetFunc()->call([arg, arg], dict) +--- +--- @param func any +--- @param arglist any +--- @param dict? any +--- @return any +function vim.fn.call(func, arglist, dict) end + +--- Return the smallest integral value greater than or equal to +--- {expr} as a |Float| (round up). +--- {expr} must evaluate to a |Float| or a |Number|. +--- Examples: > +--- echo ceil(1.456) +--- < 2.0 > +--- echo ceil(-5.456) +--- < -5.0 > +--- echo ceil(4.0) +--- < 4.0 +--- +--- Returns 0.0 if {expr} is not a |Float| or a |Number|. +--- +--- Can also be used as a |method|: > +--- Compute()->ceil() +--- +--- @param expr any +--- @return any +function vim.fn.ceil(expr) end + +--- Close a channel or a specific stream associated with it. +--- For a job, {stream} can be one of "stdin", "stdout", +--- "stderr" or "rpc" (closes stdin/stdout for a job started +--- with `"rpc":v:true`) If {stream} is omitted, all streams +--- are closed. If the channel is a pty, this will then close the +--- pty master, sending SIGHUP to the job process. +--- For a socket, there is only one stream, and {stream} should be +--- omitted. +--- +--- @param id any +--- @param stream? any +--- @return any +function vim.fn.chanclose(id, stream) end + +--- Return the number of the most recent change. This is the same +--- number as what is displayed with |:undolist| and can be used +--- with the |:undo| command. +--- When a change was made it is the number of that change. After +--- redo it is the number of the redone change. After undo it is +--- one less than the number of the undone change. +--- Returns 0 if the undo list is empty. +--- +--- @return integer +function vim.fn.changenr() end + +--- Send data to channel {id}. For a job, it writes it to the +--- stdin of the process. For the stdio channel |channel-stdio|, +--- it writes to Nvim's stdout. Returns the number of bytes +--- written if the write succeeded, 0 otherwise. +--- See |channel-bytes| for more information. +--- +--- {data} may be a string, string convertible, |Blob|, or a list. +--- If {data} is a list, the items will be joined by newlines; any +--- newlines in an item will be sent as NUL. To send a final +--- newline, include a final empty string. Example: > +--- :call chansend(id, ["abc", "123\n456", ""]) +--- 123456". +--- +--- chansend() writes raw data, not RPC messages. If the channel +--- was created with `"rpc":v:true` then the channel expects RPC +--- messages, use |rpcnotify()| and |rpcrequest()| instead. +--- +--- +--- @param id any +--- @param data any +--- @return any +function vim.fn.chansend(id, data) end + +--- Return Number value of the first char in {string}. +--- Examples: > +--- char2nr(" ") returns 32 +--- char2nr("ABC") returns 65 +--- char2nr("á") returns 225 +--- char2nr("á"[0]) returns 195 +--- char2nr("\") returns 128 +--- +--- GetChar()->char2nr() +--- +--- @param string string +--- @param utf8? any +--- @return any +function vim.fn.char2nr(string, utf8) end + +--- Return the character class of the first character in {string}. +--- The character class is one of: +--- 0 blank +--- 1 punctuation +--- 2 word character +--- 3 emoji +--- other specific Unicode class +--- The class is used in patterns and word motions. +--- Returns 0 if {string} is not a |String|. +--- +--- +--- @param string string +--- @return any +function vim.fn.charclass(string) end + +--- Same as |col()| but returns the character index of the column +--- position given with {expr} instead of the byte position. +--- +--- Example: +--- With the cursor on '세' in line 5 with text "여보세요": > +--- charcol('.') returns 3 +--- col('.') returns 7 +--- +--- +--- GetPos()->col() +--- < +--- *charidx()* +--- @param expr any +--- @param winid? integer +--- @return any +function vim.fn.charcol(expr, winid) end + +--- Return the character index of the byte at {idx} in {string}. +--- The index of the first character is zero. +--- If there are no multibyte characters the returned value is +--- equal to {idx}. +--- +--- When {countcc} is omitted or |FALSE|, then composing characters +--- are not counted separately, their byte length is added to the +--- preceding base character. +--- When {countcc} is |TRUE|, then composing characters are +--- counted as separate characters. +--- +--- When {utf16} is present and TRUE, {idx} is used as the UTF-16 +--- index in the String {expr} instead of as the byte index. +--- +--- Returns -1 if the arguments are invalid or if there are less +--- than {idx} bytes. If there are exactly {idx} bytes the length +--- of the string in characters is returned. +--- +--- An error is given and -1 is returned if the first argument is +--- not a string, the second argument is not a number or when the +--- third argument is present and is not zero or one. +--- +--- See |byteidx()| and |byteidxcomp()| for getting the byte index +--- from the character index and |utf16idx()| for getting the +--- UTF-16 index from the character index. +--- Refer to |string-offset-encoding| for more information. +--- Examples: > +--- echo charidx('áb́ć', 3) returns 1 +--- echo charidx('áb́ć', 6, 1) returns 4 +--- echo charidx('áb́ć', 16) returns -1 +--- echo charidx('a😊😊', 4, 0, 1) returns 2 +--- < +--- Can also be used as a |method|: > +--- GetName()->charidx(idx) +--- +--- @param string string +--- @param idx integer +--- @param countcc? any +--- @param utf16? any +--- @return any +function vim.fn.charidx(string, idx, countcc, utf16) end + +--- Change the current working directory to {dir}. The scope of +--- the directory change depends on the directory of the current +--- window: +--- - If the current window has a window-local directory +--- (|:lcd|), then changes the window local directory. +--- - Otherwise, if the current tabpage has a local +--- directory (|:tcd|) then changes the tabpage local +--- directory. +--- - Otherwise, changes the global directory. +--- {dir} must be a String. +--- If successful, returns the previous working directory. Pass +--- this to another chdir() to restore the directory. +--- On failure, returns an empty string. +--- +--- Example: > +--- let save_dir = chdir(newdir) +--- if save_dir != "" +--- " ... do some work +--- call chdir(save_dir) +--- endif +--- +--- +--- GetDir()->chdir() +--- < +--- @param dir string +--- @return any +function vim.fn.chdir(dir) end + +--- Get the amount of indent for line {lnum} according the C +--- indenting rules, as with 'cindent'. +--- The indent is counted in spaces, the value of 'tabstop' is +--- relevant. {lnum} is used just like in |getline()|. +--- When {lnum} is invalid -1 is returned. +--- See |C-indenting|. +--- +--- Can also be used as a |method|: > +--- GetLnum()->cindent() +--- +--- @param lnum integer +--- @return any +function vim.fn.cindent(lnum) end + +--- Clears all matches previously defined for the current window +--- by |matchadd()| and the |:match| commands. +--- If {win} is specified, use the window with this number or +--- window ID instead of the current window. +--- +--- Can also be used as a |method|: > +--- GetWin()->clearmatches() +--- < +--- @param win? any +--- @return any +function vim.fn.clearmatches(win) end + +--- The result is a Number, which is the byte index of the column +--- position given with {expr}. The accepted positions are: +--- . the cursor position +--- $ the end of the cursor line (the result is the +--- number of bytes in the cursor line plus one) +--- 'x position of mark x (if the mark is not set, 0 is +--- returned) +--- v In Visual mode: the start of the Visual area (the +--- cursor is the end). When not in Visual mode +--- returns the cursor position. Differs from |'<| in +--- that it's updated right away. +--- Additionally {expr} can be [lnum, col]: a |List| with the line +--- and column number. Most useful when the column is "$", to get +--- the last column of a specific line. When "lnum" or "col" is +--- out of range then col() returns zero. +--- With the optional {winid} argument the values are obtained for +--- that window instead of the current window. +--- To get the line number use |line()|. To get both use +--- |getpos()|. +--- For the screen column position use |virtcol()|. For the +--- character position use |charcol()|. +--- Note that only marks in the current file can be used. +--- Examples: > +--- col(".") column of cursor +--- col("$") length of cursor line plus one +--- col("'t") column of mark t +--- col("'" .. markname) column of mark markname +--- mapping the cursor isn't +--- moved, this can be used to obtain the column in Insert mode: > +--- :imap echo col(".").."\n" +--- +--- +--- GetPos()->col() +--- < +--- +--- @param expr any +--- @param winid? integer +--- @return integer +function vim.fn.col(expr, winid) end + +--- Set the matches for Insert mode completion. +--- Can only be used in Insert mode. You need to use a mapping +--- with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O +--- or with an expression mapping. +--- {startcol} is the byte offset in the line where the completed +--- text start. The text up to the cursor is the original text +--- that will be replaced by the matches. Use col('.') for an +--- empty string. "col('.') - 1" will replace one character by a +--- match. +--- {matches} must be a |List|. Each |List| item is one match. +--- See |complete-items| for the kind of items that are possible. +--- "longest" in 'completeopt' is ignored. +--- Note that the after calling this function you need to avoid +--- inserting anything that would cause completion to stop. +--- The match can be selected with CTRL-N and CTRL-P as usual with +--- Insert mode completion. The popup menu will appear if +--- specified, see |ins-completion-menu|. +--- Example: > +--- inoremap =ListMonths() +--- +--- func ListMonths() +--- call complete(col('.'), ['January', 'February', 'March', +--- \ 'April', 'May', 'June', 'July', 'August', 'September', +--- \ 'October', 'November', 'December']) +--- return '' +--- endfunc +--- +--- GetMatches()->complete(col('.')) +--- +--- @param startcol any +--- @param matches any +function vim.fn.complete(startcol, matches) end + +--- Add {expr} to the list of matches. Only to be used by the +--- function specified with the 'completefunc' option. +--- Returns 0 for failure (empty string or out of memory), +--- 1 when the match was added, 2 when the match was already in +--- the list. +--- See |complete-functions| for an explanation of {expr}. It is +--- the same as one item in the list that 'omnifunc' would return. +--- +--- Can also be used as a |method|: > +--- GetMoreMatches()->complete_add() +--- +--- @param expr any +--- @return 0|1|2 +function vim.fn.complete_add(expr) end + +--- Check for a key typed while looking for completion matches. +--- This is to be used when looking for matches takes some time. +--- Returns |TRUE| when searching for matches is to be aborted, +--- zero otherwise. +--- Only to be used by the function specified with the +--- 'completefunc' option. +--- +--- +--- @return 0|1 +function vim.fn.complete_check() end + +--- Returns a |Dictionary| with information about Insert mode +--- completion. See |ins-completion|. +--- The items are: +--- mode Current completion mode name string. +--- See |complete_info_mode| for the values. +--- pum_visible |TRUE| if popup menu is visible. +--- See |pumvisible()|. +--- items List of completion matches. Each item is a +--- dictionary containing the entries "word", +--- "abbr", "menu", "kind", "info" and "user_data". +--- See |complete-items|. +--- selected Selected item index. First index is zero. +--- Index is -1 if no item is selected (showing +--- typed text only, or the last completion after +--- no item is selected when using the or +--- keys) +--- inserted Inserted string. [NOT IMPLEMENTED YET] +--- +--- *complete_info_mode* +--- mode values are: +--- "" Not in completion mode +--- "keyword" Keyword completion |i_CTRL-X_CTRL-N| +--- "ctrl_x" Just pressed CTRL-X |i_CTRL-X| +--- "scroll" Scrolling with |i_CTRL-X_CTRL-E| or +--- |i_CTRL-X_CTRL-Y| +--- "whole_line" Whole lines |i_CTRL-X_CTRL-L| +--- "files" File names |i_CTRL-X_CTRL-F| +--- "tags" Tags |i_CTRL-X_CTRL-]| +--- "path_defines" Definition completion |i_CTRL-X_CTRL-D| +--- "path_patterns" Include completion |i_CTRL-X_CTRL-I| +--- "dictionary" Dictionary |i_CTRL-X_CTRL-K| +--- "thesaurus" Thesaurus |i_CTRL-X_CTRL-T| +--- "cmdline" Vim Command line |i_CTRL-X_CTRL-V| +--- "function" User defined completion |i_CTRL-X_CTRL-U| +--- "omni" Omni completion |i_CTRL-X_CTRL-O| +--- "spell" Spelling suggestions |i_CTRL-X_s| +--- "eval" |complete()| completion +--- "unknown" Other internal modes +--- +--- If the optional {what} list argument is supplied, then only +--- the items listed in {what} are returned. Unsupported items in +--- {what} are silently ignored. +--- +--- To get the position and size of the popup menu, see +--- |pum_getpos()|. It's also available in |v:event| during the +--- |CompleteChanged| event. +--- +--- Returns an empty |Dictionary| on error. +--- +--- Examples: > +--- " Get all items +--- call complete_info() +--- " Get only 'mode' +--- call complete_info(['mode']) +--- " Get only 'mode' and 'pum_visible' +--- call complete_info(['mode', 'pum_visible']) +--- +--- +--- GetItems()->complete_info() +--- < +--- *confirm()* +--- @param what? any +--- @return table +function vim.fn.complete_info(what) end + +--- confirm() offers the user a dialog, from which a choice can be +--- made. It returns the number of the choice. For the first +--- choice this is 1. +--- +--- {msg} is displayed in a dialog with {choices} as the +--- alternatives. When {choices} is missing or empty, "&OK" is +--- used (and translated). +--- {msg} is a String, use '\n' to include a newline. Only on +--- some systems the string is wrapped when it doesn't fit. +--- +--- {choices} is a String, with the individual choices separated +--- by '\n', e.g. > +--- confirm("Save changes?", "&Yes\n&No\n&Cancel") +--- +--- confirm("file has been modified", "&Save\nSave &All") +--- , CTRL-C, +--- or another valid interrupt key, confirm() returns 0. +--- +--- An example: > +--- let choice = confirm("What do you want?", +--- \ "&Apples\n&Oranges\n&Bananas", 2) +--- if choice == 0 +--- echo "make up your mind!" +--- elseif choice == 3 +--- echo "tasteful" +--- else +--- echo "I prefer bananas myself." +--- endif +--- +--- BuildMessage()->confirm("&Yes\n&No") +--- < +--- *copy()* +--- @param msg any +--- @param choices? any +--- @param default? any +--- @param type? any +--- @return any +function vim.fn.confirm(msg, choices, default, type) end + +--- different from using {expr} directly. +--- When {expr} is a |List| a shallow copy is created. This means +--- that the original |List| can be changed without changing the +--- copy, and vice versa. But the items are identical, thus +--- changing an item changes the contents of both |Lists|. +--- A |Dictionary| is copied in a similar way as a |List|. +--- Also see |deepcopy()|. +--- Can also be used as a |method|: > +--- mylist->copy() +--- +--- @param expr any +--- @return any +function vim.fn.copy(expr) end + +--- Return the cosine of {expr}, measured in radians, as a |Float|. +--- {expr} must evaluate to a |Float| or a |Number|. +--- Returns 0.0 if {expr} is not a |Float| or a |Number|. +--- Examples: > +--- :echo cos(100) +--- < 0.862319 > +--- :echo cos(-4.01) +--- < -0.646043 +--- +--- Can also be used as a |method|: > +--- Compute()->cos() +--- +--- @param expr any +--- @return any +function vim.fn.cos(expr) end + +--- Return the hyperbolic cosine of {expr} as a |Float| in the range +--- [1, inf]. +--- {expr} must evaluate to a |Float| or a |Number|. +--- Returns 0.0 if {expr} is not a |Float| or a |Number|. +--- Examples: > +--- :echo cosh(0.5) +--- < 1.127626 > +--- :echo cosh(-0.5) +--- < -1.127626 +--- +--- Can also be used as a |method|: > +--- Compute()->cosh() +--- +--- @param expr any +--- @return any +function vim.fn.cosh(expr) end + +--- Return the number of times an item with value {expr} appears +--- in |String|, |List| or |Dictionary| {comp}. +--- +--- If {start} is given then start with the item with this index. +--- {start} can only be used with a |List|. +--- +--- When {ic} is given and it's |TRUE| then case is ignored. +--- +--- When {comp} is a string then the number of not overlapping +--- occurrences of {expr} is returned. Zero is returned when +--- {expr} is an empty string. +--- +--- Can also be used as a |method|: > +--- mylist->count(val) +--- < +--- @param comp any +--- @param expr any +--- @param ic? any +--- @param start? any +--- @return any +function vim.fn.count(comp, expr, ic, start) end + +--- Returns a |Dictionary| representing the |context| at {index} +--- from the top of the |context-stack| (see |context-dict|). +--- If {index} is not given, it is assumed to be 0 (i.e.: top). +--- +--- @param index? any +--- @return any +function vim.fn.ctxget(index) end + +--- Pops and restores the |context| at the top of the +--- |context-stack|. +--- +--- @return any +function vim.fn.ctxpop() end + +--- Pushes the current editor state (|context|) on the +--- |context-stack|. +--- If {types} is given and is a |List| of |String|s, it specifies +--- which |context-types| to include in the pushed context. +--- Otherwise, all context types are included. +--- +--- @param types? any +--- @return any +function vim.fn.ctxpush(types) end + +--- Sets the |context| at {index} from the top of the +--- |context-stack| to that represented by {context}. +--- {context} is a Dictionary with context data (|context-dict|). +--- If {index} is not given, it is assumed to be 0 (i.e.: top). +--- +--- @param context any +--- @param index? any +--- @return any +function vim.fn.ctxset(context, index) end + +--- Returns the size of the |context-stack|. +--- +--- @return any +function vim.fn.ctxsize() end + +--- Positions the cursor at the column (byte count) {col} in the +--- line {lnum}. The first column is one. +--- +--- When there is one argument {list} this is used as a |List| +--- with two, three or four item: +--- [{lnum}, {col}] +--- [{lnum}, {col}, {off}] +--- [{lnum}, {col}, {off}, {curswant}] +--- This is like the return value of |getpos()| or |getcurpos()|, +--- but without the first item. +--- +--- To position the cursor using {col} as the character count, use +--- |setcursorcharpos()|. +--- +--- Does not change the jumplist. +--- {lnum} is used like with |getline()|, except that if {lnum} is +--- zero, the cursor will stay in the current line. +--- If {lnum} is greater than the number of lines in the buffer, +--- the cursor will be positioned at the last line in the buffer. +--- If {col} is greater than the number of bytes in the line, +--- the cursor will be positioned at the last character in the +--- line. +--- If {col} is zero, the cursor will stay in the current column. +--- If {curswant} is given it is used to set the preferred column +--- for vertical movement. Otherwise {col} is used. +--- +--- When 'virtualedit' is used {off} specifies the offset in +--- screen columns from the start of the character. E.g., a +--- position within a or after the last character. +--- Returns 0 when the position could be set, -1 otherwise. +--- +--- Can also be used as a |method|: > +--- GetCursorPos()->cursor() +--- +--- @param list any +--- @return any +function vim.fn.cursor(list) end + +--- Specifically used to interrupt a program being debugged. It +--- will cause process {pid} to get a SIGTRAP. Behavior for other +--- processes is undefined. See |terminal-debug|. +--- (Sends a SIGINT to a process {pid} other than MS-Windows) +--- +--- Returns |TRUE| if successfully interrupted the program. +--- Otherwise returns |FALSE|. +--- +--- Can also be used as a |method|: > +--- GetPid()->debugbreak() +--- +--- @param pid any +--- @return any +function vim.fn.debugbreak(pid) end + +--- Make a copy of {expr}. For Numbers and Strings this isn't +--- different from using {expr} directly. +--- When {expr} is a |List| a full copy is created. This means +--- that the original |List| can be changed without changing the +--- copy, and vice versa. When an item is a |List|, a copy for it +--- is made, recursively. Thus changing an item in the copy does +--- not change the contents of the original |List|. +--- +--- When {noref} is omitted or zero a contained |List| or +--- |Dictionary| is only copied once. All references point to +--- this single copy. With {noref} set to 1 every occurrence of a +--- |List| or |Dictionary| results in a new copy. This also means +--- that a cyclic reference causes deepcopy() to fail. +--- *E724* +--- Nesting is possible up to 100 levels. When there is an item +--- that refers back to a higher level making a deep copy with +--- {noref} set to 1 will fail. +--- Also see |copy()|. +--- +--- Can also be used as a |method|: > +--- GetObject()->deepcopy() +--- +--- @param expr any +--- @param noref? any +--- @return any +function vim.fn.deepcopy(expr, noref) end + +--- Without {flags} or with {flags} empty: Deletes the file by the +--- name {fname}. +--- +--- This also works when {fname} is a symbolic link. The symbolic +--- link itself is deleted, not what it points to. +--- +--- When {flags} is "d": Deletes the directory by the name +--- {fname}. This fails when directory {fname} is not empty. +--- +--- When {flags} is "rf": Deletes the directory by the name +--- {fname} and everything in it, recursively. BE CAREFUL! +--- Note: on MS-Windows it is not possible to delete a directory +--- that is being used. +--- +--- The result is a Number, which is 0/false if the delete +--- operation was successful and -1/true when the deletion failed +--- or partly failed. +--- +--- Can also be used as a |method|: > +--- GetName()->delete() +--- +--- @param fname integer +--- @param flags? string +--- @return integer +function vim.fn.delete(fname, flags) end + +--- Delete lines {first} to {last} (inclusive) from buffer {buf}. +--- If {last} is omitted then delete line {first} only. +--- On success 0 is returned, on failure 1 is returned. +--- +--- This function works only for loaded buffers. First call +--- |bufload()| if needed. +--- +--- For the use of {buf}, see |bufname()| above. +--- +--- {first} and {last} are used like with |getline()|. Note that +--- when using |line()| this refers to the current buffer. Use "$" +--- to refer to the last line in buffer {buf}. +--- +--- Can also be used as a |method|: > +--- GetBuffer()->deletebufline(1) +--- < +--- @param buf any +--- @param first any +--- @param last? any +--- @return any +function vim.fn.deletebufline(buf, first, last) end + +--- Adds a watcher to a dictionary. A dictionary watcher is +--- identified by three components: +--- +--- - A dictionary({dict}); +--- - A key pattern({pattern}). +--- - A function({callback}). +--- +--- After this is called, every change on {dict} and on keys +--- matching {pattern} will result in {callback} being invoked. +--- +--- For example, to watch all global variables: > +--- silent! call dictwatcherdel(g:, '*', 'OnDictChanged') +--- function! OnDictChanged(d,k,z) +--- echomsg string(a:k) string(a:z) +--- endfunction +--- call dictwatcheradd(g:, '*', 'OnDictChanged') +--- < +--- For now {pattern} only accepts very simple patterns that can +--- contain a "*" at the end of the string, in which case it will +--- match every key that begins with the substring before the "*". +--- That means if "*" is not the last character of {pattern}, only +--- keys that are exactly equal as {pattern} will be matched. +--- +--- The {callback} receives three arguments: +--- +--- - The dictionary being watched. +--- - The key which changed. +--- - A dictionary containing the new and old values for the key. +--- +--- The type of change can be determined by examining the keys +--- present on the third argument: +--- +--- - If contains both `old` and `new`, the key was updated. +--- - If it contains only `new`, the key was added. +--- - If it contains only `old`, the key was deleted. +--- +--- This function can be used by plugins to implement options with +--- validation and parsing logic. +--- +--- @param dict any +--- @param pattern any +--- @param callback any +--- @return any +function vim.fn.dictwatcheradd(dict, pattern, callback) end + +--- Removes a watcher added with |dictwatcheradd()|. All three +--- arguments must match the ones passed to |dictwatcheradd()| in +--- order for the watcher to be successfully deleted. +--- +--- *did_filetype()* +--- @param dict any +--- @param pattern any +--- @param callback any +--- @return any +function vim.fn.dictwatcherdel(dict, pattern, callback) end + +--- FileType event has been triggered at least once. Can be used +--- to avoid triggering the FileType event again in the scripts +--- that detect the file type. |FileType| +--- Returns |FALSE| when `:setf FALLBACK` was used. +--- When editing another file, the counter is reset, thus this +--- really checks if the FileType event has been triggered for the +--- current buffer. This allows an autocommand that starts +--- editing another buffer to set 'filetype' and load a syntax +--- file. +--- +--- @return any +function vim.fn.did_filetype() end + +--- Returns the number of filler lines above line {lnum}. +--- These are the lines that were inserted at this point in +--- another diff'ed window. These filler lines are shown in the +--- display but don't exist in the buffer. +--- {lnum} is used like with |getline()|. Thus "." is the current +--- line, "'m" mark m, etc. +--- Returns 0 if the current window is not in diff mode. +--- +--- Can also be used as a |method|: > +--- GetLnum()->diff_filler() +--- +--- @param lnum integer +--- @return any +function vim.fn.diff_filler(lnum) end + +--- Returns the highlight ID for diff mode at line {lnum} column +--- {col} (byte index). When the current line does not have a +--- diff change zero is returned. +--- {lnum} is used like with |getline()|. Thus "." is the current +--- line, "'m" mark m, etc. +--- {col} is 1 for the leftmost column, {lnum} is 1 for the first +--- line. +--- The highlight ID can be used with |synIDattr()| to obtain +--- syntax information about the highlighting. +--- +--- Can also be used as a |method|: > +--- GetLnum()->diff_hlID(col) +--- < +--- +--- @param lnum integer +--- @param col integer +--- @return any +function vim.fn.diff_hlID(lnum, col) end + +--- Return the digraph of {chars}. This should be a string with +--- exactly two characters. If {chars} are not just two +--- characters, or the digraph of {chars} does not exist, an error +--- is given and an empty string is returned. +--- +--- Also see |digraph_getlist()|. +--- +--- Examples: > +--- " Get a built-in digraph +--- :echo digraph_get('00') " Returns '∞' +--- +--- " Get a user-defined digraph +--- :call digraph_set('aa', 'あ') +--- :echo digraph_get('aa') " Returns 'あ' +--- < +--- Can also be used as a |method|: > +--- GetChars()->digraph_get() +--- < +--- +--- @param chars any +--- @return any +function vim.fn.digraph_get(chars) end + +--- Return a list of digraphs. If the {listall} argument is given +--- and it is TRUE, return all digraphs, including the default +--- digraphs. Otherwise, return only user-defined digraphs. +--- +--- Also see |digraph_get()|. +--- +--- Examples: > +--- " Get user-defined digraphs +--- :echo digraph_getlist() +--- +--- " Get all the digraphs, including default digraphs +--- :echo digraph_getlist(1) +--- < +--- Can also be used as a |method|: > +--- GetNumber()->digraph_getlist() +--- < +--- +--- @param listall? any +--- @return any +function vim.fn.digraph_getlist(listall) end + +--- Add digraph {chars} to the list. {chars} must be a string +--- with two characters. {digraph} is a string with one UTF-8 +--- encoded character. *E1215* +--- Be careful, composing characters are NOT ignored. This +--- function is similar to |:digraphs| command, but useful to add +--- digraphs start with a white space. +--- +--- The function result is v:true if |digraph| is registered. If +--- this fails an error message is given and v:false is returned. +--- +--- If you want to define multiple digraphs at once, you can use +--- |digraph_setlist()|. +--- +--- Example: > +--- call digraph_set(' ', 'あ') +--- < +--- Can be used as a |method|: > +--- GetString()->digraph_set('あ') +--- < +--- +--- @param chars any +--- @param digraph any +--- @return any +function vim.fn.digraph_set(chars, digraph) end + +--- Similar to |digraph_set()| but this function can add multiple +--- digraphs at once. {digraphlist} is a list composed of lists, +--- where each list contains two strings with {chars} and +--- {digraph} as in |digraph_set()|. *E1216* +--- Example: > +--- call digraph_setlist([['aa', 'あ'], ['ii', 'い']]) +--- < +--- It is similar to the following: > +--- for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']] +--- call digraph_set(chars, digraph) +--- endfor +--- +--- GetList()->digraph_setlist() +--- < +--- +--- @param digraphlist any +--- @return any +function vim.fn.digraph_setlist(digraphlist) end + +--- Return the Number 1 if {expr} is empty, zero otherwise. +--- - A |List| or |Dictionary| is empty when it does not have any +--- items. +--- - A |String| is empty when its length is zero. +--- - A |Number| and |Float| are empty when their value is zero. +--- - |v:false| and |v:null| are empty, |v:true| is not. +--- - A |Blob| is empty when its length is zero. +--- +--- Can also be used as a |method|: > +--- mylist->empty() +--- +--- @param expr any +--- @return any +function vim.fn.empty(expr) end + +--- Return all of environment variables as dictionary. You can +--- check if an environment variable exists like this: > +--- :echo has_key(environ(), 'HOME') +--- +--- :echo index(keys(environ()), 'HOME', 0, 1) != -1 +--- +--- @return any +function vim.fn.environ() end + +--- Escape the characters in {chars} that occur in {string} with a +--- backslash. Example: > +--- :echo escape('c:\program files\vim', ' \') +--- +--- c:\\program\ files\\vim +--- +--- GetText()->escape(' \') +--- < +--- *eval()* +--- @param string string +--- @param chars any +--- @return any +function vim.fn.escape(string, chars) end + +--- turn the result of |string()| back into the original value. +--- This works for Numbers, Floats, Strings, Blobs and composites +--- of them. Also works for |Funcref|s that refer to existing +--- functions. +--- +--- Can also be used as a |method|: > +--- argv->join()->eval() +--- +--- @param string string +--- @return any +function vim.fn.eval(string) end + +--- Returns 1 when inside an event handler. That is that Vim got +--- interrupted while waiting for the user to type a character, +--- e.g., when dropping a file on Vim. This means interactive +--- commands cannot be used. Otherwise zero is returned. +--- +--- @return any +function vim.fn.eventhandler() end + +--- This function checks if an executable with the name {expr} +--- exists. {expr} must be the name of the program without any +--- arguments. +--- executable() uses the value of $PATH and/or the normal +--- searchpath for programs. *PATHEXT* +--- On MS-Windows the ".exe", ".bat", etc. can optionally be +--- included. Then the extensions in $PATHEXT are tried. Thus if +--- "foo.exe" does not exist, "foo.exe.bat" can be found. If +--- $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot +--- by itself can be used in $PATHEXT to try using the name +--- without an extension. When 'shell' looks like a Unix shell, +--- then the name is also tried without adding an extension. +--- On MS-Windows it only checks if the file exists and is not a +--- directory, not if it's really executable. +--- On Windows an executable in the same directory as Vim is +--- always found (it is added to $PATH at |startup|). +--- 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. +--- +--- Can also be used as a |method|: > +--- GetCommand()->executable() +--- +--- @param expr any +--- @return any +function vim.fn.executable(expr) end + +--- Execute {command} and capture its output. +--- If {command} is a |String|, returns {command} output. +--- If {command} is a |List|, returns concatenated outputs. +--- Line continuations in {command} are not recognized. +--- Examples: > +--- echo execute('echon "foo"') +--- < foo > +--- echo execute(['echon "foo"', 'echon "bar"']) +--- < foobar +--- +--- The optional {silent} argument can have these values: +--- "" no `:silent` used +--- "silent" `:silent` used +--- "silent!" `:silent!` used +--- The default is "silent". Note that with "silent!", unlike +--- `:redir`, error messages are dropped. +--- +--- To get a list of lines use `split()` on the result: > +--- execute('args')->split("\n") +--- +--- +--- GetCommand()->execute() +--- +--- @param command any +--- @param silent? boolean +--- @return any +function vim.fn.execute(command, silent) end + +--- Returns the full path of {expr} if it is an executable and +--- given as a (partial or full) path or is found in $PATH. +--- Returns empty string otherwise. +--- If {expr} starts with "./" the |current-directory| is used. +--- +--- Can also be used as a |method|: > +--- GetCommand()->exepath() +--- < +--- *exists()* +--- @param expr any +--- @return any +function vim.fn.exepath(expr) end + +--- defined, zero otherwise. +--- +--- For checking for a supported feature use |has()|. +--- For checking if a file exists use |filereadable()|. +--- +--- The {expr} argument is a string, which contains one of these: +--- varname internal variable (see +--- dict.key |internal-variables|). Also works +--- list[i] for |curly-braces-names|, |Dictionary| +--- entries, |List| items, etc. +--- Beware that evaluating an index may +--- cause an error message for an invalid +--- expression. E.g.: > +--- :let l = [1, 2, 3] +--- :echo exists("l[5]") +--- < 0 > +--- :echo exists("l[xx]") +--- < E121: Undefined variable: xx +--- 0 +--- &option-name Vim option (only checks if it exists, +--- not if it really works) +--- +option-name Vim option that works. +--- $ENVNAME environment variable (could also be +--- done by comparing with an empty +--- string) +--- `*funcname` built-in function (see |functions|) +--- or user defined function (see +--- |user-function|). Also works for a +--- variable that is a Funcref. +--- :cmdname Ex command: built-in command, user +--- command or command modifier |:command|. +--- Returns: +--- 1 for match with start of a command +--- 2 full match with a command +--- 3 matches several user commands +--- To check for a supported command +--- always check the return value to be 2. +--- :2match The |:2match| command. +--- :3match The |:3match| command (but you +--- probably should not use it, it is +--- reserved for internal usage) +--- #event autocommand defined for this event +--- #event#pattern autocommand defined for this event and +--- pattern (the pattern is taken +--- literally and compared to the +--- autocommand patterns character by +--- character) +--- #group autocommand group exists +--- #group#event autocommand defined for this group and +--- event. +--- #group#event#pattern +--- autocommand defined for this group, +--- event and pattern. +--- ##event autocommand for this event is +--- supported. +--- +--- Examples: > +--- exists("&mouse") +--- exists("$HOSTNAME") +--- exists("*strftime") +--- exists("*s:MyFunc") +--- exists("*MyFunc") +--- exists("bufcount") +--- exists(":Make") +--- exists("#CursorHold") +--- exists("#BufReadPre#*.gz") +--- exists("#filetypeindent") +--- exists("#filetypeindent#FileType") +--- exists("#filetypeindent#FileType#*") +--- exists("##ColorScheme") +--- +--- exists(":make") +--- +--- exists(":make install") +--- +--- +--- exists(bufcount) +--- +--- Varname()->exists() +--- +--- @param expr any +--- @return 0|1 +function vim.fn.exists(expr) end + +--- Return the exponential of {expr} as a |Float| in the range +--- [0, inf]. +--- {expr} must evaluate to a |Float| or a |Number|. +--- Returns 0.0 if {expr} is not a |Float| or a |Number|. +--- Examples: > +--- :echo exp(2) +--- < 7.389056 > +--- :echo exp(-1) +--- < 0.367879 +--- +--- Can also be used as a |method|: > +--- Compute()->exp() +--- +--- @param expr any +--- @return any +function vim.fn.exp(expr) end + +--- Expand wildcards and the following special keywords in +--- {string}. 'wildignorecase' applies. +--- +--- If {list} is given and it is |TRUE|, a List will be returned. +--- Otherwise the result is a String and when there are several +--- matches, they are separated by characters. +--- +--- If the expansion fails, the result is an empty string. A name +--- for a non-existing file is not included, unless {string} does +--- not start with '%', '#' or '<', see below. +--- +--- When {string} starts with '%', '#' or '<', the expansion is +--- done like for the |cmdline-special| variables with their +--- associated modifiers. Here is a short overview: +--- +--- % current file name +--- # alternate file name +--- #n alternate file name n +--- file name under the cursor +--- autocmd file name +--- autocmd buffer number (as a String!) +--- autocmd matched name +--- C expression under the cursor +--- sourced script file or function name +--- sourced script line number or function +--- line number +--- script file line number, also when in +--- a function +--- "123_" where "123" is the +--- current script ID || +---