From 3c1d70f20b5d5bad3bec121e589187d15f325a9b Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Mon, 25 Jul 2022 12:23:04 +0200 Subject: feat(treesitter): allow customizing language symbol name --- runtime/lua/vim/treesitter/language.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index dfb6f5be84..d14b825603 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -6,10 +6,11 @@ local M = {} --- --- Parsers are searched in the `parser` runtime directory. --- ----@param lang The language the parser should parse ----@param path Optional path the parser is located at ----@param silent Don't throw an error if language not found -function M.require_language(lang, path, silent) +---@param lang string The language the parser should parse +---@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 +function M.require_language(lang, path, silent, symbol_name) if vim._ts_has_language(lang) then return true end @@ -21,7 +22,6 @@ function M.require_language(lang, path, silent) return false end - -- TODO(bfredl): help tag? error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") end path = paths[1] @@ -29,10 +29,10 @@ function M.require_language(lang, path, silent) if silent then return pcall(function() - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end) else - vim._ts_add_language(path, lang) + vim._ts_add_language(path, lang, symbol_name) end return true -- cgit From e892b7b3830f44bc8ab62e993bf07f7bf03d0029 Mon Sep 17 00:00:00 2001 From: Simon Wachter Date: Tue, 23 Aug 2022 13:02:55 +0200 Subject: fix(inspect): escape identifiers that are lua keywords (#19898) A lua keyword is not a valid table identifier --- runtime/lua/vim/inspect.lua | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/inspect.lua b/runtime/lua/vim/inspect.lua index 0a53fb203b..c232f69590 100644 --- a/runtime/lua/vim/inspect.lua +++ b/runtime/lua/vim/inspect.lua @@ -89,8 +89,38 @@ local function escape(str) ) end +-- List of lua keywords +local luaKeywords = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + local function isIdentifier(str) - return type(str) == 'string' and not not str:match('^[_%a][_%a%d]*$') + return type(str) == 'string' + -- identifier must start with a letter and underscore, and be followed by letters, numbers, and underscores + and not not str:match('^[_%a][_%a%d]*$') + -- lua keywords are not valid identifiers + and not luaKeywords[str] end local flr = math.floor -- cgit From 3aba4ba37859e4407eff2bb3f4d99c44b108ed79 Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Fri, 22 Apr 2022 21:50:52 +0200 Subject: feat(treesitter): upstream is_parent() Util from the nvim-treesitter project. Renamed is_parent to is_ancestor for clarity. --- runtime/lua/vim/treesitter.lua | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 70f2c425ed..37ab59b259 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -118,4 +118,27 @@ function M.get_string_parser(str, lang, opts) return LanguageTree.new(str, lang, opts) end +--- Determines whether a node is the ancestor of another +--- +---@param dest table the possible ancestor +---@param source table the possible descendant node +--- +---@returns (boolean) True if dest is an ancestor of source +function M.is_ancestor(dest, source) + if not (dest and source) then + return false + end + + local current = source + while current ~= nil do + if current == dest then + return true + end + + current = current:parent() + end + + return false +end + return M -- cgit From 733b2e12b86a34c00aa07e0491762f88582792a5 Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Tue, 26 Apr 2022 22:42:48 +0200 Subject: feat(treesitter): add opts.concat to query.get_text_node As part of the upstream of utility functions from nvim-treesitter, this option when set to false allows to return a table (downstream behavior). Effectively making the switch from the downstream to the upstream function much easier. --- runtime/lua/vim/treesitter/query.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 103e85abfd..697e2e7691 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -181,9 +181,14 @@ end --- Gets the text corresponding to a given node --- ----@param node the node ----@param source The buffer or string from which the node is extracted -function M.get_node_text(node, source) +---@param node table The node +---@param source table The buffer or string from which the node is extracted +---@param opts table Optional parameters. +--- - concat: (boolean default true) Concatenate result in a string +function M.get_node_text(node, source, opts) + opts = opts or {} + local concat = vim.F.if_nil(opts.concat, true) + local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() @@ -210,7 +215,7 @@ function M.get_node_text(node, source) end end - return table.concat(lines, '\n') + return concat and table.concat(lines, '\n') or lines elseif type(source) == 'string' then return source:sub(start_byte + 1, end_byte) end -- cgit From 6b2d42eb0352d01923e4bf2e3ce0824c662b7be4 Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Sat, 30 Apr 2022 10:43:26 +0200 Subject: feat(treesitter): add ability to retreive a tree/node given a range --- runtime/lua/vim/treesitter/languagetree.lua | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 4d3b0631a2..f87a66ddab 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -549,6 +549,44 @@ function LanguageTree:contains(range) return false end +--- Gets the tree that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +function LanguageTree:tree_for_range(range, opts) + opts = opts or {} + local ignore = vim.F.if_nil(opts.ignore_injections, true) + + 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 + end + end + end + + for _, tree in pairs(self._trees) do + if tree_contains(tree, range) then + return tree + end + end + + return nil +end + +--- Gets the smallest named node that contains {range} +--- +---@param range table A text range +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +function LanguageTree:named_node_for_range(range, opts) + local tree = self:tree_for_range(range, opts) + return tree:root():named_descendant_for_range(unpack(range)) +end + --- Gets the appropriate language that contains {range} --- ---@param range A text range, see |LanguageTree:contains| -- cgit From 133ff6e11ea862c7425d9c6a2827b20c97cf297f Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Thu, 2 Jun 2022 18:13:05 +0200 Subject: feat(treesitter): upstream node_contains() Util from the nvim-treesitter project. --- runtime/lua/vim/treesitter.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 37ab59b259..0936d62296 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -141,4 +141,17 @@ function M.is_ancestor(dest, source) return false end +---Determines if a node contains a range +---@param node table The node +---@param range table The range +--- +---@returns (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 +end + return M -- cgit From 244a115e494bce8e8205c04a6e5f3ab74ec4ed65 Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Sun, 24 Jul 2022 20:49:33 +0200 Subject: feat(treesitter): clarify similar 'get_node_range' functions The private 'get_node_range' function from the languagetree module has been renamed and remains private as it serve a purpose that is only relevant inside the languagetree module. The 'get_node_range' upstreamed from nvim-treesitter in the treesitter module has been made public as it is in itself a utlity function. --- runtime/lua/vim/treesitter.lua | 13 +++++++++++++ runtime/lua/vim/treesitter/languagetree.lua | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 0936d62296..82d41070ee 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -141,6 +141,19 @@ function M.is_ancestor(dest, source) return false end +--- Get the node's range or unpack a range table +--- +---@param node_or_range table +--- +---@returns start_row, start_col, end_row, end_col +function M.get_node_range(node_or_range) + if type(node_or_range) == 'table' then + return unpack(node_or_range) + else + return node_or_range:range() + end +end + ---Determines if a node contains a range ---@param node table The node ---@param range table The range diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index f87a66ddab..70317a9f94 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -299,7 +299,7 @@ function LanguageTree:included_regions() end ---@private -local function get_node_range(node, id, metadata) +local function get_range_from_metadata(node, id, metadata) if metadata[id] and metadata[id].range then return metadata[id].range end @@ -362,7 +362,7 @@ function LanguageTree:_get_injections() elseif name == 'combined' then combined = true elseif name == 'content' and #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + 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 @@ -371,7 +371,7 @@ function LanguageTree:_get_injections() end if #ranges == 0 then - table.insert(ranges, get_node_range(node, id, metadata)) + table.insert(ranges, get_range_from_metadata(node, id, metadata)) end end end -- cgit From 030b422d1e9517ed1b1c70fd8002b74881c80650 Mon Sep 17 00:00:00 2001 From: bfredl Date: Wed, 24 Aug 2022 23:48:52 +0200 Subject: feat(treesitter)!: use @foo.bar style highlight groups This removes the support for defining links via vim.treesitter.highlighter.hl_map (never documented, but plugins did anyway), or the uppercase-only `@FooGroup.Bar` to `FooGroup` rule. The fallback is now strictly `@foo.bar.lang` to `@foo.bar` to `@foo`, and casing is irrelevant (as it already was outside of treesitter) For compatibility, define default links to builting syntax groups as defined by pre-existing color schemes --- runtime/lua/vim/treesitter/highlighter.lua | 109 ++--------------------------- 1 file changed, 4 insertions(+), 105 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index e27a5fa9c3..1f242b0fdd 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -12,105 +12,18 @@ TSHighlighterQuery.__index = TSHighlighterQuery local ns = a.nvim_create_namespace('treesitter/highlighter') -local _default_highlights = {} -local _link_default_highlight_once = function(from, to) - if not _default_highlights[from] then - _default_highlights[from] = true - a.nvim_set_hl(0, from, { link = to, default = true }) - end - - return from -end - --- If @definition.special does not exist use @definition instead -local subcapture_fallback = { - __index = function(self, capture) - local rtn - local shortened = capture - while not rtn and shortened do - shortened = shortened:match('(.*)%.') - rtn = shortened and rawget(self, shortened) - end - rawset(self, capture, rtn or '__notfound') - return rtn - end, -} - -TSHighlighter.hl_map = setmetatable({ - ['error'] = 'Error', - ['text.underline'] = 'Underlined', - ['todo'] = 'Todo', - ['debug'] = 'Debug', - - -- Miscs - ['comment'] = 'Comment', - ['punctuation.delimiter'] = 'Delimiter', - ['punctuation.bracket'] = 'Delimiter', - ['punctuation.special'] = 'Delimiter', - - -- Constants - ['constant'] = 'Constant', - ['constant.builtin'] = 'Special', - ['constant.macro'] = 'Define', - ['define'] = 'Define', - ['macro'] = 'Macro', - ['string'] = 'String', - ['string.regex'] = 'String', - ['string.escape'] = 'SpecialChar', - ['character'] = 'Character', - ['character.special'] = 'SpecialChar', - ['number'] = 'Number', - ['boolean'] = 'Boolean', - ['float'] = 'Float', - - -- Functions - ['function'] = 'Function', - ['function.special'] = 'Function', - ['function.builtin'] = 'Special', - ['function.macro'] = 'Macro', - ['parameter'] = 'Identifier', - ['method'] = 'Function', - ['field'] = 'Identifier', - ['property'] = 'Identifier', - ['constructor'] = 'Special', - - -- Keywords - ['conditional'] = 'Conditional', - ['repeat'] = 'Repeat', - ['label'] = 'Label', - ['operator'] = 'Operator', - ['keyword'] = 'Keyword', - ['exception'] = 'Exception', - - ['type'] = 'Type', - ['type.builtin'] = 'Type', - ['type.qualifier'] = 'Type', - ['type.definition'] = 'Typedef', - ['storageclass'] = 'StorageClass', - ['structure'] = 'Structure', - ['include'] = 'Include', - ['preproc'] = 'PreProc', -}, subcapture_fallback) - ----@private -local function is_highlight_name(capture_name) - local firstc = string.sub(capture_name, 1, 1) - return firstc ~= string.lower(firstc) -end - ---@private function TSHighlighterQuery.new(lang, query_string) local self = setmetatable({}, { __index = TSHighlighterQuery }) self.hl_cache = setmetatable({}, { __index = function(table, capture) - local hl, is_vim_highlight = self:_get_hl_from_capture(capture) - if not is_vim_highlight then - hl = _link_default_highlight_once(lang .. hl, hl) + 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) end - local id = a.nvim_get_hl_id_by_name(hl) - rawset(table, capture, id) return id end, @@ -130,20 +43,6 @@ function TSHighlighterQuery:query() return self._query end ----@private ---- Get the hl from capture. ---- Returns a tuple { highlight_name: string, is_builtin: bool } -function TSHighlighterQuery:_get_hl_from_capture(capture) - local name = self._query.captures[capture] - - if is_highlight_name(name) then - -- From "Normal.left" only keep "Normal" - return vim.split(name, '.', true)[1], true - else - return TSHighlighter.hl_map[name] or 0, false - end -end - --- Creates a new highlighter using @param tree --- ---@param tree The language tree to use for highlighting -- cgit From 064ecb9ec581ec68f1376cde5ad7f960419b0324 Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Thu, 2 Jun 2022 20:07:08 +0200 Subject: feat(treesitter): upstream get_hl_groups_at_position() Util from the nvim-treesitter project. --- runtime/lua/vim/treesitter.lua | 57 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 82d41070ee..8ba4525003 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 highlighter = require('vim.treesitter.highlighter') -- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- Consider use weak references to release parser if all plugins are done with @@ -167,4 +168,60 @@ function M.node_contains(node, range) return start_fits and end_fits end +---Gets a list of highlight group for a given cursor position +---@param bufnr number The buffer number +---@param row number The position row +---@param col number The position column +--- +---@returns (table) A table of highlight groups +function M.get_hl_groups_at_position(bufnr, row, col) + local buf_highlighter = highlighter.active[bufnr] + + if not buf_highlighter then + return {} + end + + local matches = {} + + buf_highlighter.tree:for_each_tree(function(tstree, tree) + if not tstree then + return + end + + local root = tstree:root() + local root_start_row, _, root_end_row, _ = root:range() + + -- Only worry about trees within the line range + if root_start_row > row or root_end_row < row then + return + end + + local q = buf_highlighter:get_query(tree:lang()) + + -- Some injected languages may not have highlight queries. + if not q:query() then + return + end + + local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1) + + for capture, node, metadata in iter do + local hl = q.hl_cache[capture] + + if hl and M.is_in_node_range(node, row, col) then + local c = q._query.captures[capture] -- name of the capture in the query + if c ~= nil then + local general_hl, is_vim_hl = q:_get_hl_from_capture(capture) + local local_hl = not is_vim_hl and (tree:lang() .. general_hl) + table.insert( + matches, + { capture = c, specific = local_hl, general = general_hl, priority = metadata.priority } + ) + end + end + end + end, true) + return matches +end + return M -- cgit From b04ef7f6b966c44b12dbc65b17a761ae9313d6c4 Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 25 Aug 2022 21:41:52 +0200 Subject: fix(treesitter): make it get_captures_at_position --- runtime/lua/vim/treesitter.lua | 45 +++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 14 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 8ba4525003..6431162799 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -2,7 +2,6 @@ local a = vim.api local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') -local highlighter = require('vim.treesitter.highlighter') -- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. -- Consider use weak references to release parser if all plugins are done with @@ -155,6 +154,28 @@ function M.get_node_range(node_or_range) end end +---Determines whether (line, col) position is in node range +--- +---@param node Node defining the range +---@param line A line (0-based) +---@param col A column (0-based) +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 +end + ---Determines if a node contains a range ---@param node table The node ---@param range table The range @@ -168,14 +189,17 @@ function M.node_contains(node, range) return start_fits and end_fits end ----Gets a list of highlight group for a given cursor position +---Gets a list of captures for a given cursor position ---@param bufnr number The buffer number ---@param row number The position row ---@param col number The position column --- ----@returns (table) A table of highlight groups -function M.get_hl_groups_at_position(bufnr, row, col) - local buf_highlighter = highlighter.active[bufnr] +---@returns (table) A table of captures +function M.get_captures_at_position(bufnr, row, col) + if bufnr == 0 then + bufnr = a.nvim_get_current_buf() + end + local buf_highlighter = M.highlighter.active[bufnr] if not buf_highlighter then return {} @@ -206,17 +230,10 @@ function M.get_hl_groups_at_position(bufnr, row, col) local iter = q:query():iter_captures(root, buf_highlighter.bufnr, row, row + 1) for capture, node, metadata in iter do - local hl = q.hl_cache[capture] - - if hl and M.is_in_node_range(node, row, col) then + if M.is_in_node_range(node, row, col) then local c = q._query.captures[capture] -- name of the capture in the query if c ~= nil then - local general_hl, is_vim_hl = q:_get_hl_from_capture(capture) - local local_hl = not is_vim_hl and (tree:lang() .. general_hl) - table.insert( - matches, - { capture = c, specific = local_hl, general = general_hl, priority = metadata.priority } - ) + table.insert(matches, { capture = c, priority = metadata.priority }) end end end -- cgit From f9641d1ac6bae58a42572ce3bfa34d62d5f22619 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 23 Aug 2022 21:05:46 +0200 Subject: refactor(lsp): factor out read_loop function --- runtime/lua/vim/lsp/rpc.lua | 57 +++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 25 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 0926912066..f96321c845 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -241,6 +241,35 @@ function default_dispatchers.on_error(code, err) local _ = log.error() and log.error('client_error:', client_errors[code], err) end +---@private +local function create_read_loop(handle_body, on_no_chunk, on_error) + local parse_chunk = coroutine.wrap(request_parser_loop) + parse_chunk() + return function(err, chunk) + if err then + on_error(err) + return + end + + if not chunk then + if on_no_chunk then + on_no_chunk() + end + return + end + + while true do + local headers, body = parse_chunk(chunk) + if headers then + handle_body(body) + chunk = '' + else + break + end + end + end +end + --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the server is currently limited to stdio. --- @@ -592,31 +621,9 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) local request_parser = coroutine.wrap(request_parser_loop) request_parser() - stdout:read_start(function(err, chunk) - if err then - -- TODO better handling. Can these be intermittent errors? - on_error(client_errors.READ_ERROR, err) - return - end - -- This should signal that we are done reading from the client. - if not chunk then - return - end - -- Flush anything in the parser by looping until we don't get a result - -- anymore. - while true do - local headers, body = request_parser(chunk) - -- If we successfully parsed, then handle the response. - if headers then - handle_body(body) - -- Set chunk to empty so that we can call request_parser to get - -- anything existing in the parser to flush. - chunk = '' - else - break - end - end - end) + stdout:read_start(create_read_loop(handle_body, nil, function(err) + on_error(client_errors.READ_ERROR, err) + end)) return { pid = pid, -- cgit From 7d3e4aee6a11f0bd4c53b0dcb18a496b5fdd32b2 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Tue, 23 Aug 2022 22:39:34 +0200 Subject: refactor(lsp): encapsulate rpc uv handle To prepare for different transports like TCP where the handle won't have a kill method. --- runtime/lua/vim/lsp.lua | 11 +++++------ runtime/lua/vim/lsp/rpc.lua | 12 ++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index fd64c1a495..5986f5a5e8 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1464,14 +1464,13 @@ function lsp.start_client(config) --- you request to stop a client which has previously been requested to --- shutdown, it will automatically escalate and force shutdown. --- - ---@param force (bool, optional) + ---@param force boolean|nil function client.stop(force) - local handle = rpc.handle - if handle:is_closing() then + if rpc.is_closing() then return end if force or not client.initialized or graceful_shutdown_failed then - handle:kill(15) + rpc.terminate() return end -- Sending a signal after a process has exited is acceptable. @@ -1480,7 +1479,7 @@ function lsp.start_client(config) rpc.notify('exit') else -- If there was an error in the shutdown request, then term to be safe. - handle:kill(15) + rpc.terminate() graceful_shutdown_failed = true end end) @@ -1492,7 +1491,7 @@ function lsp.start_client(config) ---@returns (bool) true if client is stopped or in the process of being ---stopped; false otherwise function client.is_stopped() - return rpc.handle:is_closing() + return rpc.is_closing() end ---@private diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index f96321c845..1fbfd3c8e1 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -405,7 +405,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) -- --- Sends a notification to the LSP server. ---@param method (string) The invoked LSP method - ---@param params (table): Parameters for 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 local function notify(method, params) return encode_and_send({ @@ -432,7 +432,7 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) --- Sends a request to the LSP server and runs {callback} upon response. --- ---@param method (string) The invoked LSP method - ---@param params (table) Parameters for the invoked LSP method + ---@param params (table|nil) Parameters for the invoked LSP method ---@param callback (function) 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 @@ -626,8 +626,12 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) end)) return { - pid = pid, - handle = handle, + is_closing = function() + return handle:is_closing() + end, + terminate = function() + handle:kill(15) + end, request = request, notify = notify, } -- cgit From 46bb34e26b3bee89fd1d5d9d1bebced00000732d Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Wed, 24 Aug 2022 19:36:37 +0200 Subject: refactor(lsp): extract rpc client from rpc.start Makes the previously inner functions re-usable for a TCP client --- runtime/lua/vim/lsp/rpc.lua | 616 ++++++++++++++++++++++++-------------------- 1 file changed, 335 insertions(+), 281 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1fbfd3c8e1..5ac1b91e70 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -270,6 +270,292 @@ local function create_read_loop(handle_body, on_no_chunk, on_error) end end +---@class RpcClient +---@field message_index number +---@field message_callbacks table +---@field notify_reply_callbacks table +---@field transport table +---@field dispatchers table + +---@class RpcClient +local Client = {} + +---@private +function Client:encode_and_send(payload) + local _ = log.debug() and log.debug('rpc.send', payload) + if self.transport.is_closing() then + return false + end + local encoded = vim.json.encode(payload) + self.transport.write(format_message_with_content_length(encoded)) + return true +end + +---@private +--- 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 +function Client:notify(method, params) + return self:encode_and_send({ + jsonrpc = '2.0', + method = method, + params = params, + }) +end + +---@private +--- sends an error object to the remote LSP process. +function Client:send_response(request_id, err, result) + return self:encode_and_send({ + id = request_id, + jsonrpc = '2.0', + error = err, + result = result, + }) +end + +---@private +--- Sends a request to the LSP server and runs {callback} upon response. +--- +---@param method (string) The invoked LSP method +---@param params (table|nil) Parameters for the invoked LSP method +---@param callback (function) 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 +function Client:request(method, params, callback, notify_reply_callback) + validate({ + callback = { callback, 'f' }, + notify_reply_callback = { notify_reply_callback, 'f', true }, + }) + self.message_index = self.message_index + 1 + local message_id = self.message_index + local result = self:encode_and_send({ + id = message_id, + jsonrpc = '2.0', + method = method, + params = params, + }) + local message_callbacks = self.message_callbacks + local notify_reply_callbacks = self.notify_reply_callbacks + if result then + if message_callbacks then + message_callbacks[message_id] = schedule_wrap(callback) + else + return false + end + if notify_reply_callback and notify_reply_callbacks then + notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) + end + return result, message_id + else + return false + end +end + +---@private +function Client:on_error(errkind, ...) + assert(client_errors[errkind]) + -- TODO what to do if this fails? + pcall(self.dispatchers.on_error, errkind, ...) +end + +---@private +function Client:pcall_handler(errkind, status, head, ...) + if not status then + self:on_error(errkind, head, ...) + return status, head + end + return status, head, ... +end + +---@private +function Client:try_call(errkind, fn, ...) + return self:pcall_handler(errkind, pcall(fn, ...)) +end + +-- TODO periodically check message_callbacks for old requests past a certain +-- time and log them. This would require storing the timestamp. I could call +-- them with an error then, perhaps. + +---@private +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) + return + end + local _ = log.debug() and log.debug('rpc.receive', decoded) + + if type(decoded.method) == 'string' and decoded.id then + local err + -- Schedule here so that the users functions don't trigger an error and + -- we can still use the result. + schedule(function() + local status, result + status, result, err = self:try_call( + client_errors.SERVER_REQUEST_HANDLER_ERROR, + self.dispatchers.server_request, + decoded.method, + decoded.params + ) + local _ = log.debug() + and log.debug( + 'server_request: callback result', + { status = status, result = result, err = err } + ) + if status then + if not (result or err) then + -- TODO this can be a problem if `null` is sent for result. needs vim.NIL + error( + string.format( + 'method %q: either a result or an error must be sent to the server in response', + decoded.method + ) + ) + end + if err then + assert( + type(err) == 'table', + 'err must be a table. Use rpc_response_error to help format errors.' + ) + local code_name = assert( + protocol.ErrorCodes[err.code], + 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' + ) + err.message = err.message or code_name + end + else + -- On an exception, result will contain the error message. + err = rpc_response_error(protocol.ErrorCodes.InternalError, result) + result = nil + end + self:send_response(decoded.id, err, result) + end) + -- This works because we are expecting vim.NIL here + elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then + -- We sent a number, so we expect a number. + local result_id = assert(tonumber(decoded.id), 'response id must be a number') + + -- Notify the user that a response was received for the request + local notify_reply_callbacks = self.notify_reply_callbacks + local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] + if notify_reply_callback then + validate({ + notify_reply_callback = { notify_reply_callback, 'f' }, + }) + notify_reply_callback(result_id) + notify_reply_callbacks[result_id] = nil + end + + local message_callbacks = self.message_callbacks + + -- Do not surface RequestCancelled to users, it is RPC-internal. + if decoded.error then + local mute_error = false + if decoded.error.code == protocol.ErrorCodes.RequestCancelled then + local _ = log.debug() and log.debug('Received cancellation ack', decoded) + mute_error = true + end + + if mute_error then + -- Clear any callback since this is cancelled now. + -- This is safe to do assuming that these conditions hold: + -- - The server will not send a result callback after this cancellation. + -- - If the server sent this cancellation ACK after sending the result, the user of this RPC + -- client will ignore the result themselves. + if result_id and message_callbacks then + message_callbacks[result_id] = nil + end + return + end + end + + local callback = message_callbacks and message_callbacks[result_id] + if callback then + message_callbacks[result_id] = nil + validate({ + callback = { callback, 'f' }, + }) + if decoded.error then + decoded.error = setmetatable(decoded.error, { + __tostring = format_rpc_error, + }) + end + self:try_call( + client_errors.SERVER_RESULT_CALLBACK_ERROR, + callback, + decoded.error, + decoded.result + ) + else + self:on_error(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, + self.dispatchers.notification, + decoded.method, + decoded.params + ) + else + -- Invalid server message + self:on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) + end +end + +---@private +---@return RpcClient +local function new_client(dispatchers, transport) + local state = { + message_index = 0, + message_callbacks = {}, + notify_reply_callbacks = {}, + transport = transport, + dispatchers = dispatchers, + } + return setmetatable(state, { __index = Client }) +end + +---@private +---@param client RpcClient +local function public_client(client) + local result = {} + + ---@private + function result.is_closing() + return client.transport.is_closing() + end + + ---@private + function result.terminate() + client.transport.terminate() + end + + --- Sends a request to the LSP server and runs {callback} upon response. + --- + ---@param method (string) The invoked LSP method + ---@param params (table|nil) Parameters for the invoked LSP method + ---@param callback (function) 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 + function result.request(method, params, callback, notify_reply_callback) + return client:request(method, params, callback, notify_reply_callback) + end + + --- 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 + function result.notify(method, params) + return client:notify(method, params) + end + + return result +end + --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the server is currently limited to stdio. --- @@ -334,134 +620,59 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) local stdin = uv.new_pipe(false) local stdout = uv.new_pipe(false) local stderr = uv.new_pipe(false) - - local message_index = 0 - local message_callbacks = {} - local notify_reply_callbacks = {} - local handle, pid - do - ---@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) - local function onexit(code, signal) - stdin:close() - stdout:close() - stderr:close() - handle:close() - -- Make sure that message_callbacks/notify_reply_callbacks can be gc'd. - message_callbacks = nil - notify_reply_callbacks = nil - dispatchers.on_exit(code, signal) - 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 - end - end - handle, pid = uv.spawn(cmd, spawn_params, onexit) - if handle == nil then - stdin:close() - stdout:close() - stderr:close() - local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) - if string.match(pid, '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) + + local client = new_client(dispatchers, { + write = function(msg) + stdin:write(msg) + end, + is_closing = function() + return handle == nil or handle:is_closing() + end, + terminate = function() + if handle then + handle:kill(15) end - vim.notify(msg, vim.log.levels.WARN) - return - end - end + end, + }) ---@private - --- Encodes {payload} into a JSON-RPC message and sends it to the remote - --- process. - --- - ---@param payload table - ---@returns true if the payload could be scheduled, false if the main event-loop is in the process of closing. - local function encode_and_send(payload) - local _ = log.debug() and log.debug('rpc.send', payload) - if handle == nil or handle:is_closing() then - return false - end - local encoded = vim.json.encode(payload) - stdin:write(format_message_with_content_length(encoded)) - return true - end - - -- FIXME: DOC: Should be placed on the RPC client object returned by - -- `start()` - -- - --- 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 - local function notify(method, params) - return encode_and_send({ - jsonrpc = '2.0', - method = method, - params = params, - }) + --- 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) + local function onexit(code, signal) + stdin:close() + stdout:close() + stderr:close() + handle:close() + dispatchers.on_exit(code, signal) end - - ---@private - --- sends an error object to the remote LSP process. - local function send_response(request_id, err, result) - return encode_and_send({ - id = request_id, - jsonrpc = '2.0', - error = err, - result = result, - }) + 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 + end end - - -- FIXME: DOC: Should be placed on the RPC client object returned by - -- `start()` - -- - --- Sends a request to the LSP server and runs {callback} upon response. - --- - ---@param method (string) The invoked LSP method - ---@param params (table|nil) Parameters for the invoked LSP method - ---@param callback (function) 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 - local function request(method, params, callback, notify_reply_callback) - validate({ - callback = { callback, 'f' }, - notify_reply_callback = { notify_reply_callback, 'f', true }, - }) - message_index = message_index + 1 - local message_id = message_index - local result = encode_and_send({ - id = message_id, - jsonrpc = '2.0', - method = method, - params = params, - }) - if result then - if message_callbacks then - message_callbacks[message_id] = schedule_wrap(callback) - else - return false - end - if notify_reply_callback and notify_reply_callbacks then - notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback) - end - return result, message_id + handle, pid = uv.spawn(cmd, spawn_params, onexit) + if handle == nil then + stdin:close() + stdout:close() + stderr:close() + local msg = string.format('Spawning language server with cmd: `%s` failed', cmd) + if string.match(pid, 'ENOENT') then + msg = msg + .. '. The language server is either not installed, missing from PATH, or not executable.' else - return false + msg = msg .. string.format(' with error message: %s', pid) end + vim.notify(msg, vim.log.levels.WARN) + return end stderr:read_start(function(_, chunk) @@ -470,171 +681,14 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) end end) - ---@private - local function on_error(errkind, ...) - assert(client_errors[errkind]) - -- TODO what to do if this fails? - pcall(dispatchers.on_error, errkind, ...) - end - ---@private - local function pcall_handler(errkind, status, head, ...) - if not status then - on_error(errkind, head, ...) - return status, head - end - return status, head, ... - end - ---@private - local function try_call(errkind, fn, ...) - return pcall_handler(errkind, pcall(fn, ...)) - end - - -- TODO periodically check message_callbacks for old requests past a certain - -- time and log them. This would require storing the timestamp. I could call - -- them with an error then, perhaps. - - ---@private - local function handle_body(body) - local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } }) - if not ok then - on_error(client_errors.INVALID_SERVER_JSON, decoded) - return - end - local _ = log.debug() and log.debug('rpc.receive', decoded) - - if type(decoded.method) == 'string' and decoded.id then - local err - -- Schedule here so that the users functions don't trigger an error and - -- we can still use the result. - schedule(function() - local status, result - status, result, err = try_call( - client_errors.SERVER_REQUEST_HANDLER_ERROR, - dispatchers.server_request, - decoded.method, - decoded.params - ) - local _ = log.debug() - and log.debug( - 'server_request: callback result', - { status = status, result = result, err = err } - ) - if status then - if not (result or err) then - -- TODO this can be a problem if `null` is sent for result. needs vim.NIL - error( - string.format( - 'method %q: either a result or an error must be sent to the server in response', - decoded.method - ) - ) - end - if err then - assert( - type(err) == 'table', - 'err must be a table. Use rpc_response_error to help format errors.' - ) - local code_name = assert( - protocol.ErrorCodes[err.code], - 'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.' - ) - err.message = err.message or code_name - end - else - -- On an exception, result will contain the error message. - err = rpc_response_error(protocol.ErrorCodes.InternalError, result) - result = nil - end - send_response(decoded.id, err, result) - end) - -- This works because we are expecting vim.NIL here - elseif decoded.id and (decoded.result ~= vim.NIL or decoded.error ~= vim.NIL) then - -- We sent a number, so we expect a number. - local result_id = assert(tonumber(decoded.id), 'response id must be a number') - - -- Notify the user that a response was received for the request - local notify_reply_callback = notify_reply_callbacks and notify_reply_callbacks[result_id] - if notify_reply_callback then - validate({ - notify_reply_callback = { notify_reply_callback, 'f' }, - }) - notify_reply_callback(result_id) - notify_reply_callbacks[result_id] = nil - end - - -- Do not surface RequestCancelled to users, it is RPC-internal. - if decoded.error then - local mute_error = false - if decoded.error.code == protocol.ErrorCodes.RequestCancelled then - local _ = log.debug() and log.debug('Received cancellation ack', decoded) - mute_error = true - end - - if mute_error then - -- Clear any callback since this is cancelled now. - -- This is safe to do assuming that these conditions hold: - -- - The server will not send a result callback after this cancellation. - -- - If the server sent this cancellation ACK after sending the result, the user of this RPC - -- client will ignore the result themselves. - if result_id and message_callbacks then - message_callbacks[result_id] = nil - end - return - end - end - - local callback = message_callbacks and message_callbacks[result_id] - if callback then - message_callbacks[result_id] = nil - validate({ - callback = { callback, 'f' }, - }) - if decoded.error then - decoded.error = setmetatable(decoded.error, { - __tostring = format_rpc_error, - }) - end - try_call( - client_errors.SERVER_RESULT_CALLBACK_ERROR, - callback, - decoded.error, - decoded.result - ) - else - on_error(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 - try_call( - client_errors.NOTIFICATION_HANDLER_ERROR, - dispatchers.notification, - decoded.method, - decoded.params - ) - else - -- Invalid server message - on_error(client_errors.INVALID_SERVER_MESSAGE, decoded) - end + local handle_body = function(body) + client:handle_body(body) end - - local request_parser = coroutine.wrap(request_parser_loop) - request_parser() stdout:read_start(create_read_loop(handle_body, nil, function(err) - on_error(client_errors.READ_ERROR, err) + client:on_error(client_errors.READ_ERROR, err) end)) - return { - is_closing = function() - return handle:is_closing() - end, - terminate = function() - handle:kill(15) - end, - request = request, - notify = notify, - } + return public_client(client) end return { -- cgit From 60ec6e34d585a7f633d49aab790066c1740885e1 Mon Sep 17 00:00:00 2001 From: Mathias Fussenegger Date: Wed, 24 Aug 2022 20:25:34 +0200 Subject: feat(lsp): add tcp support --- runtime/lua/vim/lsp.lua | 38 +++++++++++----- runtime/lua/vim/lsp/rpc.lua | 104 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 109 insertions(+), 33 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 5986f5a5e8..1dc1a045fd 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -289,7 +289,12 @@ local function validate_client_config(config) 'flags.debounce_text_changes must be a number with the debounce time in milliseconds' ) - local cmd, cmd_args = lsp._cmd_parts(config.cmd) + local cmd, cmd_args + if type(config.cmd) == 'function' then + cmd = config.cmd + else + 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) @@ -855,14 +860,17 @@ end --- Used on all running clients. --- The default implementation re-uses a client if name --- and root_dir matches. ----@return number client_id +---@return number|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 or (config.cmd[1] and vim.fs.basename(config.cmd[1])) or nil + 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 local bufnr = api.nvim_get_current_buf() for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do for _, client in pairs(clients) do @@ -893,8 +901,13 @@ end --- The following parameters describe fields in the {config} table. --- --- ----@param cmd: (required, string or list treated like |jobstart()|) Base command ---- that initiates the LSP client. +---@param cmd: (table|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 +--- functions `request`, `notify`, `is_closing` and `terminate` +--- See |vim.lsp.rpc.request| and |vim.lsp.rpc.notify| +--- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect| --- ---@param cmd_cwd: (string, default=|getcwd()|) Directory to launch --- the `cmd` process. Not related to `root_dir`. @@ -1164,11 +1177,16 @@ function lsp.start_client(config) end -- Start the RPC client. - local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { - cwd = config.cmd_cwd, - env = config.cmd_env, - detached = config.detached, - }) + local rpc + if type(cmd) == 'function' then + rpc = cmd(dispatch) + else + rpc = lsp_rpc.start(cmd, cmd_args, dispatch, { + cwd = config.cmd_cwd, + env = config.cmd_env, + detached = config.detached, + }) + end -- Return nil if client fails to start if not rpc then diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 5ac1b91e70..238c6c0570 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -556,6 +556,84 @@ local function public_client(client) return result end +---@private +local function merge_dispatchers(dispatchers) + if dispatchers then + local user_dispatchers = dispatchers + dispatchers = {} + for dispatch_name, default_dispatch in pairs(default_dispatchers) do + local user_dispatcher = user_dispatchers[dispatch_name] + if user_dispatcher then + if type(user_dispatcher) ~= 'function' then + error(string.format('dispatcher.%s must be a function', dispatch_name)) + end + -- server_request is wrapped elsewhere. + if + not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. + then + user_dispatcher = schedule_wrap(user_dispatcher) + end + dispatchers[dispatch_name] = user_dispatcher + else + dispatchers[dispatch_name] = default_dispatch + end + end + else + dispatchers = default_dispatchers + end + return dispatchers +end + +--- Create a LSP RPC client factory that connects via TCP to the given host +--- and port +--- +---@param host string +---@param port number +---@return function +local function connect(host, port) + return function(dispatchers) + dispatchers = merge_dispatchers(dispatchers) + local tcp = uv.new_tcp() + local closing = false + local transport = { + write = function(msg) + tcp:write(msg) + end, + is_closing = function() + return closing + end, + terminate = function() + if not closing then + closing = true + tcp:shutdown() + tcp:close() + dispatchers.on_exit(0, 0) + end + end, + } + local client = new_client(dispatchers, transport) + tcp:connect(host, port, function(err) + if err then + vim.schedule(function() + vim.notify( + string.format('Could not connect to %s:%s, reason: %s', host, port, vim.inspect(err)), + vim.log.levels.WARN + ) + end) + return + end + local handle_body = function(body) + client:handle_body(body) + end + tcp:read_start(create_read_loop(handle_body, transport.terminate, function(read_err) + client:on_error(client_errors.READ_ERROR, read_err) + end)) + end) + + return public_client(client) + end +end + --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the server is currently limited to stdio. --- @@ -593,30 +671,8 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) if extra_spawn_params and extra_spawn_params.cwd then assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory') end - if dispatchers then - local user_dispatchers = dispatchers - dispatchers = {} - for dispatch_name, default_dispatch in pairs(default_dispatchers) do - local user_dispatcher = user_dispatchers[dispatch_name] - if user_dispatcher then - if type(user_dispatcher) ~= 'function' then - error(string.format('dispatcher.%s must be a function', dispatch_name)) - end - -- server_request is wrapped elsewhere. - if - not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason. - then - user_dispatcher = schedule_wrap(user_dispatcher) - end - dispatchers[dispatch_name] = user_dispatcher - else - dispatchers[dispatch_name] = default_dispatch - end - end - else - dispatchers = default_dispatchers - end + dispatchers = merge_dispatchers(dispatchers) local stdin = uv.new_pipe(false) local stdout = uv.new_pipe(false) local stderr = uv.new_pipe(false) @@ -693,8 +749,10 @@ 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, } -- vim:sw=2 ts=2 et -- cgit From efacb6e974fa6391bcc916749103f04fa9b9f6f7 Mon Sep 17 00:00:00 2001 From: Raphael Date: Tue, 30 Aug 2022 01:09:14 +0800 Subject: fix(lsp): clean the diagnostic cache when buffer delete (#19449) Co-authored-by: Gregory Anders --- runtime/lua/vim/diagnostic.lua | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 3f71d4f70d..db92085423 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -45,18 +45,24 @@ local bufnr_and_namespace_cacher_mt = { end, } -local diagnostic_cache = setmetatable({}, { - __index = function(t, bufnr) - assert(bufnr > 0, 'Invalid buffer number') - vim.api.nvim_buf_attach(bufnr, false, { - on_detach = function() - rawset(t, bufnr, nil) -- clear cache - end, - }) - t[bufnr] = {} - return t[bufnr] - end, -}) +local diagnostic_cache +do + local group = vim.api.nvim_create_augroup('DiagnosticBufDelete', {}) + diagnostic_cache = setmetatable({}, { + __index = function(t, bufnr) + assert(bufnr > 0, 'Invalid buffer number') + vim.api.nvim_create_autocmd('BufDelete', { + group = group, + buffer = bufnr, + callback = function() + rawset(t, bufnr, nil) + end, + }) + t[bufnr] = {} + return t[bufnr] + end, + }) +end local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) local diagnostic_attached_buffers = {} -- cgit From 981ae83fadd3bf8603f144a8bc27347e4fb7b3ad Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 30 Aug 2022 13:14:27 +0200 Subject: fix(docs): update lsp.rpc.start docs to match return value changes (#20003) Follow up to https://github.com/neovim/neovim/pull/19916 --- runtime/lua/vim/lsp/rpc.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 238c6c0570..70f838f34d 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -654,11 +654,8 @@ end ---@returns Methods: --- - `notify()` |vim.lsp.rpc.notify()| --- - `request()` |vim.lsp.rpc.request()| ---- ----@returns Members: ---- - {pid} (number) The LSP server's PID. ---- - {handle} A handle for low-level interaction with the LSP server process ---- |vim.loop|. +--- - `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 }) -- cgit From 6b7eed1884c3b022cc15568c39f80f8f4da0780b Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 30 Aug 2022 21:16:03 +0200 Subject: Vim 9.0.{0314,0319}: some filetypes are not recognized (#20005) * vim-patch:9.0.0314: VDM files are not recognized Problem: VDM files are not recognized. Solution: Add patterns for VDM files. (Alessandro Pezzoni, closes vim/vim#11004) https://github.com/vim/vim/commit/bf26941f40923d331169a4ecb7341608f5d1ca38 * vim-patch:9.0.0319: Godot shader files are not recognized Problem: Godot shader files are not recognized. Solution: Add patterns for "gdshader". (Maxim Kim, closes vim/vim#11006) https://github.com/vim/vim/commit/d5c8f11905abc1bdf3b8864dbc40187855ed9374 --- runtime/lua/vim/filetype.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 99c98764dd..fcd697a7c1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -422,9 +422,11 @@ local extension = { gdb = 'gdb', gdmo = 'gdmo', mo = 'gdmo', - tres = 'gdresource', tscn = 'gdresource', + tres = 'gdresource', gd = 'gdscript', + gdshader = 'gdshader', + shader = 'gdshader', ged = 'gedcom', gmi = 'gemtext', gemini = 'gemtext', @@ -1011,6 +1013,11 @@ local extension = { dsm = 'vb', ctl = 'vb', vbs = 'vb', + vdmpp = 'vdmpp', + vpp = 'vdmpp', + vdmrt = 'vdmrt', + vdmsl = 'vdmsl', + vdm = 'vdmsl', vr = 'vera', vri = 'vera', vrh = 'vera', -- cgit From ce80b8f50d7d56ac12aa06a64a65799ec18b69af Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Fri, 2 Sep 2022 08:16:17 +0200 Subject: vim-patch:9.0.0349: filetype of *.sil files not well detected (#20050) Problem: Filetype of *.sil files not well detected. Solution: Inspect the file contents to guess the filetype. https://github.com/vim/vim/commit/be807d582499acbe314ead3891481cba6ca136df --- runtime/lua/vim/filetype.lua | 4 +++- runtime/lua/vim/filetype/detect.lua | 13 +++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index fcd697a7c1..6306605641 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -902,7 +902,9 @@ local extension = { sig = function(path, bufnr) return require('vim.filetype.detect').sig(bufnr) end, - sil = 'sil', + sil = function(path, bufnr) + return require('vim.filetype.detect').sil(bufnr) + end, sim = 'simula', ['s85'] = 'sinda', sin = 'sinda', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 2be9dcff88..7fc7f1b7ca 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1194,6 +1194,19 @@ function M.shell(path, contents, name) return name end +-- Swift Intermediate Language or SILE +function M.sil(bufnr) + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if line:find('^%s*[\\%%]') then + return 'sile' + elseif line:find('^%s*%S') then + return 'sil' + end + end + -- No clue, default to "sil" + return 'sil' +end + -- SMIL or SNMP MIB file function M.smi(bufnr) local line = getlines(bufnr, 1) -- cgit From 2afcdbd63a5b0cbeaad9d83b096a3af5201c67a9 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Fri, 2 Sep 2022 15:20:29 +0100 Subject: feat(Man): port to Lua (#19912) Co-authored-by: zeertzjq --- runtime/lua/man.lua | 601 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 594 insertions(+), 7 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 5da3d2a92f..4b8239ce74 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,7 +1,75 @@ require('vim.compat') +local api, fn = vim.api, vim.fn + +local find_arg = '-w' +local localfile_arg = true -- Always use -l if possible. #6683 local buf_hls = {} +local M = {} + +local function man_error(msg) + M.errormsg = 'man.lua: ' .. vim.inspect(msg) + error(M.errormsg) +end + +-- Run a system command and timeout after 30 seconds. +local function man_system(cmd, silent) + local stdout_data = {} + local stderr_data = {} + local stdout = vim.loop.new_pipe(false) + local stderr = vim.loop.new_pipe(false) + + local done = false + local exit_code + + local handle = vim.loop.spawn(cmd[1], { + args = vim.list_slice(cmd, 2), + stdio = { nil, stdout, stderr }, + }, function(code) + exit_code = code + stdout:close() + stderr:close() + done = true + end) + + if handle then + stdout:read_start(function(_, data) + stdout_data[#stdout_data + 1] = data + end) + stderr:read_start(function(_, data) + stderr_data[#stderr_data + 1] = data + end) + else + stdout:close() + stderr:close() + if not silent then + man_error(string.format('command error: %s', table.concat(cmd))) + end + end + + vim.wait(30000, function() + return done + end) + + if not done then + if handle then + vim.loop.shutdown(handle) + stdout:close() + stderr:close() + end + man_error(string.format('command timed out: %s', table.concat(cmd, ' '))) + end + + if exit_code ~= 0 and not silent then + man_error( + string.format("command error '%s': %s", table.concat(cmd, ' '), table.concat(stderr_data)) + ) + end + + return table.concat(stdout_data) +end + local function highlight_line(line, linenr) local chars = {} local prev_char = '' @@ -152,21 +220,540 @@ local function highlight_line(line, linenr) end local function highlight_man_page() - local mod = vim.api.nvim_buf_get_option(0, 'modifiable') - vim.api.nvim_buf_set_option(0, 'modifiable', true) + local mod = vim.bo.modifiable + vim.bo.modifiable = true - local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local lines = api.nvim_buf_get_lines(0, 0, -1, false) for i, line in ipairs(lines) do lines[i] = highlight_line(line, i) end - vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + api.nvim_buf_set_lines(0, 0, -1, false, lines) for _, args in ipairs(buf_hls) do - vim.api.nvim_buf_add_highlight(unpack(args)) + api.nvim_buf_add_highlight(unpack(args)) end buf_hls = {} - vim.api.nvim_buf_set_option(0, 'modifiable', mod) + vim.bo.modifiable = mod +end + +-- replace spaces in a man page name with underscores +-- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)'; +-- while editing SQL source code, it's nice to visually select 'CREATE TABLE' +-- and hit 'K', which requires this transformation +local function spaces_to_underscores(str) + local res = str:gsub('%s', '_') + return res +end + +local function get_path(sect, name, silent) + name = name or '' + sect = sect or '' + -- Some man implementations (OpenBSD) return all available paths from the + -- search command. Previously, this function would simply select the first one. + -- + -- However, some searches will report matches that are incorrect: + -- man -w strlen may return string.3 followed by strlen.3, and therefore + -- selecting the first would get us the wrong page. Thus, we must find the + -- first matching one. + -- + -- There's yet another special case here. Consider the following: + -- If you run man -w strlen and string.3 comes up first, this is a problem. We + -- should search for a matching named one in the results list. + -- However, if you search for man -w clock_gettime, you will *only* get + -- clock_getres.2, which is the right page. Searching the resuls for + -- clock_gettime will no longer work. In this case, we should just use the + -- first one that was found in the correct section. + -- + -- Finally, we can avoid relying on -S or -s here since they are very + -- inconsistently supported. Instead, call -w with a section and a name. + local cmd + if sect == '' then + cmd = { 'man', find_arg, name } + else + cmd = { 'man', find_arg, sect, name } + end + + local lines = man_system(cmd, silent) + if lines == nil then + return nil + end + + local results = vim.split(lines, '\n', { trimempty = true }) + + if #results == 0 then + return + end + + -- find any that match the specified name + local namematches = vim.tbl_filter(function(v) + return fn.fnamemodify(v, ':t'):match(name) + end, results) or {} + local sectmatches = {} + + if #namematches > 0 and sect ~= '' then + sectmatches = vim.tbl_filter(function(v) + return fn.fnamemodify(v, ':e') == sect + end, namematches) + end + + return fn.substitute(sectmatches[1] or namematches[1] or results[1], [[\n\+$]], '', '') +end + +local function matchstr(text, pat_or_re) + local re = type(pat_or_re) == 'string' and vim.regex(pat_or_re) or pat_or_re + + local s, e = re:match_str(text) + + if s == nil then + return + end + + return text:sub(vim.str_utfindex(text, s) + 1, vim.str_utfindex(text, e)) +end + +-- attempt to extract the name and sect out of 'name(sect)' +-- otherwise just return the largest string of valid characters in ref +local function extract_sect_and_name_ref(ref) + ref = ref or '' + if ref:sub(1, 1) == '-' then -- try ':Man -pandoc' with this disabled. + man_error("manpage name cannot start with '-'") + end + local ref1 = ref:match('[^()]+%([^()]+%)') + if not ref1 then + local name = ref:match('[^()]+') + if not name then + man_error('manpage reference cannot contain only parentheses: ' .. ref) + end + return '', spaces_to_underscores(name) + end + local parts = vim.split(ref1, '(', { plain = true }) + -- see ':Man 3X curses' on why tolower. + -- TODO(nhooyr) Not sure if this is portable across OSs + -- but I have not seen a single uppercase section. + local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower() + local name = spaces_to_underscores(parts[1]) + return sect, name +end + +-- verify_exists attempts to find the path to a manpage +-- based on the passed section and name. +-- +-- 1. If manpage could not be found with the given sect and name, +-- then try all the sections in b:man_default_sects. +-- 2. If it still could not be found, then we try again without a section. +-- 3. If still not found but $MANSECT is set, then we try again with $MANSECT +-- unset. +local function verify_exists(sect, name) + if sect and sect ~= '' then + local ret = get_path(sect, name, true) + if ret then + return ret + end + end + + if vim.b.man_default_sects ~= nil then + local sects = vim.split(vim.b.man_default_sects, ',', { plain = true, trimempty = true }) + for _, sec in ipairs(sects) do + local ret = get_path(sec, name, true) + if ret then + return ret + end + end + end + + -- if none of the above worked, we will try with no section + local res_empty_sect = get_path('', name, true) + if res_empty_sect then + return res_empty_sect + end + + -- if that still didn't work, we will check for $MANSECT and try again with it + -- unset + if vim.env.MANSECT then + local mansect = vim.env.MANSECT + vim.env.MANSECT = nil + local res = get_path('', name, true) + vim.env.MANSECT = mansect + if res then + return res + end + end + + -- finally, if that didn't work, there is no hope + man_error('no manual entry for ' .. name) +end + +local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]]) + +-- Extracts the name/section from the 'path/name.sect', because sometimes the actual section is +-- more specific than what we provided to `man` (try `:Man 3 App::CLI`). +-- Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we +-- still want the name of the buffer to be 'printf'. +local function extract_sect_and_name_path(path) + local tail = fn.fnamemodify(path, ':t') + if EXT_RE:match_str(path) then -- valid extensions + tail = fn.fnamemodify(tail, ':r') + end + local name, sect = tail:match('^(.+)%.([^.]+)$') + return sect, name +end + +local function find_man() + local win = 1 + while win <= fn.winnr('$') do + local buf = fn.winbufnr(win) + if vim.bo[buf].filetype == 'man' then + vim.cmd(win .. 'wincmd w') + return true + end + win = win + 1 + end + return false +end + +local function set_options(pager) + vim.bo.swapfile = false + vim.bo.buftype = 'nofile' + vim.bo.bufhidden = 'hide' + vim.bo.modified = false + vim.bo.readonly = true + vim.bo.modifiable = false + vim.b.pager = pager + vim.bo.filetype = 'man' +end + +local function get_page(path, silent) + -- Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065). + -- Soft-wrap: ftplugin/man.lua sets wrap/breakindent/…. + -- Hard-wrap: driven by `man`. + local manwidth + if (vim.g.man_hardwrap or 1) ~= 1 then + manwidth = 999 + elseif vim.env.MANWIDTH then + manwidth = vim.env.MANWIDTH + else + manwidth = api.nvim_win_get_width(0) + end + -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). + -- http://comments.gmane.org/gmane.editors.vim.devel/29085 + -- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. + local cmd = { 'env', 'MANPAGER=cat', 'MANWIDTH=' .. manwidth, 'MAN_KEEP_FORMATTING=1', 'man' } + if localfile_arg then + cmd[#cmd + 1] = '-l' + end + cmd[#cmd + 1] = path + return man_system(cmd, silent) +end + +local function put_page(page) + vim.bo.modified = true + vim.bo.readonly = false + vim.bo.swapfile = false + + api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n')) + + while fn.getline(1):match('^%s*$') do + api.nvim_buf_set_lines(0, 0, 1, false, {}) + end + -- XXX: nroff justifies text by filling it with whitespace. That interacts + -- badly with our use of $MANWIDTH=999. Hack around this by using a fixed + -- size for those whitespace regions. + vim.cmd([[silent! keeppatterns keepjumps %s/\s\{199,}/\=repeat(' ', 10)/g]]) + vim.cmd('1') -- Move cursor to first line + highlight_man_page() + set_options(false) +end + +local function format_candidate(path, psect) + if matchstr(path, [[\.\%(pdf\|in\)$]]) then -- invalid extensions + return '' + end + local sect, name = extract_sect_and_name_path(path) + if sect == psect then + return name + elseif sect and name and matchstr(sect, psect .. '.\\+$') then -- invalid extensions + -- We include the section if the user provided section is a prefix + -- of the actual section. + return ('%s(%s)'):format(name, sect) + end + return '' end -return { highlight_man_page = highlight_man_page } +local function get_paths(sect, name, do_fallback) + -- callers must try-catch this, as some `man` implementations don't support `s:find_arg` + local ok, ret = pcall(function() + local mandirs = + table.concat(vim.split(man_system({ 'man', find_arg }), '[:\n]', { trimempty = true }), ',') + local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) + pcall(function() + -- Prioritize the result from verify_exists as it obeys b:man_default_sects. + local first = verify_exists(sect, name) + paths = vim.tbl_filter(function(v) + return v ~= first + end, paths) + paths = { first, unpack(paths) } + end) + return paths + end) + + if not ok then + if not do_fallback then + error(ret) + end + + -- Fallback to a single path, with the page we're trying to find. + ok, ret = pcall(verify_exists, sect, name) + + return { ok and ret or nil } + end + return ret or {} +end + +local function complete(sect, psect, name) + local pages = get_paths(sect, name, false) + -- We remove duplicates in case the same manpage in different languages was found. + return fn.uniq(fn.sort(vim.tbl_map(function(v) + return format_candidate(v, psect) + end, pages) or {}, 'i')) +end + +-- see extract_sect_and_name_ref on why tolower(sect) +function M.man_complete(arg_lead, cmd_line, _) + local args = vim.split(cmd_line, '%s+', { trimempty = true }) + local cmd_offset = fn.index(args, 'Man') + if cmd_offset > 0 then + -- Prune all arguments up to :Man itself. Otherwise modifier commands like + -- :tab, :vertical, etc. would lead to a wrong length. + args = vim.list_slice(args, cmd_offset + 1) + end + + if #args > 3 then + return {} + end + + if #args == 1 then + -- returning full completion is laggy. Require some arg_lead to complete + -- return complete('', '', '') + return {} + end + + if arg_lead:match('^[^()]+%([^()]*$') then + -- cursor (|) is at ':Man printf(|' or ':Man 1 printf(|' + -- The later is is allowed because of ':Man pri'. + -- It will offer 'priclass.d(1m)' even though section is specified as 1. + local tmp = vim.split(arg_lead, '(', { plain = true }) + local name = tmp[1] + local sect = (tmp[2] or ''):lower() + return complete(sect, '', name) + end + + if not args[2]:match('^[^()]+$') then + -- cursor (|) is at ':Man 3() |' or ':Man (3|' or ':Man 3() pri|' + -- or ':Man 3() pri |' + return {} + end + + if #args == 2 then + local name, sect + if arg_lead == '' then + -- cursor (|) is at ':Man 1 |' + name = '' + sect = args[1]:lower() + else + -- cursor (|) is at ':Man pri|' + if arg_lead:match('/') then + -- if the name is a path, complete files + -- TODO(nhooyr) why does this complete the last one automatically + return fn.glob(arg_lead .. '*', false, true) + end + name = arg_lead + sect = '' + end + return complete(sect, sect, name) + end + + if not arg_lead:match('[^()]+$') then + -- cursor (|) is at ':Man 3 printf |' or ':Man 3 (pr)i|' + return {} + end + + -- cursor (|) is at ':Man 3 pri|' + local name = arg_lead + local sect = args[2]:lower() + return complete(sect, sect, name) +end + +function M.goto_tag(pattern, _, _) + local sect, name = extract_sect_and_name_ref(pattern) + + local paths = get_paths(sect, name, true) + local structured = {} + + for _, path in ipairs(paths) do + sect, name = extract_sect_and_name_path(path) + if sect and name then + structured[#structured + 1] = { + name = name, + title = name .. '(' .. sect .. ')', + } + end + end + + if vim.o.cscopetag then + -- return only a single entry so we work well with :cstag (#11675) + structured = { structured[1] } + end + + return vim.tbl_map(function(entry) + return { + name = entry.name, + filename = 'man://' .. entry.title, + cmd = '1', + } + end, structured) +end + +-- Called when Nvim is invoked as $MANPAGER. +function M.init_pager() + if fn.getline(1):match('^%s*$') then + api.nvim_buf_set_lines(0, 0, 1, false, {}) + else + vim.cmd('keepjumps 1') + end + highlight_man_page() + -- Guess the ref from the heading (which is usually uppercase, so we cannot + -- know the correct casing, cf. `man glDrawArraysInstanced`). + local ref = fn.substitute(matchstr(fn.getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g') + local ok, res = pcall(extract_sect_and_name_ref, ref) + vim.b.man_sect = ok and res or '' + + if not fn.bufname('%'):match('man://') then -- Avoid duplicate buffers, E95. + vim.cmd.file({ 'man://' .. fn.fnameescape(ref):lower(), mods = { silent = true } }) + end + + set_options(true) +end + +function M.open_page(count, smods, args) + if #args > 2 then + man_error('too many arguments') + end + + local ref + if #args == 0 then + ref = vim.bo.filetype == 'man' and fn.expand('') or fn.expand('') + if ref == '' then + man_error('no identifier under cursor') + end + elseif #args == 1 then + ref = args[1] + else + -- Combine the name and sect into a manpage reference so that all + -- verification/extraction can be kept in a single function. + -- If args[2] is a reference as well, that is fine because it is the only + -- reference that will match. + ref = ('%s(%s)'):format(args[2], args[1]) + end + + local sect, name = extract_sect_and_name_ref(ref) + if count >= 0 then + sect = tostring(count) + end + + local path = verify_exists(sect, name) + sect, name = extract_sect_and_name_path(path) + + local buf = fn.bufnr() + local save_tfu = vim.bo[buf].tagfunc + vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag" + + local target = ('%s(%s)'):format(name, sect) + + local ok, ret = pcall(function() + if not smods.tab and find_man() then + vim.cmd.tag({ target, mods = { silent = true, keepalt = true } }) + else + smods.silent = true + smods.keepalt = true + vim.cmd.stag({ target, mods = smods }) + end + end) + + vim.bo[buf].tagfunc = save_tfu + + if not ok then + error(ret) + else + set_options(false) + end + + vim.b.man_sect = sect +end + +-- Called when a man:// buffer is opened. +function M.read_page(ref) + local sect, name = extract_sect_and_name_ref(ref) + local path = verify_exists(sect, name) + sect = extract_sect_and_name_path(path) + local page = get_page(path) + vim.b.man_sect = sect + put_page(page) +end + +function M.show_toc() + local bufname = fn.bufname('%') + local info = fn.getloclist(0, { winid = 1 }) + if info ~= '' and vim.w[info.winid].qf_toc == bufname then + vim.cmd.lopen() + return + end + + local toc = {} + local lnum = 2 + local last_line = fn.line('$') - 1 + local section_title_re = vim.regex([[^\%( \{3\}\)\=\S.*$]]) + local flag_title_re = vim.regex([[^\s\+\%(+\|-\)\S\+]]) + while lnum and lnum < last_line do + local text = fn.getline(lnum) + if section_title_re:match_str(text) then + -- if text is a section title + toc[#toc + 1] = { + bufnr = fn.bufnr('%'), + lnum = lnum, + text = text, + } + elseif flag_title_re:match_str(text) then + -- if text is a flag title. we strip whitespaces and prepend two + -- spaces to have a consistent format in the loclist. + toc[#toc + 1] = { + bufnr = fn.bufnr('%'), + lnum = lnum, + text = ' ' .. fn.substitute(text, [[^\s*\(.\{-}\)\s*$]], [[\1]], ''), + } + end + lnum = fn.nextnonblank(lnum + 1) + end + + fn.setloclist(0, toc, ' ') + fn.setloclist(0, {}, 'a', { title = 'Man TOC' }) + vim.cmd.lopen() + vim.w.qf_toc = bufname +end + +local function init() + local path = get_path('', 'man', true) + local page + if path ~= nil then + -- Check for -l support. + page = get_page(path, true) + end + + if page == '' or page == nil then + localfile_arg = false + end +end + +init() + +return M -- cgit From 1ef7720567b08caec0c077605fb2a01a9d6eafbc Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 1 Sep 2022 18:46:34 +0800 Subject: fix(api)!: correctly deal with number before :tab Now nvim_parse_cmd and nvim_create_user_command use a "tab" value which is the same as the number passed before :tab modifier instead of the number plus 1, and "tab" value is -1 if :tab modifier is not used. --- runtime/lua/man.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 4b8239ce74..82ed0305f7 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -671,7 +671,7 @@ function M.open_page(count, smods, args) local target = ('%s(%s)'):format(name, sect) local ok, ret = pcall(function() - if not smods.tab and find_man() then + if smods.tab == -1 and find_man() then vim.cmd.tag({ target, mods = { silent = true, keepalt = true } }) else smods.silent = true -- cgit From 0822896efcf0da7002e323369fdc1e4a15ad1d57 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 5 Sep 2022 15:52:27 +0200 Subject: feat(treesitter): add vim.treesitter.start(), enable for Lua * Add vim.treesitter.start() for starting treesitter highlighting via ftplugin or autocommand (can be extended later for fold, indent, matchpairs, ...) * Add vim.treesitter.stop() for manually stopping treesitter highlighting * Enable treesitter highlighting for Lua if `vim.g.ts_highlight_lua = true` is set in `init.lua` --- runtime/lua/vim/treesitter.lua | 86 +++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 17 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 6431162799..9c43811e03 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -32,9 +32,11 @@ setmetatable(M, { --- --- It is not recommended to use this, use vim.treesitter.get_parser() instead. --- ----@param bufnr The buffer the parser will be tied to ----@param lang The language of the parser ----@param opts Options to pass to the created language tree +---@param bufnr string 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 +--- +---@returns table Created parser object function M._create_parser(bufnr, lang, opts) language.require_language(lang) if bufnr == 0 then @@ -79,11 +81,11 @@ end --- If needed this will create the parser. --- Unconditionally attach the provided callback --- ----@param bufnr The buffer the parser should be tied to ----@param lang The filetype of this parser ----@param opts Options object to pass to the created language tree +---@param bufnr number|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 --- ----@returns The parser +---@returns table Parser object function M.get_parser(bufnr, lang, opts) opts = opts or {} @@ -120,8 +122,8 @@ end --- Determines whether a node is the ancestor of another --- ----@param dest table the possible ancestor ----@param source table the possible descendant node +---@param dest table Possible ancestor +---@param source table Possible descendant node --- ---@returns (boolean) True if dest is an ancestor of source function M.is_ancestor(dest, source) @@ -156,9 +158,11 @@ end ---Determines whether (line, col) position is in node range --- ----@param node Node defining the range ----@param line A line (0-based) ----@param col A column (0-based) +---@param node table Node defining the range +---@param line number Line (0-based) +---@param col number Column (0-based) +--- +---@returns (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 @@ -177,8 +181,8 @@ function M.is_in_node_range(node, line, col) end ---Determines if a node contains a range ----@param node table The node ----@param range table The range +---@param node table +---@param range table --- ---@returns (boolean) True if the node contains the range function M.node_contains(node, range) @@ -190,9 +194,9 @@ function M.node_contains(node, range) end ---Gets a list of captures for a given cursor position ----@param bufnr number The buffer number ----@param row number The position row ----@param col number The position column +---@param bufnr number Buffer number (0 for current buffer) +---@param row number Position row +---@param col number Position column --- ---@returns (table) A table of captures function M.get_captures_at_position(bufnr, row, col) @@ -241,4 +245,52 @@ function M.get_captures_at_position(bufnr, row, col) return matches end +--- Start treesitter highlighting for a buffer +--- +--- Can be used in an ftplugin or FileType autocommand +--- +--- Note: By default, disables regex syntax highlighting, which may be required for some plugins. +--- In this case, add `{ syntax = true }`. +--- +--- Example: +--- +---
+--- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
+---     callback = function(args)
+---         vim.treesitter.start(args.buf, 'latex', { syntax = true })
+---     end
+--- })
+--- 
+--- +---@param bufnr number|nil Buffer to be highlighted (default: current buffer) +---@param lang string|nil Language of the parser (default: buffer filetype) +---@param opts table|nil Optional keyword arguments: +--- - `syntax` boolean Run regex syntax highlighting (default false) +function M.start(bufnr, lang, opts) + bufnr = bufnr or a.nvim_get_current_buf() + + local parser = M.get_parser(bufnr, lang) + + M.highlighter.new(parser) + + vim.b[bufnr].ts_highlight = true + + if opts and opts.syntax then + vim.bo[bufnr].syntax = 'on' + end +end + +---Stop treesitter highlighting for a buffer +--- +---@param bufnr number|nil Buffer to stop highlighting (default: current buffer) +function M.stop(bufnr) + bufnr = bufnr or a.nvim_get_current_buf() + + if M.highlighter.active[bufnr] then + M.highlighter.active[bufnr]:destroy() + end + + vim.bo[bufnr].syntax = 'on' +end + return M -- cgit From ffe98531b9a6a90a7f4a7ae2105b3c50ad9332fd Mon Sep 17 00:00:00 2001 From: Quentin Rasmont Date: Sat, 30 Apr 2022 11:51:14 +0200 Subject: feat(treesitter): upstream get_node_at_cursor() Util from the nvim-treesitter project. --- runtime/lua/vim/treesitter.lua | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 9c43811e03..d71f8611c1 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -81,7 +81,7 @@ end --- If needed this will create the parser. --- Unconditionally attach the provided callback --- ----@param bufnr number|nil Buffer the parser should be tied to: (default current buffer) +---@param bufnr number|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 --- @@ -245,6 +245,27 @@ function M.get_captures_at_position(bufnr, row, col) return matches end +--- Gets the smallest named node under the cursor +--- +---@param winnr number Window handle or 0 for current window +---@param opts table Options table +---@param opts.ignore_injections boolean (default true) Ignore injected languages. +--- +---@returns (table) The named node under the cursor +function M.get_node_at_cursor(winnr, opts) + winnr = winnr or 0 + local cursor = a.nvim_win_get_cursor(winnr) + local ts_cursor_range = { cursor[1] - 1, cursor[2], cursor[1] - 1, cursor[2] } + + local buf = a.nvim_win_get_buf(winnr) + local root_lang_tree = M.get_parser(buf) + if not root_lang_tree then + return + end + + return root_lang_tree:named_node_for_range(ts_cursor_range, opts) +end + --- Start treesitter highlighting for a buffer --- --- Can be used in an ftplugin or FileType autocommand -- cgit From 95fd1ad83e24bbb14cc084fb001251939de6c0a9 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 6 Sep 2022 08:50:06 +0200 Subject: refactor(treesitter): get_{nodes,captures}_at_{position,cursor} --- runtime/lua/vim/treesitter.lua | 65 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 12 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index d71f8611c1..8a540b9012 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -147,7 +147,7 @@ end --- ---@param node_or_range table --- ----@returns start_row, start_col, end_row, end_col +---@returns table start_row, start_col, end_row, end_col function M.get_node_range(node_or_range) if type(node_or_range) == 'table' then return unpack(node_or_range) @@ -198,7 +198,11 @@ end ---@param row number Position row ---@param col number Position column --- ----@returns (table) A table of captures +---@param bufnr number Buffer number (0 for current buffer) +---@param row number Position row +---@param col number Position column +--- +---@returns (table) Table of captures function M.get_captures_at_position(bufnr, row, col) if bufnr == 0 then bufnr = a.nvim_get_current_buf() @@ -245,25 +249,62 @@ function M.get_captures_at_position(bufnr, row, col) return matches end ---- Gets the smallest named node under the cursor +---Gets a list of captures under the cursor --- ----@param winnr number Window handle or 0 for current window ----@param opts table Options table ----@param opts.ignore_injections boolean (default true) Ignore injected languages. +---@param winnr number|nil Window handle or 0 for current window (default) --- ----@returns (table) The named node under the cursor -function M.get_node_at_cursor(winnr, opts) +---@returns (table) Named node under the cursor +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 ts_cursor_range = { cursor[1] - 1, cursor[2], cursor[1] - 1, cursor[2] } - local buf = a.nvim_win_get_buf(winnr) - local root_lang_tree = M.get_parser(buf) + local data = M.get_captures_at_position(bufnr, cursor[1] - 1, cursor[2]) + + local captures = {} + + for _, capture in ipairs(data) do + table.insert(captures, capture.capture) + end + + return captures +end + +--- Gets the smallest named node at position +--- +---@param bufnr number Buffer number (0 for current buffer) +---@param row number Position row +---@param col number Position column +---@param opts table Optional keyword arguments: +--- - ignore_injections boolean Ignore injected languages (default true) +--- +---@returns (table) Named node under the cursor +function M.get_node_at_position(bufnr, row, col, opts) + if bufnr == 0 then + bufnr = a.nvim_get_current_buf() + end + 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_cursor_range, opts) + return root_lang_tree:named_node_for_range(ts_range, opts) +end + +--- Gets the smallest named node under the cursor +--- +---@param winnr number|nil Window handle or 0 for current window (default) +--- +---@returns (string) Named node under the cursor +function M.get_node_at_cursor(winnr) + 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_position(bufnr, cursor[1] - 1, cursor[2], { ignore_injections = false }) + :type() end --- Start treesitter highlighting for a buffer -- cgit From 75adfefc85bcf0d62d2c0f51a951e6003b595cea Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Mon, 18 Jul 2022 14:21:40 +0200 Subject: feat(extmarks,ts,spell): full support for spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added 'spell' option to extmarks: Extmarks with this set will have the region spellchecked. - Added 'noplainbuffer' option to 'spelloptions': This is used to tell Neovim not to spellcheck the buffer. The old behaviour was to spell check the whole buffer unless :syntax was set. - Added spelling support to the treesitter highlighter: @spell captures in highlights.scm are used to define regions which should be spell checked. - Added support for navigating spell errors for extmarks: Works for both ephemeral and static extmarks - Added '_on_spell_nav' callback for decoration providers: Since ephemeral callbacks are only drawn for the visible screen, providers must implement this callback to instruct Neovim which regions in the buffer need can be spell checked. The callback takes a start position and an end position. Note: this callback is subject to change hence the _ prefix. - Added spell captures for built-in support languages Co-authored-by: Lewis Russell Co-authored-by: Björn Linse --- runtime/lua/vim/treesitter/highlighter.lua | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 1f242b0fdd..9e95af98db 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -97,6 +97,7 @@ function TSHighlighter.new(tree, opts) if vim.g.syntax_on ~= 1 then vim.api.nvim_command('runtime! syntax/synload.vim') end + vim.bo[self.bufnr].spelloptions = 'noplainbuffer' self.tree:parse() @@ -156,7 +157,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line) +local function on_line_impl(self, buf, line, spell) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -193,7 +194,9 @@ local function on_line_impl(self, buf, line) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - if hl and end_row >= line then + local is_spell = highlighter_query:query().captures[capture] == 'spell' + + if hl and end_row >= line and (not spell or is_spell) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, @@ -201,6 +204,7 @@ local function on_line_impl(self, buf, line) ephemeral = true, priority = tonumber(metadata.priority) or 100, -- Low but leaves room below conceal = metadata.conceal, + spell = is_spell, }) end if start_row > line then @@ -217,7 +221,21 @@ function TSHighlighter._on_line(_, _win, buf, line, _) return end - on_line_impl(self, buf, line) + on_line_impl(self, buf, line, false) +end + +---@private +function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) + local self = TSHighlighter.active[buf] + if not self then + return + end + + self:reset_highlight_state() + + for row = srow, erow do + on_line_impl(self, buf, row, true) + end end ---@private @@ -244,6 +262,7 @@ a.nvim_set_decoration_provider(ns, { on_buf = TSHighlighter._on_buf, on_win = TSHighlighter._on_win, on_line = TSHighlighter._on_line, + _on_spell_nav = TSHighlighter._on_spell_nav, }) return TSHighlighter -- cgit From d01cadd82fc74baf7d292c5cbbcf223e0aa2c097 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 6 Sep 2022 17:33:44 +0200 Subject: fix(treesitter): don't support legacy syntax in start() --- runtime/lua/vim/treesitter.lua | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 9c43811e03..5ebccff8f7 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -250,23 +250,22 @@ end --- Can be used in an ftplugin or FileType autocommand --- --- Note: By default, disables regex syntax highlighting, which may be required for some plugins. ---- In this case, add `{ syntax = true }`. +--- In this case, add `vim.bo.syntax = 'on'` after the call to `start`. --- --- Example: --- ---
 --- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
 ---     callback = function(args)
----         vim.treesitter.start(args.buf, 'latex', { syntax = true })
+---         vim.treesitter.start(args.buf, 'latex')
+---         vim.bo[args.buf].syntax = 'on'  -- only if additional legacy syntax is needed
 ---     end
 --- })
 --- 
--- ---@param bufnr number|nil Buffer to be highlighted (default: current buffer) ---@param lang string|nil Language of the parser (default: buffer filetype) ----@param opts table|nil Optional keyword arguments: ---- - `syntax` boolean Run regex syntax highlighting (default false) -function M.start(bufnr, lang, opts) +function M.start(bufnr, lang) bufnr = bufnr or a.nvim_get_current_buf() local parser = M.get_parser(bufnr, lang) @@ -274,10 +273,6 @@ function M.start(bufnr, lang, opts) M.highlighter.new(parser) vim.b[bufnr].ts_highlight = true - - if opts and opts.syntax then - vim.bo[bufnr].syntax = 'on' - end end ---Stop treesitter highlighting for a buffer -- cgit From 707edfc9e6a1745f268d1a9184183da521dc86b8 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 6 Sep 2022 19:22:05 +0100 Subject: fix(ts): do not clobber spelloptions (#20095) --- runtime/lua/vim/treesitter/highlighter.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 9e95af98db..1e625eddb8 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -86,7 +86,7 @@ function TSHighlighter.new(tree, opts) end end - a.nvim_buf_set_option(self.bufnr, 'syntax', '') + vim.bo[self.bufnr].syntax = '' TSHighlighter.active[self.bufnr] = self @@ -95,9 +95,12 @@ function TSHighlighter.new(tree, opts) -- syntax FileType autocmds. Later on we should integrate with the -- `:syntax` and `set syntax=...` machinery properly. if vim.g.syntax_on ~= 1 then - vim.api.nvim_command('runtime! syntax/synload.vim') + vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) end - vim.bo[self.bufnr].spelloptions = 'noplainbuffer' + + a.nvim_buf_call(self.bufnr, function() + vim.opt_local.spelloptions:append('noplainbuffer') + end) self.tree:parse() -- cgit From f32fd19f1eedbd75e6a37b73f28cf8761e0e875c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Wed, 7 Sep 2022 03:55:03 +0100 Subject: fix(diagnostic): remove buf from cache on `BufWipeout` (#20099) Doing so on `BufDelete` has issues: - `BufDelete` is also fired for listed buffers that are made unlisted. - `BufDelete` is not fired for unlisted buffers that are deleted. This means that diagnostics will be lost for a buffer that becomes unlisted. It also means that if an entry exists for an unlisted buffer, deleting that buffer later will not remove its entry from the cache (and you may see "Invalid buffer id" errors when using diagnostic functions if it was wiped). Instead, remove a buffer from the cache if it is wiped out. This means simply `:bd`ing a buffer will not clear its diagnostics now. --- runtime/lua/vim/diagnostic.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index db92085423..4f7d8cccd5 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -47,11 +47,11 @@ local bufnr_and_namespace_cacher_mt = { local diagnostic_cache do - local group = vim.api.nvim_create_augroup('DiagnosticBufDelete', {}) + local group = vim.api.nvim_create_augroup('DiagnosticBufWipeout', {}) diagnostic_cache = setmetatable({}, { __index = function(t, bufnr) assert(bufnr > 0, 'Invalid buffer number') - vim.api.nvim_create_autocmd('BufDelete', { + vim.api.nvim_create_autocmd('BufWipeout', { group = group, buffer = bufnr, callback = function() -- cgit From fd1595514b747d8b083f78007579d869ccfbe89c Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Wed, 7 Sep 2022 08:39:56 +0200 Subject: Use weak tables in tree-sitter code (#17117) feat(treesitter): use weak tables when possible Also add the defaulttable function to create a table whose values are created when a key is missing. --- runtime/lua/vim/shared.lua | 25 +++++++++++++++++++++++++ runtime/lua/vim/treesitter.lua | 5 +---- runtime/lua/vim/treesitter/query.lua | 9 +++------ 3 files changed, 29 insertions(+), 10 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index e1b4ed4ea9..59cb669609 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -715,5 +715,30 @@ function vim.is_callable(f) return type(m.__call) == 'function' end +--- Creates a table whose members are automatically created when accessed, if they don't already +--- exist. +--- +--- They mimic defaultdict in python. +--- +--- If @p create is @c nil, this will create a defaulttable whose constructor function is +--- this function, effectively allowing to create nested tables on the fly: +--- +---
+--- local a = vim.defaulttable()
+--- a.b.c = 1
+--- 
+--- +---@param create function|nil The function called to create a missing value. +---@return table Empty table with metamethod +function vim.defaulttable(create) + create = create or vim.defaulttable + return setmetatable({}, { + __index = function(tbl, key) + rawset(tbl, key, create()) + return rawget(tbl, key) + end, + }) +end + return vim -- vim:sw=2 ts=2 et diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 5ebccff8f7..cde0491b12 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -3,10 +3,7 @@ local query = require('vim.treesitter.query') local language = require('vim.treesitter.language') local LanguageTree = require('vim.treesitter.languagetree') --- TODO(bfredl): currently we retain parsers for the lifetime of the buffer. --- Consider use weak references to release parser if all plugins are done with --- it. -local parsers = {} +local parsers = setmetatable({}, { __mode = 'v' }) local M = vim.tbl_extend('error', query, language) diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 697e2e7691..78042a74bf 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -140,12 +140,9 @@ function M.get_query(lang, query_name) end end -local query_cache = setmetatable({}, { - __index = function(tbl, key) - rawset(tbl, key, {}) - return rawget(tbl, key) - end, -}) +local query_cache = vim.defaulttable(function() + return setmetatable({}, { __mode = 'v' }) +end) --- Parse {query} as a string. (If the query is in a file, the caller --- should read the contents into a string before calling). -- cgit From 9d1d3a67073ab04f29a1e437e90faede764a4313 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 7 Sep 2022 15:55:39 +0200 Subject: vim-patch:9.0.0402: javascript module files are not recoginzed (#20108) Problem: Javascript module files are not recoginzed. Solution: Recognize "*.jsm" files as Javascript. (Brett Holman, closes vim/vim#11069) https://github.com/vim/vim/commit/bb6c4073e79e86ef69c315338e00c12f0d8d6395 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 6306605641..f76b4cced9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -540,6 +540,7 @@ local extension = { mjs = 'javascript', javascript = 'javascript', js = 'javascript', + jsm = 'javascript', cjs = 'javascript', jsx = 'javascriptreact', clp = 'jess', -- cgit From 4dc4cf346755375e49410e16635c00a602b26c36 Mon Sep 17 00:00:00 2001 From: ii14 <59243201+ii14@users.noreply.github.com> Date: Wed, 7 Sep 2022 17:59:27 +0200 Subject: fix(options): mark `winhighlight` as list style (#19477) Also add missing fcs, lcs and winhighlight to list of key-value options for `vim.opt`. Co-authored-by: ii14 --- runtime/lua/vim/_meta.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index f1652718ee..0f45c916dc 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -198,7 +198,10 @@ end -- Can be done in a separate PR. local key_value_options = { fillchars = true, + fcs = true, listchars = true, + lcs = true, + winhighlight = true, winhl = true, } -- cgit From 99e6e0f221ccdb7aa983121359aedb7791e870dd Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Thu, 8 Sep 2022 12:54:41 +0800 Subject: docs(treesitter): fix doxygen --- runtime/lua/vim/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index cde0491b12..69faea7edc 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -247,7 +247,7 @@ end --- Can be used in an ftplugin or FileType autocommand --- --- Note: By default, disables regex syntax highlighting, which may be required for some plugins. ---- In this case, add `vim.bo.syntax = 'on'` after the call to `start`. +--- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`. --- --- Example: --- -- cgit From 0405594399741babd6d935d581eac2584b289f92 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Thu, 8 Sep 2022 09:47:36 +0200 Subject: feat(treesitter)!: do not merge queries by default (#20105) Problem: Treesitter queries for a given language in runtime were merged together, leading to errors if they targeted different parser versions (e.g., bundled viml queries and those shipped by nvim-treesitter). Solution: Runtime queries now work as follows: * The last query in the rtp without `; extends` in the header will be used as the base query * All queries (without a specific order) with `; extends` are concatenated with the base query BREAKING CHANGE: queries need to be updated if they are meant to extend other queries --- runtime/lua/vim/treesitter/query.lua | 37 ++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 78042a74bf..be5b24fd95 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -47,6 +47,9 @@ function M.get_query_files(lang, query_name, is_included) return {} end + local base_query = nil + local extensions = {} + local base_langs = {} -- Now get the base languages by looking at the first line of every file @@ -55,13 +58,26 @@ function M.get_query_files(lang, query_name, is_included) -- -- {language} ::= {lang} | ({lang}) local MODELINE_FORMAT = '^;+%s*inherits%s*:?%s*([a-z_,()]+)%s*$' + local EXTENDS_FORMAT = '^;+%s*extends%s*$' - for _, file in ipairs(lang_files) do - local modeline = safe_read(file, '*l') + for _, filename in ipairs(lang_files) do + local file, err = io.open(filename, 'r') + if not file then + error(err) + end - if modeline then - local langlist = modeline:match(MODELINE_FORMAT) + local extension = false + + for modeline in + function() + return file:read('*l') + end + do + if not vim.startswith(modeline, ';') then + break + end + local langlist = modeline:match(MODELINE_FORMAT) if langlist then for _, incllang in ipairs(vim.split(langlist, ',', true)) do local is_optional = incllang:match('%(.*%)') @@ -74,16 +90,25 @@ function M.get_query_files(lang, query_name, is_included) table.insert(base_langs, incllang) end end + elseif modeline:match(EXTENDS_FORMAT) then + extension = true end end + + if extension then + table.insert(extensions, filename) + else + base_query = filename + end + io.close(file) end - local query_files = {} + local query_files = { base_query } for _, base_lang in ipairs(base_langs) do local base_files = M.get_query_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end - vim.list_extend(query_files, lang_files) + vim.list_extend(query_files, extensions) return query_files end -- cgit From 893b659e88c61a8c3ce5b140ab475cd67e0ca6bc Mon Sep 17 00:00:00 2001 From: bfredl Date: Thu, 8 Sep 2022 11:17:29 +0200 Subject: fix(treesitter): use the right loading order for base queries (#20117) Use the first, not last, query for a language on runtimepath. Typically, this implies that a user query will override a site plugin query, which will override a bundled runtime query. --- runtime/lua/vim/treesitter/query.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index be5b24fd95..2f6227af8e 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -97,7 +97,7 @@ function M.get_query_files(lang, query_name, is_included) if extension then table.insert(extensions, filename) - else + elseif base_query == nil then base_query = filename end io.close(file) -- cgit From 11167ab6d569994dd0a4f58155c84b118706380c Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 8 Sep 2022 11:33:04 +0200 Subject: feat(lsp): add range option to lsp.buf.format (#19998) --- runtime/lua/vim/lsp/buf.lua | 82 ++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 24 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 6a070928d9..2ce565e1d9 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -150,6 +150,33 @@ local function select_client(method, on_choice) end end +---@private +---@return table {start={row, col}, end={row, col}} using (1, 0) indexing +local function range_from_selection() + -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896 + + -- [bufnum, lnum, col, off]; both row and column 1-indexed + local start = vim.fn.getpos('v') + local end_ = vim.fn.getpos('.') + local start_row = start[2] + local start_col = start[3] + local end_row = end_[2] + local end_col = end_[3] + + -- A user can start visual selection at the end and move backwards + -- Normalize the range to start < end + if start_row == end_row and end_col < start_col then + end_col, start_col = start_col, end_col + elseif end_row < start_row then + start_row, end_row = end_row, start_row + start_col, end_col = end_col, start_col + end + return { + ['start'] = { start_row, start_col - 1 }, + ['end'] = { end_row, end_col - 1 }, + } +end + --- Formats a buffer using the attached (and optionally filtered) language --- server clients. --- @@ -184,7 +211,12 @@ end --- Restrict formatting to the client with ID (client.id) matching this field. --- - name (string|nil): --- 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 +--- (1,0) indexing. +--- Defaults to current selection in visual mode +--- Defaults to `nil` in other modes, formatting the full buffer function M.format(options) options = options or {} local bufnr = options.bufnr or api.nvim_get_current_buf() @@ -206,16 +238,32 @@ function M.format(options) vim.notify('[LSP] Format request failed, no matching language servers.') end + 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() + end + + ---@private + local function set_range(client, params) + if range then + local range_params = + util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding) + params.range = range_params.range + end + return params + end + + local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting' if options.async then local do_format do_format = function(idx, client) if not client then return end - local params = util.make_formatting_params(options.formatting_options) - client.request('textDocument/formatting', params, function(...) - local handler = client.handlers['textDocument/formatting'] - or vim.lsp.handlers['textDocument/formatting'] + local params = set_range(client, util.make_formatting_params(options.formatting_options)) + client.request(method, params, function(...) + local handler = client.handlers[method] or vim.lsp.handlers[method] handler(...) do_format(next(clients, idx)) end, bufnr) @@ -224,8 +272,8 @@ function M.format(options) else local timeout_ms = options.timeout_ms or 1000 for _, client in pairs(clients) do - local params = util.make_formatting_params(options.formatting_options) - local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr) + local params = set_range(client, util.make_formatting_params(options.formatting_options)) + local result, err = client.request_sync(method, params, timeout_ms, bufnr) if result and result.result then util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then @@ -356,6 +404,7 @@ end ---@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) + vim.deprecate('vim.lsp.buf.range_formatting', 'vim.lsp.formatexpr or vim.lsp.format', '0.9.0') local params = util.make_given_range_params(start_pos, end_pos) params.options = util.make_formatting_params(options).options select_client('textDocument/rangeFormatting', function(client) @@ -885,23 +934,8 @@ 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 - -- [bufnum, lnum, col, off]; both row and column 1-indexed - local start = vim.fn.getpos('v') - local end_ = vim.fn.getpos('.') - local start_row = start[2] - local start_col = start[3] - local end_row = end_[2] - local end_col = end_[3] - - -- A user can start visual selection at the end and move backwards - -- Normalize the range to start < end - if start_row == end_row and end_col < start_col then - end_col, start_col = start_col, end_col - elseif end_row < start_row then - start_row, end_row = end_row, start_row - start_col, end_col = end_col, start_col - end - params = util.make_given_range_params({ start_row, start_col - 1 }, { end_row, end_col - 1 }) + local range = range_from_selection() + params = util.make_given_range_params(range.start, range['end']) else params = util.make_range_params() end -- cgit From 08602ec1ab2674385ddda8feaef2d3d9360d834d Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Thu, 8 Sep 2022 16:06:00 +0200 Subject: vim-patch:9.0.0417: Jsonnet files are not recognized (#20119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: Jsonnet files are not recognized. Solution: Add a pattern for Jsonnet files. (Cezary Drożak, closes vim/vim#11073, closes vim/vim#11081) https://github.com/vim/vim/commit/2a4c885d54171f68ec2c2d6eb4ae281c7fefb802 --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index f76b4cced9..fc2bcdabd2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -557,6 +557,8 @@ local extension = { ['json-patch'] = 'json', json5 = 'json5', jsonc = 'jsonc', + jsonnet = 'jsonnet', + libjsonnet = 'jsonnet', jsp = 'jsp', jl = 'julia', kv = 'kivy', -- cgit From bc88691dbd2f5352d8793cac952bf90eea6442b2 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 6 Sep 2022 22:51:32 +0100 Subject: refactor(vim.opt): remove del arg --- runtime/lua/vim/_meta.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 0f45c916dc..5dbadd3ff4 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -43,7 +43,7 @@ local function _setup() win_options = get_scoped_options('win') end -local function make_meta_accessor(get, set, del, validator) +local function make_meta_accessor(get, set, validator) validator = validator or function() return true end @@ -51,7 +51,6 @@ local function make_meta_accessor(get, set, del, validator) validate({ get = { get, 'f' }, set = { set, 'f' }, - del = { del, 'f', true }, validator = { validator, 'f' }, }) @@ -61,9 +60,6 @@ local function make_meta_accessor(get, set, del, validator) return end - if del and v == nil then - return del(k) - end return set(k, v) end function mt:__index(k) @@ -98,7 +94,7 @@ do -- buffer option accessor return a.nvim_set_option_value(k, v, { buf = bufnr or 0 }) end - return make_meta_accessor(get, set, nil, function(k) + return make_meta_accessor(get, set, function(k) if type(k) == 'string' then _setup() if win_options[k] then @@ -132,7 +128,7 @@ do -- window option accessor return a.nvim_set_option_value(k, v, { win = winnr or 0 }) end - return make_meta_accessor(get, set, nil, function(k) + return make_meta_accessor(get, set, function(k) if type(k) == 'string' then _setup() if buf_options[k] then -- cgit From f21e2a51ba43a06d929ca372bf5cfce40d4e4331 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 6 Sep 2022 22:56:55 +0100 Subject: refactor(vim.opt): remove enums --- runtime/lua/vim/_meta.lua | 124 +++++++++++++++++----------------------------- 1 file changed, 45 insertions(+), 79 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 5dbadd3ff4..fb3a1e72e9 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -4,12 +4,6 @@ local vim = assert(vim) local a = vim.api local validate = vim.validate -local SET_TYPES = setmetatable({ - SET = 0, - LOCAL = 1, - GLOBAL = 2, -}, { __index = error }) - local options_info = nil local buf_options = nil local glb_options = nil @@ -201,51 +195,30 @@ local key_value_options = { winhl = true, } ----@class OptionTypes ---- Option Type Enum -local OptionTypes = setmetatable({ - BOOLEAN = 0, - NUMBER = 1, - STRING = 2, - ARRAY = 3, - MAP = 4, - SET = 5, -}, { - __index = function(_, k) - error('Not a valid OptionType: ' .. k) - end, - __newindex = function(_, k) - error('Cannot set a new OptionType: ' .. k) - end, -}) - --- Convert a vimoption_T style dictionary to the correct OptionType associated with it. ----@return OptionType +---@return string local get_option_type = function(name, info) if info.type == 'boolean' then - return OptionTypes.BOOLEAN + return 'boolean' elseif info.type == 'number' then - return OptionTypes.NUMBER + return 'number' elseif info.type == 'string' then if not info.commalist and not info.flaglist then - return OptionTypes.STRING + return 'string' end if key_value_options[name] then assert(info.commalist, 'Must be a comma list to use key:value style') - return OptionTypes.MAP + return 'map' end if info.flaglist then - return OptionTypes.SET + return 'set' elseif info.commalist then - return OptionTypes.ARRAY + return 'array' end - - error('Fallthrough in OptionTypes') - else - error('Not a known info.type:' .. info.type) end + error('Not a known info.type:' .. info.type) end -- Check whether the OptionTypes is allowed for vim.opt @@ -269,12 +242,12 @@ local function assert_valid_value(name, value, types) end local valid_types = { - [OptionTypes.BOOLEAN] = { 'boolean' }, - [OptionTypes.NUMBER] = { 'number' }, - [OptionTypes.STRING] = { 'string' }, - [OptionTypes.SET] = { 'string', 'table' }, - [OptionTypes.ARRAY] = { 'string', 'table' }, - [OptionTypes.MAP] = { 'string', 'table' }, + boolean = { 'boolean' }, + number = { 'number' }, + string = { 'string' }, + set = { 'string', 'table' }, + array = { 'string', 'table' }, + map = { 'string', 'table' }, } --- Convert a lua value to a vimoption_T value @@ -282,17 +255,17 @@ local convert_value_to_vim = (function() -- 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 = { - [OptionTypes.BOOLEAN] = function(_, value) + boolean = function(_, value) return value end, - [OptionTypes.NUMBER] = function(_, value) + number = function(_, value) return value end, - [OptionTypes.STRING] = function(_, value) + string = function(_, value) return value end, - [OptionTypes.SET] = function(info, value) + set = function(info, value) if type(value) == 'string' then return value end @@ -319,7 +292,7 @@ local convert_value_to_vim = (function() end end, - [OptionTypes.ARRAY] = function(info, value) + array = function(info, value) if type(value) == 'string' then return value end @@ -329,7 +302,7 @@ local convert_value_to_vim = (function() return table.concat(value, ',') end, - [OptionTypes.MAP] = function(_, value) + map = function(_, value) if type(value) == 'string' then return value end @@ -361,17 +334,17 @@ local convert_value_to_lua = (function() -- 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 = { - [OptionTypes.BOOLEAN] = function(_, value) + boolean = function(_, value) return value end, - [OptionTypes.NUMBER] = function(_, value) + number = function(_, value) return value end, - [OptionTypes.STRING] = function(_, value) + string = function(_, value) return value end, - [OptionTypes.ARRAY] = function(info, value) + array = function(info, value) if type(value) == 'table' then if not info.allows_duplicates then value = remove_duplicate_values(value) @@ -420,7 +393,7 @@ local convert_value_to_lua = (function() return vim.split(value, ',') end, - [OptionTypes.SET] = function(info, value) + set = function(info, value) if type(value) == 'table' then return value end @@ -451,7 +424,7 @@ local convert_value_to_lua = (function() end end, - [OptionTypes.MAP] = function(info, raw_value) + map = function(info, raw_value) if type(raw_value) == 'table' then return raw_value end @@ -485,15 +458,15 @@ end --- Handles the '^' operator local prepend_value = (function() local methods = { - [OptionTypes.NUMBER] = function() + number = function() error("The '^' operator is not currently supported for") end, - [OptionTypes.STRING] = function(left, right) + string = function(left, right) return right .. left end, - [OptionTypes.ARRAY] = function(left, right) + array = function(left, right) for i = #right, 1, -1 do table.insert(left, 1, right[i]) end @@ -501,11 +474,11 @@ local prepend_value = (function() return left end, - [OptionTypes.MAP] = function(left, right) + map = function(left, right) return vim.tbl_extend('force', left, right) end, - [OptionTypes.SET] = function(left, right) + set = function(left, right) return vim.tbl_extend('force', left, right) end, } @@ -524,15 +497,15 @@ end)() --- Handles the '+' operator local add_value = (function() local methods = { - [OptionTypes.NUMBER] = function(left, right) + number = function(left, right) return left + right end, - [OptionTypes.STRING] = function(left, right) + string = function(left, right) return left .. right end, - [OptionTypes.ARRAY] = function(left, right) + array = function(left, right) for _, v in ipairs(right) do table.insert(left, v) end @@ -540,11 +513,11 @@ local add_value = (function() return left end, - [OptionTypes.MAP] = function(left, right) + map = function(left, right) return vim.tbl_extend('force', left, right) end, - [OptionTypes.SET] = function(left, right) + set = function(left, right) return vim.tbl_extend('force', left, right) end, } @@ -580,15 +553,15 @@ local remove_value = (function() end local methods = { - [OptionTypes.NUMBER] = function(left, right) + number = function(left, right) return left - right end, - [OptionTypes.STRING] = function() + string = function() error('Subtraction not supported for strings.') end, - [OptionTypes.ARRAY] = function(left, right) + array = function(left, right) if type(right) == 'string' then remove_one_item(left, right) else @@ -600,7 +573,7 @@ local remove_value = (function() return left end, - [OptionTypes.MAP] = function(left, right) + map = function(left, right) if type(right) == 'string' then left[right] = nil else @@ -612,7 +585,7 @@ local remove_value = (function() return left end, - [OptionTypes.SET] = function(left, right) + set = function(left, right) if type(right) == 'string' then left[right] = nil else @@ -630,7 +603,7 @@ local remove_value = (function() end end)() -local create_option_metatable = function(set_type) +local create_option_metatable = function(scope) local set_mt, option_mt local make_option = function(name, value) @@ -650,13 +623,6 @@ local create_option_metatable = function(set_type) }, option_mt) end - local scope - if set_type == SET_TYPES.GLOBAL then - scope = 'global' - elseif set_type == SET_TYPES.LOCAL then - scope = 'local' - end - option_mt = { -- To set a value, instead use: -- opt[my_option] = value @@ -711,6 +677,6 @@ local create_option_metatable = function(set_type) return set_mt end -vim.opt = setmetatable({}, create_option_metatable(SET_TYPES.SET)) -vim.opt_local = setmetatable({}, create_option_metatable(SET_TYPES.LOCAL)) -vim.opt_global = setmetatable({}, create_option_metatable(SET_TYPES.GLOBAL)) +vim.opt = setmetatable({}, create_option_metatable()) +vim.opt_local = setmetatable({}, create_option_metatable('local')) +vim.opt_global = setmetatable({}, create_option_metatable('global')) -- cgit From b8de5ada80f7cc2cda3dc55defeb635ee39f3a24 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 15:42:17 +0100 Subject: refactor(vim.opt): replace _setup with lazy table --- runtime/lua/vim/_meta.lua | 154 +++++++++++++++++++--------------------------- 1 file changed, 63 insertions(+), 91 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index fb3a1e72e9..9e10e5ccf5 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -4,39 +4,52 @@ local vim = assert(vim) local a = vim.api local validate = vim.validate -local options_info = nil -local buf_options = nil -local glb_options = nil -local win_options = nil - -local function _setup() - if options_info ~= nil then - return - end - options_info = {} - for _, v in pairs(a.nvim_get_all_options_info()) do - options_info[v.name] = v - if v.shortname ~= '' then - options_info[v.shortname] = v +-- 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 == 'boolean' then + return 'boolean' + elseif info.type == 'number' then + return 'number' + elseif info.type == 'string' then + if not info.commalist and not info.flaglist then + return 'string' end - end - local function get_scoped_options(scope) - local result = {} - for name, option_info in pairs(options_info) do - if option_info.scope == scope then - result[name] = true - end + if key_value_options[name] then + assert(info.commalist, 'Must be a comma list to use key:value style') + return 'map' end - return result + if info.flaglist then + return 'set' + elseif info.commalist then + return 'array' + end end - - buf_options = get_scoped_options('buf') - glb_options = get_scoped_options('global') - win_options = get_scoped_options('win') + error('Not a known info.type:' .. info.type) end +local options_info = setmetatable({}, { + __index = function(t, k) + local info = a.nvim_get_option_info(k) + info.metatype = get_option_metatype(k, info) + rawset(t, k, info) + return rawget(t, k) + end, +}) + local function make_meta_accessor(get, set, validator) validator = validator or function() return true @@ -90,12 +103,12 @@ do -- buffer option accessor return make_meta_accessor(get, set, function(k) if type(k) == 'string' then - _setup() - if win_options[k] then + local scope = options_info[k].scope + if scope == 'win' then error( string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k) ) - elseif glb_options[k] then + elseif scope == 'global' then error( string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k) ) @@ -124,12 +137,12 @@ do -- window option accessor return make_meta_accessor(get, set, function(k) if type(k) == 'string' then - _setup() - if buf_options[k] then + local scope = options_info[k].scope + if scope == 'buf' then error( string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k) ) - elseif glb_options[k] then + elseif scope == 'global' then error( string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k) ) @@ -184,43 +197,6 @@ local remove_duplicate_values = function(t) return result end --- 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 get_option_type = function(name, info) - if info.type == 'boolean' then - return 'boolean' - elseif info.type == 'number' then - return 'number' - elseif info.type == 'string' then - if not info.commalist and not info.flaglist then - return 'string' - end - - if key_value_options[name] then - assert(info.commalist, 'Must be a comma list to use key:value style') - return 'map' - end - - if info.flaglist then - return 'set' - elseif info.commalist then - return 'array' - end - end - error('Not a known info.type:' .. info.type) -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) @@ -322,10 +298,9 @@ local convert_value_to_vim = (function() return vim.NIL end - local option_type = get_option_type(name, info) - assert_valid_value(name, value, valid_types[option_type]) + assert_valid_value(name, value, valid_types[info.metatype]) - return to_vim_value[option_type](info, value) + return to_vim_value[info.metatype](info, value) end end)() @@ -445,14 +420,14 @@ local convert_value_to_lua = (function() end, } - return function(name, info, option_value) - return to_lua_value[get_option_type(name, info)](info, option_value) + return function(info, option_value) + return to_lua_value[info.metatype](info, option_value) end end)() --- Handles the mutation of various different values. -local value_mutator = function(name, info, current, new, mutator) - return mutator[get_option_type(name, info)](current, new) +local value_mutator = function(info, current, new, mutator) + return mutator[info.metatype](current, new) end --- Handles the '^' operator @@ -483,12 +458,11 @@ local prepend_value = (function() end, } - return function(name, info, current, new) + return function(info, current, new) return value_mutator( - name, info, - convert_value_to_lua(name, info, current), - convert_value_to_lua(name, info, new), + convert_value_to_lua(info, current), + convert_value_to_lua(info, new), methods ) end @@ -522,12 +496,11 @@ local add_value = (function() end, } - return function(name, info, current, new) + return function(info, current, new) return value_mutator( - name, info, - convert_value_to_lua(name, info, current), - convert_value_to_lua(name, info, new), + convert_value_to_lua(info, current), + convert_value_to_lua(info, new), methods ) end @@ -598,8 +571,8 @@ local remove_value = (function() end, } - return function(name, info, current, new) - return value_mutator(name, info, convert_value_to_lua(name, info, current), new, methods) + return function(info, current, new) + return value_mutator(info, convert_value_to_lua(info, current), new, methods) end end)() @@ -607,7 +580,6 @@ local create_option_metatable = function(scope) local set_mt, option_mt local make_option = function(name, value) - _setup() local info = assert(options_info[name], 'Not a valid option name: ' .. name) if type(value) == 'table' and getmetatable(value) == option_mt then @@ -634,7 +606,7 @@ local create_option_metatable = function(scope) end, get = function(self) - return convert_value_to_lua(self._name, self._info, self._value) + return convert_value_to_lua(self._info, self._value) end, append = function(self, right) @@ -642,7 +614,7 @@ local create_option_metatable = function(scope) end, __add = function(self, right) - return make_option(self._name, add_value(self._name, self._info, self._value, right)) + return make_option(self._name, add_value(self._info, self._value, right)) end, prepend = function(self, right) @@ -650,7 +622,7 @@ local create_option_metatable = function(scope) end, __pow = function(self, right) - return make_option(self._name, prepend_value(self._name, self._info, self._value, right)) + return make_option(self._name, prepend_value(self._info, self._value, right)) end, remove = function(self, right) @@ -658,7 +630,7 @@ local create_option_metatable = function(scope) end, __sub = function(self, right) - return make_option(self._name, remove_value(self._name, self._info, self._value, right)) + return make_option(self._name, remove_value(self._info, self._value, right)) end, } option_mt.__index = option_mt -- cgit From 514a1679dcd7444c7a1b66612b3313fb249d310a Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 15:44:00 +0100 Subject: refactor(vim.opt): simplify get_option_metatype --- runtime/lua/vim/_meta.lua | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 9e10e5ccf5..66b55a3d47 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -18,27 +18,18 @@ local key_value_options = { --- 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 == 'boolean' then - return 'boolean' - elseif info.type == 'number' then - return 'number' - elseif info.type == 'string' then - if not info.commalist and not info.flaglist then - return 'string' - end - - if key_value_options[name] then - assert(info.commalist, 'Must be a comma list to use key:value style') - return 'map' - end - + 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 - error('Not a known info.type:' .. info.type) + return info.type end local options_info = setmetatable({}, { -- cgit From 30ca6d23a9c77175a76a4cd59da81de83d9253af Mon Sep 17 00:00:00 2001 From: Raphael Date: Thu, 8 Sep 2022 23:09:32 +0800 Subject: fix(lsp): when buffer detach remove buffer from client attached buffers (#20081) Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1dc1a045fd..051127c9c6 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1644,6 +1644,7 @@ function lsp.buf_attach_client(bufnr, client_id) if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then client.notify('textDocument/didClose', params) end + client.attached_buffers[bufnr] = nil end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil -- cgit From 164752b38074e9d71493c304dc3b8c672aac397d Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 15:52:39 +0100 Subject: refactor(vim.opt): remove make_meta_accessor() --- runtime/lua/vim/_meta.lua | 171 +++++++++++++++++++++------------------------- 1 file changed, 77 insertions(+), 94 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 66b55a3d47..93077c89d6 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -2,7 +2,6 @@ local vim = assert(vim) local a = vim.api -local validate = vim.validate -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. -- Can be done in a separate PR. @@ -41,107 +40,85 @@ local options_info = setmetatable({}, { end, }) -local function make_meta_accessor(get, set, validator) - validator = validator or function() - return true - end - - validate({ - get = { get, 'f' }, - set = { set, 'f' }, - validator = { validator, 'f' }, - }) - - local mt = {} - function mt:__newindex(k, v) - if not validator(k) then - return - end - - return set(k, v) - end - function mt:__index(k) - if not validator(k) then - return +vim.env = setmetatable({}, { + __index = function(_, k) + local v = vim.fn.getenv(k) + if v == vim.NIL then + return nil end + return v + end, - return get(k) - end - return setmetatable({}, mt) -end + __newindex = function(_, k, v) + vim.fn.setenv(k, v) + end, +}) -vim.env = make_meta_accessor(function(k) - local v = vim.fn.getenv(k) - if v == vim.NIL then - return nil +do -- buffer option accessor + local function buf_opt_validate(k) + local scope = options_info[k].scope + if scope == 'win' then + error( + string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k) + ) + elseif scope == 'global' then + error( + string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k) + ) + end end - return v -end, vim.fn.setenv) -do -- buffer option accessor local function new_buf_opt_accessor(bufnr) - local function get(k) - if bufnr == nil and type(k) == 'number' then - return new_buf_opt_accessor(k) - end - - return a.nvim_get_option_value(k, { buf = bufnr or 0 }) - end + return setmetatable({},{ + __index = function(_, k) + if bufnr == nil and type(k) == 'number' then + return new_buf_opt_accessor(k) + end + buf_opt_validate(k) - local function set(k, v) - return a.nvim_set_option_value(k, v, { buf = bufnr or 0 }) - end + return a.nvim_get_option_value(k, { buf = bufnr or 0 }) + end, - return make_meta_accessor(get, set, function(k) - if type(k) == 'string' then - local scope = options_info[k].scope - if scope == 'win' then - error( - string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k) - ) - elseif scope == 'global' then - error( - string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k) - ) - end - end + __newindex = function(_, k, v) + buf_opt_validate(k) + return a.nvim_set_option_value(k, v, { buf = bufnr or 0 }) + end, + }) - return true - end) end vim.bo = new_buf_opt_accessor(nil) end do -- window option accessor - local function new_win_opt_accessor(winnr) - local function get(k) - if winnr == nil and type(k) == 'number' then - return new_win_opt_accessor(k) - end - return a.nvim_get_option_value(k, { win = winnr or 0 }) - end - - local function set(k, v) - return a.nvim_set_option_value(k, v, { win = winnr or 0 }) + local function win_opt_validate(k) + local scope = options_info[k].scope + if scope == 'buf' then + error( + string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k) + ) + elseif scope == 'global' then + error( + string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k) + ) end + end - return make_meta_accessor(get, set, function(k) - if type(k) == 'string' then - local scope = options_info[k].scope - if scope == 'buf' then - error( - string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k) - ) - elseif scope == 'global' then - error( - string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k) - ) + local function new_win_opt_accessor(winnr) + return setmetatable({}, { + __index = function(_, k) + if winnr == nil and type(k) == 'number' then + return new_win_opt_accessor(k) end - end - - return true - end) + win_opt_validate(k) + return a.nvim_get_option_value(k, { win = winnr or 0 }) + end, + + __newindex = function(_, k, v) + win_opt_validate(k) + return a.nvim_set_option_value(k, v, { win = winnr or 0 }) + end, + }) end vim.wo = new_win_opt_accessor(nil) @@ -149,19 +126,25 @@ end -- vim global option -- this ONLY sets the global option. like `setglobal` -vim.go = make_meta_accessor(function(k) - return a.nvim_get_option_value(k, { scope = 'global' }) -end, function(k, v) - return a.nvim_set_option_value(k, v, { scope = 'global' }) -end) +vim.go = setmetatable({}, { + __index = function(_, k) + return a.nvim_get_option_value(k, { scope = 'global' }) + end, + __newindex = function(_, k, v) + return a.nvim_set_option_value(k, v, { scope = 'global' }) + end, +}) -- vim `set` style options. -- it has no additional metamethod magic. -vim.o = make_meta_accessor(function(k) - return a.nvim_get_option_value(k, {}) -end, function(k, v) - return a.nvim_set_option_value(k, v, {}) -end) +vim.o = setmetatable({}, { + __index = function(_, k) + return a.nvim_get_option_value(k, {}) + end, + __newindex = function(_, k, v) + return a.nvim_set_option_value(k, v, {}) + end, +}) ---@brief [[ --- vim.opt, vim.opt_local and vim.opt_global implementation -- cgit From 19a3b2c26e28382a65529a38be9ff63ca58cc023 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Thu, 8 Sep 2022 19:25:16 +0200 Subject: docs(lsp): update rpc.start stdio limitations (#20120) --- runtime/lua/vim/lsp/rpc.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 70f838f34d..7d047f8958 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -635,7 +635,8 @@ local function connect(host, port) end --- Starts an LSP server process and create an LSP RPC client object to ---- interact with it. Communication with the server is currently limited to stdio. +--- interact with it. Communication with the spawned process happens via stdio. For +--- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect| --- ---@param cmd (string) Command to start the LSP server. ---@param cmd_args (table) List of additional string arguments to pass to {cmd}. -- cgit From 7533ceec13a1dd9a1e46a523975bddf52f533a93 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 15:56:35 +0100 Subject: refactor(vim.opt): unify vim.bo/wo building --- runtime/lua/vim/_meta.lua | 92 +++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 63 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 93077c89d6..f78c2da581 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -54,76 +54,42 @@ vim.env = setmetatable({}, { end, }) -do -- buffer option accessor - local function buf_opt_validate(k) - local scope = options_info[k].scope - if scope == 'win' then - error( - string.format([['%s' is a window option, not a buffer option. See ":help %s"]], k, k) +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 ) - elseif scope == 'global' then - error( - string.format([['%s' is a global option, not a buffer option. See ":help %s"]], k, k) - ) - 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 - buf_opt_validate(k) - - return a.nvim_get_option_value(k, { buf = bufnr or 0 }) - end, - - __newindex = function(_, k, v) - buf_opt_validate(k) - return a.nvim_set_option_value(k, v, { buf = bufnr or 0 }) - end, - }) - + ) end - - vim.bo = new_buf_opt_accessor(nil) end -do -- window option accessor - local function win_opt_validate(k) - local scope = options_info[k].scope - if scope == 'buf' then - error( - string.format([['%s' is a buffer option, not a window option. See ":help %s"]], k, k) - ) - elseif scope == 'global' then - error( - string.format([['%s' is a global option, not a window option. See ":help %s"]], k, k) - ) - end - end - - local function new_win_opt_accessor(winnr) - return setmetatable({}, { - __index = function(_, k) - if winnr == nil and type(k) == 'number' then - return new_win_opt_accessor(k) - end - win_opt_validate(k) - return a.nvim_get_option_value(k, { win = winnr or 0 }) - end, - - __newindex = function(_, k, v) - win_opt_validate(k) - return a.nvim_set_option_value(k, v, { win = winnr or 0 }) - end, - }) - end +local function new_opt_accessor(handle, scope) + return setmetatable({}, { + __index = function(_, k) + if handle == nil and type(k) == 'number' then + return new_opt_accessor(k, scope) + end + opt_validate(k, scope) + return a.nvim_get_option_value(k, { [scope] = handle or 0 }) + end, - vim.wo = new_win_opt_accessor(nil) + __newindex = function(_, k, v) + opt_validate(k, scope) + return a.nvim_set_option_value(k, v, { [scope] = handle or 0 }) + end, + }) end +vim.bo = new_opt_accessor(nil, 'buf') +vim.wo = new_opt_accessor(nil, 'win') + -- vim global option -- this ONLY sets the global option. like `setglobal` vim.go = setmetatable({}, { -- cgit From 925a8119902ebe63343374f9edc57b4ea6038d08 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 16:44:18 +0100 Subject: refactor(vim.opt): remove value_mutator() --- runtime/lua/vim/_meta.lua | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index f78c2da581..9daef780f4 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -365,11 +365,6 @@ local convert_value_to_lua = (function() end end)() ---- Handles the mutation of various different values. -local value_mutator = function(info, current, new, mutator) - return mutator[info.metatype](current, new) -end - --- Handles the '^' operator local prepend_value = (function() local methods = { @@ -399,11 +394,9 @@ local prepend_value = (function() } return function(info, current, new) - return value_mutator( - info, + methods[info.metatype]( convert_value_to_lua(info, current), - convert_value_to_lua(info, new), - methods + convert_value_to_lua(info, new) ) end end)() @@ -437,11 +430,9 @@ local add_value = (function() } return function(info, current, new) - return value_mutator( - info, + methods[info.metatype]( convert_value_to_lua(info, current), - convert_value_to_lua(info, new), - methods + convert_value_to_lua(info, new) ) end end)() @@ -512,7 +503,7 @@ local remove_value = (function() } return function(info, current, new) - return value_mutator(info, convert_value_to_lua(info, current), new, methods) + return methods[info.metatype](convert_value_to_lua(info, current), new) end end)() -- cgit From b364bc2c340e4d56ca34960bbd73e2774e7f235c Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 16:54:34 +0100 Subject: refactor(vim.opt): dry up and tidy --- runtime/lua/vim/_meta.lua | 117 +++++++++++++++++----------------------------- 1 file changed, 42 insertions(+), 75 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 9daef780f4..cb02174e71 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -123,9 +123,6 @@ vim.o = setmetatable({}, { --- Preserves the order and does not mutate the original list local remove_duplicate_values = function(t) local result, seen = {}, {} - if type(t) == 'function' then - error(debug.traceback('asdf')) - end for _, v in ipairs(t) do if not seen[v] then table.insert(result, v) @@ -157,6 +154,26 @@ local function assert_valid_value(name, value, types) ) 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' }, @@ -171,15 +188,9 @@ local convert_value_to_vim = (function() -- 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 = function(_, value) - return value - end, - number = function(_, value) - return value - end, - string = function(_, value) - return value - end, + boolean = passthrough, + number = passthrough, + string = passthrough, set = function(info, value) if type(value) == 'string' then @@ -249,15 +260,9 @@ local convert_value_to_lua = (function() -- 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 = function(_, value) - return value - end, - number = function(_, value) - return value - end, - string = function(_, value) - return value - end, + boolean = passthrough, + number = passthrough, + string = passthrough, array = function(info, value) if type(value) == 'table' then @@ -276,9 +281,7 @@ local convert_value_to_lua = (function() -- Handles unescaped commas in a list. if string.find(value, ',,,') then - local comma_split = vim.split(value, ',,,') - local left = comma_split[1] - local right = comma_split[2] + local left, right = unpack(vim.split(value, ',,,')) local result = {} vim.list_extend(result, vim.split(left, ',')) @@ -291,9 +294,7 @@ local convert_value_to_lua = (function() end if string.find(value, ',^,,', 1, true) then - local comma_split = vim.split(value, ',^,,', true) - local left = comma_split[1] - local right = comma_split[2] + local left, right = unpack(vim.split(value, ',^,,', true)) local result = {} vim.list_extend(result, vim.split(left, ',')) @@ -384,13 +385,8 @@ local prepend_value = (function() return left end, - map = function(left, right) - return vim.tbl_extend('force', left, right) - end, - - set = function(left, right) - return vim.tbl_extend('force', left, right) - end, + map = tbl_merge, + set = tbl_merge, } return function(info, current, new) @@ -420,13 +416,8 @@ local add_value = (function() return left end, - map = function(left, right) - return vim.tbl_extend('force', left, right) - end, - - set = function(left, right) - return vim.tbl_extend('force', left, right) - end, + map = tbl_merge, + set = tbl_merge, } return function(info, current, new) @@ -477,29 +468,8 @@ local remove_value = (function() return left end, - map = function(left, right) - if type(right) == 'string' then - left[right] = nil - else - for _, v in ipairs(right) do - left[v] = nil - end - end - - return left - end, - - set = function(left, right) - if type(right) == 'string' then - left[right] = nil - else - for _, v in ipairs(right) do - left[v] = nil - end - end - - return left - end, + map = tbl_remove, + set = tbl_remove, } return function(info, current, new) @@ -507,8 +477,8 @@ local remove_value = (function() end end)() -local create_option_metatable = function(scope) - local set_mt, option_mt +local create_option_accessor = function(scope) + local option_mt local make_option = function(name, value) local info = assert(options_info[name], 'Not a valid option name: ' .. name) @@ -566,20 +536,17 @@ local create_option_metatable = function(scope) } option_mt.__index = option_mt - set_mt = { + return setmetatable({}, { __index = function(_, k) return make_option(k, a.nvim_get_option_value(k, { scope = scope })) end, __newindex = function(_, k, v) - local opt = make_option(k, v) - opt:_set() + make_option(k, v):_set() end, - } - - return set_mt + }) end -vim.opt = setmetatable({}, create_option_metatable()) -vim.opt_local = setmetatable({}, create_option_metatable('local')) -vim.opt_global = setmetatable({}, create_option_metatable('global')) +vim.opt = create_option_accessor() +vim.opt_local = create_option_accessor('local') +vim.opt_global = create_option_accessor('global') -- cgit From ad972990ad7ee59f0be7d267b58ca880c9ccaa7b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 16:57:58 +0100 Subject: refactor(vim.opt): optimize append/prepend/remove --- runtime/lua/vim/_meta.lua | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index cb02174e71..7ec132fbb5 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -502,8 +502,6 @@ local create_option_accessor = function(scope) _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 }) - - return self end, get = function(self) @@ -511,7 +509,8 @@ local create_option_accessor = function(scope) end, append = function(self, right) - return self:__add(right):_set() + self._value = add_value(self._info, self._value, right) + self:_set() end, __add = function(self, right) @@ -519,7 +518,8 @@ local create_option_accessor = function(scope) end, prepend = function(self, right) - return self:__pow(right):_set() + self._value = prepend_value(self._info, self._value, right) + self:_set() end, __pow = function(self, right) @@ -527,7 +527,8 @@ local create_option_accessor = function(scope) end, remove = function(self, right) - return self:__sub(right):_set() + self._value = remove_value(self._info, self._value, right) + self:_set() end, __sub = function(self, right) -- cgit From 9272d20ea48ea0beb1ecefc0331344a1a3e4b05e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 16:59:09 +0100 Subject: refactor(vim.opt): use local function syntax --- runtime/lua/vim/_meta.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 7ec132fbb5..fc5df0116d 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -121,7 +121,7 @@ vim.o = setmetatable({}, { ---@brief ]] --- Preserves the order and does not mutate the original list -local remove_duplicate_values = function(t) +local function remove_duplicate_values(t) local result, seen = {}, {} for _, v in ipairs(t) do if not seen[v] then @@ -430,7 +430,7 @@ end)() --- Handles the '-' operator local remove_value = (function() - local remove_one_item = function(t, val) + local function remove_one_item(t, val) if vim.tbl_islist(t) then local remove_index = nil for i, v in ipairs(t) do @@ -477,10 +477,10 @@ local remove_value = (function() end end)() -local create_option_accessor = function(scope) +local function create_option_accessor(scope) local option_mt - local make_option = function(name, value) + 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 -- cgit From 038c7115393c931b29c451eb1434d283c74ba55e Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Thu, 8 Sep 2022 17:09:44 +0100 Subject: refactor(vim.opt): de-nest code --- runtime/lua/vim/_meta.lua | 446 ++++++++++++++++++++++------------------------ 1 file changed, 218 insertions(+), 228 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index fc5df0116d..dada918d69 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -183,299 +183,289 @@ local valid_types = { map = { 'string', 'table' }, } ---- Convert a lua value to a vimoption_T value -local convert_value_to_vim = (function() - -- 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 +-- 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 - table.sort(keys) - return table.concat(keys, ',') - else - local result = '' - for k, v in pairs(value) do - if v then - result = result .. k - 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 - - 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) + 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 table.concat(value, ',') - end, - map = function(_, value) - if type(value) == 'string' then - return value - end + return result + end + end, - local result = {} - for opt_key, opt_value in pairs(value) do - table.insert(result, string.format('%s:%s', opt_key, opt_value)) - 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, - table.sort(result) - return table.concat(result, ',') - end, - } + map = function(_, value) + if type(value) == 'string' then + return value + end - return function(name, info, value) - if value == nil then - return vim.NIL + local result = {} + for opt_key, opt_value in pairs(value) do + table.insert(result, string.format('%s:%s', opt_key, opt_value)) end - assert_valid_value(name, value, valid_types[info.metatype]) + table.sort(result) + return table.concat(result, ',') + end, +} - return to_vim_value[info.metatype](info, 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 end -end)() ---- Converts a vimoption_T style value to a Lua value -local convert_value_to_lua = (function() - -- 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 + assert_valid_value(name, value, valid_types[info.metatype]) - return value - end + 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, - -- Empty strings mean that there is nothing there, - -- so empty table should be returned. - if value == '' then - return {} + array = function(info, value) + if type(value) == 'table' then + if not info.allows_duplicates then + value = remove_duplicate_values(value) end - -- Handles unescaped commas in a list. - if string.find(value, ',,,') then - local left, right = unpack(vim.split(value, ',,,')) + return value + end - local result = {} - vim.list_extend(result, vim.split(left, ',')) - table.insert(result, ',') - vim.list_extend(result, vim.split(right, ',')) + -- Empty strings mean that there is nothing there, + -- so empty table should be returned. + if value == '' then + return {} + end - table.sort(result) + -- Handles unescaped commas in a list. + if string.find(value, ',,,') then + local left, right = unpack(vim.split(value, ',,,')) - return result - end + local result = {} + vim.list_extend(result, vim.split(left, ',')) + table.insert(result, ',') + vim.list_extend(result, vim.split(right, ',')) - if string.find(value, ',^,,', 1, true) then - local left, right = unpack(vim.split(value, ',^,,', true)) + table.sort(result) - local result = {} - vim.list_extend(result, vim.split(left, ',')) - table.insert(result, '^,') - vim.list_extend(result, vim.split(right, ',')) + return result + end - table.sort(result) + if string.find(value, ',^,,', 1, true) then + local left, right = unpack(vim.split(value, ',^,,', true)) - return result - end + local result = {} + vim.list_extend(result, vim.split(left, ',')) + table.insert(result, '^,') + vim.list_extend(result, vim.split(right, ',')) - return vim.split(value, ',') - end, + table.sort(result) - set = function(info, value) - if type(value) == 'table' then - return value - end + return result + end - -- Empty strings mean that there is nothing there, - -- so empty table should be returned. - if value == '' then - return {} - end + return vim.split(value, ',') + end, - assert(info.flaglist, 'That is the only one I know how to handle') + set = function(info, value) + if type(value) == 'table' then + return value + end - 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 + -- Empty strings mean that there is nothing there, + -- so empty table should be returned. + if value == '' then + return {} + end - return result - else - local result = {} - for i = 1, #value do - result[value:sub(i, i)] = true - end + assert(info.flaglist, 'That is the only one I know how to handle') - return result + 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 - end, - map = function(info, raw_value) - if type(raw_value) == 'table' then - return raw_value + return result + else + local result = {} + for i = 1, #value do + result[value:sub(i, i)] = true end - assert(info.commalist, 'Only commas are supported currently') + return result + end + end, - local result = {} + map = function(info, raw_value) + if type(raw_value) == 'table' then + return raw_value + end - 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) + assert(info.commalist, 'Only commas are supported currently') - result[key] = value - end + local result = {} - return result - end, - } + 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) - return function(info, option_value) - return to_lua_value[info.metatype](info, option_value) - end -end)() + result[key] = value + end ---- Handles the '^' operator -local prepend_value = (function() - local methods = { - number = function() - error("The '^' operator is not currently supported for") - end, + return result + end, +} - string = function(left, right) - return right .. left - 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 - array = function(left, right) - for i = #right, 1, -1 do - table.insert(left, 1, right[i]) - end +local prepend_methods = { + number = function() + error("The '^' operator is not currently supported for") + end, - return left - end, + string = function(left, right) + return right .. left + end, - map = tbl_merge, - set = tbl_merge, - } + array = function(left, right) + for i = #right, 1, -1 do + table.insert(left, 1, right[i]) + end - return function(info, current, new) - methods[info.metatype]( - convert_value_to_lua(info, current), - convert_value_to_lua(info, new) - ) - end -end)() + return left + end, ---- Handles the '+' operator -local add_value = (function() - local methods = { - number = function(left, right) - return left + right - end, + map = tbl_merge, + set = tbl_merge, +} - string = function(left, right) - return left .. right - end, +--- 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 - array = function(left, right) - for _, v in ipairs(right) do - table.insert(left, v) - end +local add_methods = { + number = function(left, right) + return left + right + end, - return left - end, + string = function(left, right) + return left .. right + end, - map = tbl_merge, - set = tbl_merge, - } + array = function(left, right) + for _, v in ipairs(right) do + table.insert(left, v) + end - return function(info, current, new) - methods[info.metatype]( - convert_value_to_lua(info, current), - convert_value_to_lua(info, new) - ) - end -end)() + return left + end, ---- Handles the '-' operator -local remove_value = (function() - 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 + map = tbl_merge, + set = tbl_merge, +} - if remove_index then - table.remove(t, remove_index) +--- 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 - else - t[val] = nil end + + if remove_index then + table.remove(t, remove_index) + end + else + t[val] = nil end +end - local methods = { - number = function(left, right) - return left - right - end, +local remove_methods = { + number = function(left, right) + return left - right + end, - string = function() - error('Subtraction not supported for strings.') - 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 + 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, + return left + end, - map = tbl_remove, - set = tbl_remove, - } + map = tbl_remove, + set = tbl_remove, +} - return function(info, current, new) - return methods[info.metatype](convert_value_to_lua(info, current), new) - end -end)() +--- 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 -- cgit From ad2d6a624b10a52cdcfb7fdd9d8b1be24b13ed83 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 9 Sep 2022 17:53:15 +0200 Subject: vim-patch:9.0.0424: gitattributes files are not recognized (#20134) Problem: gitattributes files are not recognized. Solution: Add patterns to match gitattributes files. (closes vim/vim#11085) https://github.com/vim/vim/commit/7d56cfc861e57145f003315efd835cf5dfd5b145 --- runtime/lua/vim/filetype.lua | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index fc2bcdabd2..12e6fa837b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1383,6 +1383,7 @@ local filename = { ['EDIT_DESCRIPTION'] = 'gitcommit', ['.gitconfig'] = 'gitconfig', ['.gitmodules'] = 'gitconfig', + ['.gitattributes'] = 'gitattributes', ['gitolite.conf'] = 'gitolite', ['git-rebase-todo'] = 'gitrebase', gkrellmrc = 'gkrellmrc', @@ -1825,6 +1826,14 @@ local pattern = { return 'gitconfig' end end, + ['.*%.git/info/attributes'] = 'gitattributes', + ['.*/etc/gitattributes'] = 'gitattributes', + ['.*/%.config/git/attributes'] = 'gitattributes', + ['.*/git/attributes'] = function(path, bufnr) + if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/attributes') then + return 'gitattributes' + end + end, ['%.gitsendemail%.msg%.......'] = 'gitsendemail', ['gkrellmrc_.'] = 'gkrellmrc', ['.*/usr/.*/gnupg/options%.skel'] = 'gpg', -- cgit From 9b0e1256e25d387bf65cb9baa1edd99fbc128724 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 9 Sep 2022 18:48:12 +0200 Subject: vim-patch:9.0.0427: Drupal theme files are not recognized (#20138) Problem: Drupal theme files are not recognized. Solution: Use php filetype for Drupl theme files. Remove trailing spaces. (Rodrigo Aguilera, closes vim/vim#11096) https://github.com/vim/vim/commit/8995c4cd4e697141faf74da9a87e0c1221bfb161 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 12e6fa837b..6aa765ebaf 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -746,6 +746,7 @@ local extension = { php = 'php', phpt = 'php', phtml = 'php', + theme = 'php', pike = 'pike', pmod = 'pike', rcp = 'pilrc', -- cgit From 40f9f479b746d0f76fbdd4bc0567d593ca7a6070 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 10 Sep 2022 13:30:54 +0200 Subject: vim-patch:9.0.0434: gitignore files are not recognized (#20143) Problem: gitignore files are not recognized. Solution: Add patterns for the gitignore filetype. (closes vim/vim#11102) https://github.com/vim/vim/commit/9ba2786f15f0b53a90fd221832a5bedfc6dbfe20 --- runtime/lua/vim/filetype.lua | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 6aa765ebaf..c8777801ec 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1385,6 +1385,7 @@ local filename = { ['.gitconfig'] = 'gitconfig', ['.gitmodules'] = 'gitconfig', ['.gitattributes'] = 'gitattributes', + ['.gitignore'] = 'gitignore', ['gitolite.conf'] = 'gitolite', ['git-rebase-todo'] = 'gitrebase', gkrellmrc = 'gkrellmrc', @@ -1835,6 +1836,13 @@ local pattern = { return 'gitattributes' end end, + ['.*%.git/info/exclude'] = 'gitignore', + ['.*/%.config/git/ignore'] = 'gitignore', + ['.*/git/ignore'] = function(path, bufnr) + if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/ignore') then + return 'gitignore' + end + end, ['%.gitsendemail%.msg%.......'] = 'gitsendemail', ['gkrellmrc_.'] = 'gkrellmrc', ['.*/usr/.*/gnupg/options%.skel'] = 'gpg', -- cgit From 9b4cab012662514af6fda3648d544633e1d73d4b Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Sat, 10 Sep 2022 18:56:29 -0600 Subject: fix(lsp): schedule removal of client object (#20148) The execution of the LspDetach autocommands in the LSP client's on_exit function are scheduled on the event loop to avoid making API calls in a fast context; however, this means that by the time the LspDetach autocommands finally run the client object has already been deleted. To address this, we also schedule the deletion of the client on the event loop so that it is guaranteed to occur after all of the LspDetach autocommands have fired. --- runtime/lua/vim/lsp.lua | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 051127c9c6..22933d8143 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -1147,33 +1147,34 @@ function lsp.start_client(config) local namespace = vim.lsp.diagnostic.get_namespace(client_id) vim.diagnostic.reset(namespace, bufnr) - end) - client_ids[client_id] = nil - end - if vim.tbl_isempty(client_ids) then - vim.schedule(function() - unset_defaults(bufnr) + client_ids[client_id] = nil + if vim.tbl_isempty(client_ids) then + unset_defaults(bufnr) + end end) end end - 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 - -- Client can be absent if executable starts, but initialize fails - -- init/attach won't have happened - if client then - changetracking.reset(client) - end - if code ~= 0 or (signal ~= 0 and signal ~= 15) then - local msg = - string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal) - vim.schedule(function() + -- 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 + + -- Client can be absent if executable starts, but initialize fails + -- init/attach won't have happened + if client then + changetracking.reset(client) + end + if code ~= 0 or (signal ~= 0 and signal ~= 15) then + local msg = + string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal) vim.notify(msg, vim.log.levels.WARN) - end) - end + end + end) end -- Start the RPC client. -- cgit From 1939518ebab72878f2a8ca0cb85c09f7e70d1093 Mon Sep 17 00:00:00 2001 From: Thomas Vigouroux Date: Sat, 10 Sep 2022 11:26:23 +0200 Subject: fix(treesitter): prevent endless loop on self-inheritence Fixes #20139 --- runtime/lua/vim/treesitter/query.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 2f6227af8e..90ed2a357c 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -34,6 +34,18 @@ local function safe_read(filename, read_quantifier) return content end +---@private +--- Adds @p ilang to @p base_langs, only if @p ilang is different than @lang +--- +---@return boolean true it lang == ilang +local function add_included_lang(base_langs, lang, ilang) + if lang == ilang then + return true + end + table.insert(base_langs, ilang) + return false +end + --- Gets the list of files used to make up a query --- ---@param lang The language @@ -84,10 +96,14 @@ function M.get_query_files(lang, query_name, is_included) if is_optional then if not is_included then - table.insert(base_langs, incllang:sub(2, #incllang - 1)) + if add_included_lang(base_langs, lang, incllang:sub(2, #incllang - 1)) then + extension = true + end end else - table.insert(base_langs, incllang) + if add_included_lang(base_langs, lang, incllang) then + extension = true + end end end elseif modeline:match(EXTENDS_FORMAT) then -- cgit From f98cff9575e75a050d2bde01ad950c0c72bcfc3e Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 11 Sep 2022 16:07:54 +0200 Subject: vim-patch:9.0.0443: blueprint files are not recognized (#20155) Problem: Blueprint files are not recognized. Solution: Add a pattern for blueprint files. (Gabriele Musco, closes vim/vim#11107) https://github.com/vim/vim/commit/cce82a55b8105560a2ef724999c856966337b48e --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c8777801ec..e4be1f04a2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -177,6 +177,7 @@ local extension = { bbappend = 'bitbake', bbclass = 'bitbake', bl = 'blank', + blp = 'blueprint', bsd = 'bsdl', bsdl = 'bsdl', bst = 'bst', -- cgit From afe01842ef37620e63cab815b84c89454a6b4a87 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 12 Sep 2022 15:12:39 +0200 Subject: vim-patch:9.0.0448: SubRip files are not recognized (#20167) Problem: SubRip files are not recognized. Solution: Add a pattern for SubRip. (closes vim/vim#11113) https://github.com/vim/vim/commit/5a4eb55122e45444d3a6c56ce108ce29bc8e52ab --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e4be1f04a2..39985c948e 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -959,6 +959,7 @@ local extension = { srec = 'srec', mot = 'srec', ['s19'] = 'srec', + srt = 'srt', st = 'st', imata = 'stata', ['do'] = 'stata', -- cgit From 1970d2ac43059639e4e2c83223d86397b38786ff Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Tue, 13 Sep 2022 08:33:39 -0600 Subject: feat(diagnostic): pass diagnostics as data to DiagnosticChanged autocmd (#20173) --- runtime/lua/vim/diagnostic.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 4f7d8cccd5..172bd867c7 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -727,6 +727,7 @@ function M.set(namespace, bufnr, diagnostics, opts) vim.api.nvim_exec_autocmds('DiagnosticChanged', { modeline = false, buffer = bufnr, + data = { diagnostics = diagnostics }, }) end -- cgit From a8c9e721d91efe4730db78c1115261fc128dca68 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 13 Sep 2022 22:16:20 +0200 Subject: feat(fs): extend fs.find to accept predicate (#20193) Makes it possible to use `vim.fs.find` to find files where only a substring is known. This is useful for `vim.lsp.start` to get the `root_dir` for languages where the project-file is only known by its extension, not by the full name. For example in .NET projects there is usually a `.csproj` file in the project root. Example: vim.fs.find(function(x) return vim.endswith(x, '.csproj') end, { upward = true }) --- runtime/lua/vim/fs.lua | 56 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 16 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index ce845eda15..7bd635d8b6 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -76,8 +76,11 @@ end --- The search can be narrowed to find only files or or only directories by --- specifying {type} to be "file" or "directory", respectively. --- ----@param names (string|table) Names of the files and directories to find. Must ---- be base names, paths and globs are not supported. +---@param names (string|table|fun(name: string): boolean) Names of the files +--- and directories to find. +--- Must be base names, paths and globs are not supported. +--- If a function it is called per file and dir within the +--- traversed directories to test if they match. ---@param opts (table) Optional keyword arguments: --- - path (string): Path to begin searching from. If --- omitted, the current working directory is used. @@ -98,7 +101,7 @@ end function M.find(names, opts) opts = opts or {} vim.validate({ - names = { names, { 's', 't' } }, + names = { names, { 's', 't', 'f' } }, path = { opts.path, 's', true }, upward = { opts.upward, 'b', true }, stop = { opts.stop, 's', true }, @@ -123,18 +126,31 @@ function M.find(names, opts) end if opts.upward then - ---@private - local function test(p) - local t = {} - for _, name in ipairs(names) do - local f = 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 + local test + + if type(names) == 'function' then + 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 + table.insert(t, p .. '/' .. name) + end end + return t end + else + test = function(p) + local t = {} + for _, name in ipairs(names) do + local f = 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 + end + end - return t + return t + end end for _, match in ipairs(test(path)) do @@ -162,17 +178,25 @@ function M.find(names, opts) break end - for other, type in M.dir(dir) do + for other, type_ in M.dir(dir) do local f = dir .. '/' .. other - for _, name in ipairs(names) do - if name == other and (not opts.type or opts.type == type) then + if type(names) == 'function' then + if names(other) and (not opts.type or opts.type == type_) then if add(f) then return matches end end + else + for _, name in ipairs(names) do + if name == other and (not opts.type or opts.type == type_) then + if add(f) then + return matches + end + end + end end - if type == 'directory' then + if type_ == 'directory' then dirs[#dirs + 1] = f end end -- cgit From ddb762f4013ac2532ad45704466058d867e3a6ed Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Wed, 14 Sep 2022 11:08:31 +0200 Subject: docs(treesitter): clean up and update treesitter.txt (#20142) * add type annotations to code * clean up and expand static documentation * consistent use of tags for static and generated docs --- runtime/lua/vim/shared.lua | 2 +- runtime/lua/vim/treesitter.lua | 85 ++++++++++---------- runtime/lua/vim/treesitter/health.lua | 2 +- runtime/lua/vim/treesitter/highlighter.lua | 14 ++-- runtime/lua/vim/treesitter/language.lua | 16 ++-- runtime/lua/vim/treesitter/languagetree.lua | 115 +++++++++++++++++----------- runtime/lua/vim/treesitter/query.lua | 102 ++++++++++++------------ 7 files changed, 187 insertions(+), 149 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 59cb669609..de5f7240aa 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -720,7 +720,7 @@ end --- --- They mimic defaultdict in python. --- ---- If @p create is @c nil, this will create a defaulttable whose constructor function is +--- If {create} is `nil`, this will create a defaulttable whose constructor function is --- this function, effectively allowing to create nested tables on the fly: --- ---
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index d93c485dfe..89aa611acd 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -25,15 +25,15 @@ setmetatable(M, {
   end,
 })
 
---- Creates a new parser.
+--- Creates a new parser
 ---
---- It is not recommended to use this, use vim.treesitter.get_parser() instead.
+--- 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 lang string Language of the parser
----@param opts table|nil Options to pass to the created language tree
+---@param opts (table|nil) Options to pass to the created language tree
 ---
----@returns table Created parser object
+---@return LanguageTree |LanguageTree| object to use for parsing
 function M._create_parser(bufnr, lang, opts)
   language.require_language(lang)
   if bufnr == 0 then
@@ -73,16 +73,15 @@ function M._create_parser(bufnr, lang, opts)
   return self
 end
 
---- Gets the parser for this bufnr / ft combination.
+--- Returns the parser for a specific buffer and filetype and attaches it to the buffer
 ---
---- If needed this will create the parser.
---- Unconditionally attach the provided callback
+--- If needed, this will create the parser.
 ---
----@param bufnr number|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
+---@param bufnr (number|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
 ---
----@returns table Parser object
+---@return LanguageTree |LanguageTree| object to use for parsing
 function M.get_parser(bufnr, lang, opts)
   opts = opts or {}
 
@@ -102,11 +101,13 @@ function M.get_parser(bufnr, lang, opts)
   return parsers[bufnr]
 end
 
---- Gets a string parser
+--- Returns a string parser
 ---
----@param str The string to parse
----@param lang The language of this string
----@param opts Options to pass to the created language tree
+---@param str string Text to parse
+---@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
 function M.get_string_parser(str, lang, opts)
   vim.validate({
     str = { str, 'string' },
@@ -119,10 +120,10 @@ end
 
 --- Determines whether a node is the ancestor of another
 ---
----@param dest table Possible ancestor
----@param source table Possible descendant node
+---@param dest userdata Possible ancestor |tsnode|
+---@param source userdata Possible descendant |tsnode|
 ---
----@returns (boolean) True if dest is an ancestor of source
+---@return boolean True if {dest} is an ancestor of {source}
 function M.is_ancestor(dest, source)
   if not (dest and source) then
     return false
@@ -140,11 +141,11 @@ function M.is_ancestor(dest, source)
   return false
 end
 
---- Get the node's range or unpack a range table
+--- Returns the node's range or an unpacked range table
 ---
----@param node_or_range table
+---@param node_or_range (userdata|table) |tsnode| or table of positions
 ---
----@returns table start_row, start_col, end_row, end_col
+---@return table `{ start_row, start_col, end_row, end_col }`
 function M.get_node_range(node_or_range)
   if type(node_or_range) == 'table' then
     return unpack(node_or_range)
@@ -155,11 +156,11 @@ end
 
 ---Determines whether (line, col) position is in node range
 ---
----@param node table Node defining the range
+---@param node userdata |tsnode| defining the range
 ---@param line number Line (0-based)
 ---@param col number Column (0-based)
 ---
----@returns (boolean) True if the position is in node range
+---@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
@@ -178,10 +179,10 @@ function M.is_in_node_range(node, line, col)
 end
 
 ---Determines if a node contains a range
----@param node table
+---@param node userdata |tsnode|
 ---@param range table
 ---
----@returns (boolean) True if the node contains the range
+---@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])
@@ -190,7 +191,8 @@ function M.node_contains(node, range)
   return start_fits and end_fits
 end
 
----Gets a list of captures for a given cursor position
+---Returns a list of highlight captures at the given position
+--
 ---@param bufnr number Buffer number (0 for current buffer)
 ---@param row number Position row
 ---@param col number Position column
@@ -199,7 +201,7 @@ end
 ---@param row number Position row
 ---@param col number Position column
 ---
----@returns (table) Table of captures
+---@return table[] Captures of the form `{ capture = "capture name", priority = capture priority }`
 function M.get_captures_at_position(bufnr, row, col)
   if bufnr == 0 then
     bufnr = a.nvim_get_current_buf()
@@ -246,11 +248,11 @@ function M.get_captures_at_position(bufnr, row, col)
   return matches
 end
 
----Gets a list of captures under the cursor
+---Returns a list of highlight capture names under the cursor
 ---
----@param winnr number|nil Window handle or 0 for current window (default)
+---@param winnr (number|nil) Window handle or 0 for current window (default)
 ---
----@returns (table) Named node under the cursor
+---@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)
@@ -267,7 +269,7 @@ function M.get_captures_at_cursor(winnr)
   return captures
 end
 
---- Gets the smallest named node at position
+--- Returns the smallest named node at the given position
 ---
 ---@param bufnr number Buffer number (0 for current buffer)
 ---@param row number Position row
@@ -275,7 +277,7 @@ end
 ---@param opts table Optional keyword arguments:
 ---             - ignore_injections boolean Ignore injected languages (default true)
 ---
----@returns (table) Named node under the cursor
+---@return userdata |tsnode| under the cursor
 function M.get_node_at_position(bufnr, row, col, opts)
   if bufnr == 0 then
     bufnr = a.nvim_get_current_buf()
@@ -290,11 +292,11 @@ function M.get_node_at_position(bufnr, row, col, opts)
   return root_lang_tree:named_node_for_range(ts_range, opts)
 end
 
---- Gets the smallest named node under the cursor
+--- Returns the smallest named node under the cursor
 ---
----@param winnr number|nil Window handle or 0 for current window (default)
+---@param winnr (number|nil) Window handle or 0 for current window (default)
 ---
----@returns (string) Named node under the cursor
+---@return string Name of node under the cursor
 function M.get_node_at_cursor(winnr)
   winnr = winnr or 0
   local bufnr = a.nvim_win_get_buf(winnr)
@@ -304,15 +306,14 @@ function M.get_node_at_cursor(winnr)
     :type()
 end
 
---- Start treesitter highlighting for a buffer
+--- Starts treesitter highlighting for a buffer
 ---
---- Can be used in an ftplugin or FileType autocommand
+--- Can be used in an ftplugin or FileType autocommand.
 ---
 --- Note: By default, disables regex syntax highlighting, which may be required for some plugins.
 --- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`.
 ---
 --- Example:
----
 --- 
 --- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
 ---     callback = function(args)
@@ -322,8 +323,8 @@ end
 --- })
 --- 
--- ----@param bufnr number|nil Buffer to be highlighted (default: current buffer) ----@param lang string|nil Language of the parser (default: buffer filetype) +---@param bufnr (number|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() @@ -334,9 +335,9 @@ function M.start(bufnr, lang) vim.b[bufnr].ts_highlight = true end ----Stop treesitter highlighting for a buffer +--- Stops treesitter highlighting for a buffer --- ----@param bufnr number|nil Buffer to stop highlighting (default: current buffer) +---@param bufnr (number|nil) Buffer to stop highlighting (default: current buffer) function M.stop(bufnr) bufnr = bufnr or a.nvim_get_current_buf() diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 3bd59ca282..4995c80a02 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -3,7 +3,7 @@ local ts = vim.treesitter --- Lists the parsers currently installed --- ----@return A list of parsers +---@return string[] list of parser files function M.list_parsers() return vim.api.nvim_get_runtime_file('parser/*', true) end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 1e625eddb8..83a26aff13 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -2,6 +2,7 @@ local a = vim.api local query = require('vim.treesitter.query') -- support reload for quick experimentation +---@class TSHighlighter local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} TSHighlighter.__index = TSHighlighter @@ -45,9 +46,10 @@ end --- Creates a new highlighter using @param tree --- ----@param tree The language tree to use for highlighting ----@param opts Table used to configure the highlighter ---- - queries: Table to overwrite queries used by the highlighter +---@param tree LanguageTree |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 function TSHighlighter.new(tree, opts) local self = setmetatable({}, TSHighlighter) @@ -149,8 +151,10 @@ function TSHighlighter:on_changedtree(changes) end --- Gets the query used for @param lang ---- ----@param lang A language used by the highlighter. +-- +---@private +---@param lang string Language used by the highlighter. +---@return Query function TSHighlighter:get_query(lang) if not self._queries[lang] then self._queries[lang] = TSHighlighterQuery.new(lang) diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index d14b825603..c92d63b8c4 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -2,14 +2,15 @@ local a = vim.api local M = {} ---- Asserts that the provided language is installed, and optionally provide a path for the parser +--- Asserts that a parser for the language {lang} is installed. --- ---- Parsers are searched in the `parser` runtime directory. +--- Parsers are searched in the `parser` runtime directory, or the provided {path} --- ----@param lang string The language the parser should parse ----@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 lang string Language the parser should parse +---@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 +---@return boolean If the specified language is installed function M.require_language(lang, path, silent, symbol_name) if vim._ts_has_language(lang) then return true @@ -42,7 +43,8 @@ end --- --- Inspecting provides some useful information on the language like node names, ... --- ----@param lang The language. +---@param lang string Language +---@return table function M.inspect_language(lang) M.require_language(lang) return vim._ts_inspect_language(lang) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 70317a9f94..e9d70c4204 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -2,19 +2,35 @@ 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 + local LanguageTree = {} LanguageTree.__index = LanguageTree ---- Represents a single treesitter parser for a language. ---- The language can contain child languages with in its range, ---- hence the tree. +--- 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). --- ----@param source Can be a bufnr or a string of text to parse ----@param lang The language this tree represents ----@param opts Options table ----@param opts.injections A table of language to injection query strings. ---- This is useful for overriding the built-in runtime file ---- searching for the injection language query per language. +---@param source (number|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 function LanguageTree.new(source, lang, opts) language.require_language(lang) opts = opts or {} @@ -94,6 +110,9 @@ end --- for the language this tree represents. --- 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 function LanguageTree:parse() if self._valid then return self._trees @@ -167,10 +186,10 @@ function LanguageTree:parse() return self._trees, changes end ---- Invokes the callback for each LanguageTree and it's children recursively +--- Invokes the callback for each |LanguageTree| and its children recursively --- ----@param fn The function to invoke. This is invoked with arguments (tree: LanguageTree, lang: string) ----@param include_self Whether to include the invoking tree in the results. +---@param fn function(tree: LanguageTree, lang: string) +---@param include_self boolean 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) @@ -181,12 +200,11 @@ function LanguageTree:for_each_child(fn, include_self) end end ---- Invokes the callback for each treesitter trees recursively. +--- Invokes the callback for each |LanguageTree| recursively. --- ---- Note, this includes the invoking language tree's trees as well. +--- Note: This includes the invoking tree's child trees as well. --- ----@param fn The callback to invoke. The callback is invoked with arguments ---- (tree: TSTree, languageTree: LanguageTree) +---@param fn function(tree: TSTree, languageTree: LanguageTree) function LanguageTree:for_each_tree(fn) for _, tree in ipairs(self._trees) do fn(tree, self) @@ -197,11 +215,13 @@ function LanguageTree:for_each_tree(fn) end end ---- Adds a child language to this tree. +--- Adds a child language to this |LanguageTree|. --- --- If the language already exists as a child, it will first be removed. --- ----@param lang The language to add. +---@private +---@param lang string Language to add. +---@return LanguageTree Injected |LanguageTree| function LanguageTree:add_child(lang) if self._children[lang] then self:remove_child(lang) @@ -215,9 +235,10 @@ function LanguageTree:add_child(lang) return self._children[lang] end ---- Removes a child language from this tree. +--- Removes a child language from this |LanguageTree|. --- ----@param lang The language to remove. +---@private +---@param lang string Language to remove. function LanguageTree:remove_child(lang) local child = self._children[lang] @@ -229,12 +250,11 @@ function LanguageTree:remove_child(lang) end end ---- Destroys this language tree and all its children. +--- Destroys this |LanguageTree| and all its children. --- --- Any cleanup logic should be performed here. --- ---- Note: ---- This DOES NOT remove this tree from a parent. Instead, +--- Note: This DOES NOT remove this tree from a parent. Instead, --- `remove_child` must be called on the parent to remove it. function LanguageTree:destroy() -- Cleanup here @@ -243,23 +263,24 @@ function LanguageTree:destroy() end end ---- Sets the included regions that should be parsed by this parser. +--- 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. --- ---- For example, `{ { node1 }, { node2} }` is two separate regions. ---- This will be parsed by the parser in two different contexts... thus resulting +--- For example, `{ { node1 }, { node2} }` contains two separate regions. +--- They will be parsed by the parser in two different contexts, thus resulting --- in two separate trees. --- ---- `{ { node1, node2 } }` is a single region consisting of two nodes. ---- This will be parsed by the parser in a single context... thus resulting +--- On the other hand, `{ { node1, node2 } }` is a single region consisting of +--- two nodes. This will be parsed by the parser in a single context, thus resulting --- in a single tree. --- --- 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. +--- Note: This call invalidates the tree and requires it to be parsed again. --- ----@param regions (table) list of regions this tree should manage and parse. +---@private +---@param regions table 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 @@ -288,7 +309,7 @@ function LanguageTree:set_included_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 - -- effects injected languages. + -- affects injected languages. self._trees = {} self:invalidate() end @@ -493,8 +514,8 @@ function LanguageTree:_on_detach(...) self:_do_callback('detach', ...) end ---- Registers callbacks for the parser. ----@param cbs table An |nvim_buf_attach()|-like table argument with the following keys : +--- Registers callbacks for the |LanguageTree|. +---@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers: --- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback. --- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes. --- It will only be passed one argument, which is a table of the ranges (as node ranges) that @@ -536,9 +557,10 @@ local function tree_contains(tree, range) return start_fits and end_fits end ---- Determines whether {range} is contained in this language tree +--- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range A range, that is a `{ start_line, start_col, end_line, end_col }` table. +---@param range table `{ start_line, start_col, end_line, end_col }` +---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do if tree_contains(tree, range) then @@ -549,11 +571,12 @@ function LanguageTree:contains(range) return false end ---- Gets the tree that contains {range} +--- Gets the tree that contains {range}. --- ----@param range table A text range ----@param opts table Options table ----@param opts.ignore_injections boolean (default true) Ignore injected languages. +---@param range table `{ 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| function LanguageTree:tree_for_range(range, opts) opts = opts or {} local ignore = vim.F.if_nil(opts.ignore_injections, true) @@ -577,19 +600,21 @@ function LanguageTree:tree_for_range(range, opts) return nil end ---- Gets the smallest named node that contains {range} +--- Gets the smallest named node that contains {range}. --- ----@param range table A text range ----@param opts table Options table ----@param opts.ignore_injections boolean (default true) Ignore injected languages. +---@param range table `{ 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| function LanguageTree:named_node_for_range(range, opts) local tree = self:tree_for_range(range, opts) return tree:root():named_descendant_for_range(unpack(range)) end ---- Gets the appropriate language that contains {range} +--- Gets the appropriate language that contains {range}. --- ----@param range A text range, see |LanguageTree:contains| +---@param range table `{ 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 if child:contains(range) then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 90ed2a357c..d1dc29969b 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -3,6 +3,11 @@ 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 query userdata Parsed query local Query = {} Query.__index = Query @@ -35,9 +40,9 @@ local function safe_read(filename, read_quantifier) end ---@private ---- Adds @p ilang to @p base_langs, only if @p ilang is different than @lang +--- Adds {ilang} to {base_langs}, only if {ilang} is different than {lang} --- ----@return boolean true it lang == ilang +---@return boolean true If lang == ilang local function add_included_lang(base_langs, lang, ilang) if lang == ilang then return true @@ -48,9 +53,10 @@ end --- Gets the list of files used to make up a query --- ----@param lang The language ----@param query_name The name of the query to load ----@param is_included Internal parameter, most of the time left as `nil` +---@param lang string Language to get query for +---@param query_name string Name of the query to load (e.g., 'highlights') +---@param is_included (boolean|nil) Internal parameter, most of the time left as `nil` +---@return string[] query_files List of files to load for given query and language function M.get_query_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)) @@ -150,24 +156,24 @@ local explicit_queries = setmetatable({}, { end, }) ---- Sets the runtime query {query_name} for {lang} +--- Sets the runtime query named {query_name} for {lang} --- --- This allows users to override any runtime files and/or configuration --- set by plugins. --- ----@param lang string: The language to use for the query ----@param query_name string: The name of the query (i.e. "highlights") ----@param text string: The query text (unparsed). +---@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) end --- Returns the runtime query {query_name} for {lang}. --- ----@param lang The language to use for the query ----@param query_name The name of the query (i.e. "highlights") +---@param lang string Language to use for the query +---@param query_name string Name of the query (e.g. 'highlights') --- ----@return The corresponding query, parsed. +---@return Query Parsed query function M.get_query(lang, query_name) if explicit_queries[lang][query_name] then return explicit_queries[lang][query_name] @@ -198,10 +204,10 @@ end) --- -` info.captures` also points to `captures`. --- - `info.patterns` contains information about predicates. --- ----@param lang string The language ----@param query string A string containing the query (s-expr syntax) +---@param lang string Language to use for the query +---@param query string Query in s-expr syntax --- ----@returns The query +---@return Query Parsed query function M.parse_query(lang, query) language.require_language(lang) local cached = query_cache[lang][query] @@ -219,10 +225,11 @@ end --- Gets the text corresponding to a given node --- ----@param node table The node ----@param source table The buffer or string from which the node is extracted ----@param opts table Optional parameters. ---- - concat: (boolean default true) Concatenate result in a string +---@param node userdata |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) function M.get_node_text(node, source, opts) opts = opts or {} local concat = vim.F.if_nil(opts.concat, true) @@ -410,9 +417,8 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ----@param name the name of the predicate, without leading # ----@param handler the handler function to be used ---- signature will be (match, pattern, bufnr, predicate) +---@param name string Name of the predicate, without leading # +---@param handler function(match:string, pattern:string, bufnr:number, predicate:function) function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -428,9 +434,8 @@ end --- can set node level data by using the capture id on the --- metadata table `metadata[capture_id].key = value` --- ----@param name the name of the directive, without leading # ----@param handler the handler function to be used ---- signature will be (match, pattern, bufnr, predicate, metadata) +---@param name string Name of the directive, without leading # +---@param handler function(match:string, pattern:string, bufnr:number, predicate:function, metadata:table) function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -440,12 +445,13 @@ function M.add_directive(name, handler, force) end --- Lists the currently available directives to use in queries. ----@return The list of supported directives. +---@return string[] List of supported directives. function M.list_directives() return vim.tbl_keys(directive_handlers) end ----@return The list of supported predicates. +--- Lists the currently available predicates to use in queries. +---@return string[] List of supported predicates. function M.list_predicates() return vim.tbl_keys(predicate_handlers) end @@ -532,17 +538,16 @@ end --- Iterate over all captures from all matches inside {node} --- ---- {source} is needed if the query contains predicates, then the caller +--- {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 --- 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. +--- 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. --- ---- The iterator returns three values, a numeric id identifying the capture, +--- The iterator returns three values: a numeric id identifying the capture, --- the captured node, and metadata from any directives processing the match. --- The following example shows how to get captures by name: ---- ---
 --- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
 ---   local name = query.captures[id] -- name of the capture in the query
@@ -553,13 +558,14 @@ end
 --- end
 --- 
--- ----@param node The node under which the search will occur ----@param source The source buffer or string to extract text from ----@param start The starting line of the search ----@param stop The stopping line of the search (end-exclusive) +---@param node userdata |tsnode| under which the search will occur +---@param source (number|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) --- ----@returns The matching capture id ----@returns The captured node +---@return number capture Matching capture id +---@return table capture_node Capture for {node} +---@return table metadata for the {capture} function Query:iter_captures(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() @@ -589,14 +595,13 @@ end --- Iterates the matches of self on a given range. --- ---- Iterate over all matches within a node. The arguments are the same as +--- Iterate over all matches within a {node}. The arguments are the same as --- for |query:iter_captures()| but the iterated values are different: --- an (1-based) index of the pattern in the query, a table mapping --- capture indices to nodes, and metadata from any directives processing the match. ---- If the query has more than one pattern the capture table might be sparse, +--- If the query has more than one pattern, the capture table might be sparse --- and e.g. `pairs()` method should be used over `ipairs`. ---- Here an example iterating over all captures in every match: ---- +--- Here is an example iterating over all captures in every match: ---
 --- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
 ---   for id, node in pairs(match) do
@@ -610,13 +615,14 @@ end
 --- end
 --- 
--- ----@param node The node under which the search will occur ----@param source The source buffer or string to search ----@param start The starting line of the search ----@param stop The stopping line of the search (end-exclusive) +---@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) --- ----@returns The matching pattern id ----@returns The matching match +---@return number pattern id +---@return table match +---@return table metadata function Query:iter_matches(node, source, start, stop) if type(source) == 'number' and source == 0 then source = vim.api.nvim_get_current_buf() -- cgit From 982fef6018fb64c883ddafc897c8f7c58fb1c62d Mon Sep 17 00:00:00 2001 From: Gregory Anders <8965202+gpanders@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:03:07 -0600 Subject: fix(diagnostic): populate data key in DiagnosticChanged autocmd in reset (#20207) Follow up to #20173. --- runtime/lua/vim/diagnostic.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 172bd867c7..98dbe0779b 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -1426,6 +1426,7 @@ function M.reset(namespace, bufnr) vim.api.nvim_exec_autocmds('DiagnosticChanged', { modeline = false, buffer = iter_bufnr, + data = { diagnostics = {} }, }) end end -- cgit From 9ec4b20be695501ba166f31dec39ef9e30cc7dd8 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 16 Sep 2022 09:05:05 +0200 Subject: fix(treesitter): return full metadata for get_captures_at_position (#20203) fix(treesitter): get_captures_at_position returns metadata Return the full `metadata` table for the capture instead of just the priority. Further cleanup of related docs. --- runtime/lua/vim/treesitter.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 89aa611acd..04e12cbe0b 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -154,7 +154,7 @@ function M.get_node_range(node_or_range) end end ----Determines whether (line, col) position is in node range +--- Determines whether (line, col) position is in node range --- ---@param node userdata |tsnode| defining the range ---@param line number Line (0-based) @@ -178,7 +178,8 @@ function M.is_in_node_range(node, line, col) end end ----Determines if a node contains a range +--- Determines if a node contains a range +--- ---@param node userdata |tsnode| ---@param range table --- @@ -191,17 +192,16 @@ function M.node_contains(node, range) return start_fits and end_fits end ----Returns a list of highlight captures at the given position --- ----@param bufnr number Buffer number (0 for current buffer) ----@param row number Position row ----@param col number Position column +--- Returns a list of highlight captures at the given position +--- +--- 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 --- ----@return table[] Captures of the form `{ capture = "capture name", priority = capture priority }` +---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` function M.get_captures_at_position(bufnr, row, col) if bufnr == 0 then bufnr = a.nvim_get_current_buf() @@ -240,7 +240,7 @@ function M.get_captures_at_position(bufnr, row, col) if M.is_in_node_range(node, row, col) then local c = q._query.captures[capture] -- name of the capture in the query if c ~= nil then - table.insert(matches, { capture = c, priority = metadata.priority }) + table.insert(matches, { capture = c, metadata = metadata }) end end end @@ -248,7 +248,7 @@ function M.get_captures_at_position(bufnr, row, col) return matches end ----Returns a list of highlight capture names under the cursor +--- Returns a list of highlight capture names under the cursor --- ---@param winnr (number|nil) Window handle or 0 for current window (default) --- -- cgit From e512d3ecf2b6e0104d2df0d863c8d51a2d7e5ab1 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Fri, 16 Sep 2022 17:52:08 +0200 Subject: vim-patch:9.0.0479: in :def function all closures in loop get the sam… (#20220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vim-patch:9.0.0479: in :def function all closures in loop get the same variables Problem: In a :def function all closures in a loop get the same variables. Solution: Use a separate list of variables for LOADOUTER and SAVEOUTER. https://github.com/vim/vim/commit/1aea184a0dc558a222cc5bcbaad9ab0fd700c7b9 (note: patch description is wrong) --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 39985c948e..8fb8e98f7d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1021,6 +1021,7 @@ local extension = { dsm = 'vb', ctl = 'vb', vbs = 'vb', + vdf = 'vdf', vdmpp = 'vdmpp', vpp = 'vdmpp', vdmrt = 'vdmrt', -- cgit From 8dc61713d9db433dfa2024b45f3bc590dcb58f83 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 18 Sep 2022 14:16:24 +0200 Subject: vim-patch:9.0.0497: LyRiCs files are not recognized (#20239) Problem: LyRiCs files are not recognized. Solution: Add a pattern to detect LyRiCs files. (closes vim/vim#11155) https://github.com/vim/vim/commit/65ee49decf5677690cd695d5d288e39344965fff --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 8fb8e98f7d..5c595df087 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -611,6 +611,7 @@ local extension = { nse = 'lua', rockspec = 'lua', lua = 'lua', + lrc = 'lyrics', m = function(path, bufnr) return require('vim.filetype.detect').m(bufnr) end, -- cgit From 10196f1b462400b8a4a9e8f13893da2514f8c850 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Tue, 20 Sep 2022 09:28:23 +0200 Subject: fix(lsp): support `false` result in handlers (#20252) Closes https://github.com/neovim/neovim/issues/20111 --- runtime/lua/vim/lsp/rpc.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 7d047f8958..755c0ffc6f 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -405,8 +405,7 @@ function Client:handle_body(body) { status = status, result = result, err = err } ) if status then - if not (result or err) then - -- TODO this can be a problem if `null` is sent for result. needs vim.NIL + if result == nil and err == nil then error( string.format( 'method %q: either a result or an error must be sent to the server in response', -- cgit From e76215830522f75dea1495a0de38933d18954eca Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 20 Sep 2022 10:42:45 +0100 Subject: docs(lua): opts in `vim.keymap.{set,del}` can be optional (#20255) --- runtime/lua/vim/keymap.lua | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index 219de16b5c..af41794c53 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -36,17 +36,17 @@ local keymap = {} ---@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 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()|. ---- Defaults to `false`. +---@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()|. +--- Defaults to `false`. ---@see |nvim_set_keymap()| function keymap.set(mode, lhs, rhs, opts) vim.validate({ @@ -57,7 +57,6 @@ function keymap.set(mode, lhs, rhs, opts) }) opts = vim.deepcopy(opts) or {} - local is_rhs_luaref = type(rhs) == 'function' mode = type(mode) == 'string' and { mode } or mode if opts.expr and opts.replace_keycodes ~= false then @@ -73,7 +72,7 @@ function keymap.set(mode, lhs, rhs, opts) opts.remap = nil end - if is_rhs_luaref then + if type(rhs) == 'function' then opts.callback = rhs rhs = '' end @@ -99,9 +98,9 @@ end --- --- vim.keymap.del({'n', 'i', 'v'}, 'w', { buffer = 5 }) ---
----@param opts table A table of optional arguments: ---- - buffer: (number or boolean) Remove a mapping from the given buffer. ---- When "true" or 0, use the current buffer. +---@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. ---@see |vim.keymap.set()| --- function keymap.del(modes, lhs, opts) -- cgit From abe2d90693e5cec3428c0162c48f0ea38972ff31 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 20 Sep 2022 11:15:32 +0100 Subject: feat(lua): move compat module from runtime to test (#20257) --- runtime/lua/man.lua | 2 -- runtime/lua/vim/compat.lua | 12 ------------ 2 files changed, 14 deletions(-) delete mode 100644 runtime/lua/vim/compat.lua (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 82ed0305f7..f0306f4871 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,5 +1,3 @@ -require('vim.compat') - local api, fn = vim.api, vim.fn local find_arg = '-w' diff --git a/runtime/lua/vim/compat.lua b/runtime/lua/vim/compat.lua deleted file mode 100644 index 2c9786d491..0000000000 --- a/runtime/lua/vim/compat.lua +++ /dev/null @@ -1,12 +0,0 @@ --- Lua 5.1 forward-compatibility layer. --- For background see https://github.com/neovim/neovim/pull/9280 --- --- Reference the lua-compat-5.2 project for hints: --- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/compat52/mstrict.lua --- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/tests/test.lua - -local lua_version = _VERSION:sub(-3) - -if lua_version >= '5.2' then - unpack = table.unpack -- luacheck: ignore 121 143 -end -- cgit From ae30e388dee08e59c08dffc20c2f5122b8c31249 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 20 Sep 2022 14:10:00 +0200 Subject: vim-patch:9.0.0510: Chatito files are not recognized (#20260) Problem: Chatito files are not recognized. Solution: Add a pattern for Chatito files. (closes vim/vim#11174) https://github.com/vim/vim/commit/7c046ae99ba85a4fdf1a546157e2ed6f12b79ea6 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5c595df087..e58d56ad39 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -202,6 +202,7 @@ local extension = { return require('vim.filetype.detect').change(bufnr) end, chs = 'chaskell', + chatito = 'chatito', chopro = 'chordpro', crd = 'chordpro', crdpro = 'chordpro', -- cgit From ec94014cd1d09884b12cb19021d5a1eff52cb76d Mon Sep 17 00:00:00 2001 From: ofwinterpassed Date: Tue, 20 Sep 2022 22:14:58 +0200 Subject: fix(lsp): out of bounds error in lsp.util.apply_text_edits (#20137) Co-authored-by: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> --- runtime/lua/vim/lsp/util.lua | 65 ++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 24 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 283099bbcf..1909dbd4d1 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -459,35 +459,52 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) text = split(text_edit.newText, '\n', true), } - -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. local max = api.nvim_buf_line_count(bufnr) - if max <= e.start_row or max <= e.end_row then - local len = #(get_line(bufnr, max - 1) or '') - if max <= e.start_row then - e.start_row = max - 1 - e.start_col = len - table.insert(e.text, 1, '') - end + -- If the whole edit is after the lines in the buffer we can simply add the new text to the end + -- of the buffer. + if max <= e.start_row then + api.nvim_buf_set_lines(bufnr, max, max, false, e.text) + else + local last_line_len = #(get_line(bufnr, math.min(e.end_row, max - 1)) or '') + -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't + -- accept it so we should fix it here. if max <= e.end_row then e.end_row = max - 1 - e.end_col = len + e.end_col = last_line_len + has_eol_text_edit = true + else + -- If the replacement is over the end of a line (i.e. e.end_col is out of bounds and the + -- replacement text ends with a newline We can likely assume that the replacement is assumed + -- to be meant to replace the newline with another newline and we need to make sure this + -- doens't add an extra empty line. E.g. when the last line to be replaced contains a '\r' + -- in the file some servers (clangd on windows) will include that character in the line + -- while nvim_buf_set_text doesn't count it as part of the line. + if + e.end_col > last_line_len + and #text_edit.newText > 0 + and string.sub(text_edit.newText, -1) == '\n' + then + table.remove(e.text, #e.text) + end end - has_eol_text_edit = true - end - api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) - - -- Fix cursor position. - local row_count = (e.end_row - e.start_row) + 1 - if e.end_row < cursor.row then - cursor.row = cursor.row + (#e.text - row_count) - is_cursor_fixed = true - elseif e.end_row == cursor.row and e.end_col <= cursor.col then - cursor.row = cursor.row + (#e.text - row_count) - cursor.col = #e.text[#e.text] + (cursor.col - e.end_col) - if #e.text == 1 then - cursor.col = cursor.col + e.start_col + -- Make sure we don't go out of bounds for e.end_col + e.end_col = math.min(last_line_len, e.end_col) + + api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) + + -- Fix cursor position. + local row_count = (e.end_row - e.start_row) + 1 + if e.end_row < cursor.row then + cursor.row = cursor.row + (#e.text - row_count) + is_cursor_fixed = true + elseif e.end_row == cursor.row and e.end_col <= cursor.col then + cursor.row = cursor.row + (#e.text - row_count) + cursor.col = #e.text[#e.text] + (cursor.col - e.end_col) + if #e.text == 1 then + cursor.col = cursor.col + e.start_col + end + is_cursor_fixed = true end - is_cursor_fixed = true end end -- cgit From b4b05f160dbb6b9b945c173b7e910b0e4c1a8b01 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Wed, 21 Sep 2022 23:58:57 +0200 Subject: feat(filetype): expand environment variables in filetype patterns (#20145) --- runtime/lua/vim/filetype.lua | 59 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 27 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index e58d56ad39..5f58da7a34 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1296,7 +1296,6 @@ local filename = { WORKSPACE = 'bzl', BUILD = 'bzl', ['cabal.project'] = 'cabalproject', - [vim.env.HOME .. '/cabal.config'] = 'cabalconfig', ['cabal.config'] = 'cabalconfig', calendar = 'calendar', catalog = 'catalog', @@ -1704,6 +1703,7 @@ local pattern = { ['.*/meta%-.*/conf/.*%.conf'] = 'bitbake', ['bzr_log%..*'] = 'bzr', ['.*enlightenment/.*%.cfg'] = 'c', + ['${HOME}/cabal%.config'] = 'cabalconfig', ['cabal%.project%..*'] = starsetf('cabalproject'), ['.*/%.calendar/.*'] = starsetf('calendar'), ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'), @@ -1828,41 +1828,21 @@ local pattern = { ['.*/%.config/git/config'] = 'gitconfig', ['.*%.git/config%.worktree'] = 'gitconfig', ['.*%.git/worktrees/.*/config%.worktree'] = 'gitconfig', - ['.*/git/config'] = function(path, bufnr) - if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/config') then - return 'gitconfig' - end - end, + ['${XDG_CONFIG_HOME}/git/config'] = 'gitconfig', ['.*%.git/info/attributes'] = 'gitattributes', ['.*/etc/gitattributes'] = 'gitattributes', ['.*/%.config/git/attributes'] = 'gitattributes', - ['.*/git/attributes'] = function(path, bufnr) - if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/attributes') then - return 'gitattributes' - end - end, + ['${XDG_CONFIG_HOME}/git/attributes'] = 'gitattributes', ['.*%.git/info/exclude'] = 'gitignore', ['.*/%.config/git/ignore'] = 'gitignore', - ['.*/git/ignore'] = function(path, bufnr) - if vim.env.XDG_CONFIG_HOME and path:find(vim.env.XDG_CONFIG_HOME .. '/git/ignore') then - return 'gitignore' - end - end, + ['${XDG_CONFIG_HOME}/git/ignore'] = 'gitignore', ['%.gitsendemail%.msg%.......'] = 'gitsendemail', ['gkrellmrc_.'] = 'gkrellmrc', ['.*/usr/.*/gnupg/options%.skel'] = 'gpg', ['.*/%.gnupg/options'] = 'gpg', ['.*/%.gnupg/gpg%.conf'] = 'gpg', - ['.*/options'] = function(path, bufnr) - if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/options') then - return 'gpg' - end - end, - ['.*/gpg%.conf'] = function(path, bufnr) - if vim.env.GNUPGHOME and path:find(vim.env.GNUPGHOME .. '/gpg%.conf') then - return 'gpg' - end - end, + ['${GNUPGHOME}/options'] = 'gpg', + ['${GNUPGHOME}/gpg%.conf'] = 'gpg', ['.*/etc/group'] = 'group', ['.*/etc/gshadow'] = 'group', ['.*/etc/group%.edit'] = 'group', @@ -1876,7 +1856,7 @@ local pattern = { ['.*/etc/grub%.conf'] = 'grub', -- gtkrc* and .gtkrc* ['%.?gtkrc.*'] = starsetf('gtkrc'), - [vim.env.VIMRUNTIME .. '/doc/.*%.txt'] = 'help', + ['${VIMRUNTIME}/doc/.*%.txt'] = 'help', ['hg%-editor%-.*%.txt'] = 'hgcommit', ['.*/etc/host%.conf'] = 'hostconf', ['.*/etc/hosts%.deny'] = 'hostsaccess', @@ -2280,6 +2260,9 @@ end --- Filename patterns can specify an optional priority to resolve cases when a --- file path matches multiple patterns. Higher priorities are matched first. --- When omitted, the priority defaults to 0. +--- A pattern can contain environment variables of the form "${SOME_VAR}" that will +--- be automatically expanded. If the environment variable is not set, the pattern +--- won't be matched. --- --- See $VIMRUNTIME/lua/vim/filetype.lua for more examples. --- @@ -2308,6 +2291,8 @@ end --- ['.*/etc/foo/.*'] = 'fooscript', --- -- Using an optional priority --- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, +--- -- A pattern containing an environment variable +--- ['${XDG_CONFIG_HOME}/foo/git'] = 'git', --- ['README.(%a+)$'] = function(path, bufnr, ext) --- if ext == 'md' then --- return 'markdown' @@ -2381,8 +2366,28 @@ local function dispatch(ft, path, bufnr, ...) end 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 + end + if expand_env_lookup[pat] then + local return_early + pat = pat:gsub('%${(%S-)}', function(env) + -- If an environment variable is present in the pattern but not set, there is no match + if not vim.env[env] then + return_early = true + return nil + end + return vim.env[env] + end) + if return_early then + return false + end + end -- If the pattern contains a / match against the full path, otherwise just the tail local fullpat = '^' .. pat .. '$' local matches -- cgit From 02f8ca59a80cd3570593c717ff6ceadc33239b89 Mon Sep 17 00:00:00 2001 From: bfredl Date: Tue, 20 Sep 2022 22:03:16 +0200 Subject: fix(tests): indicate in test logs when nvim exit times out When it happens it wastes 2 seconds which is NOT included in the normal busted timing info. It is hard to correct this, but we can at least print a warning when this happens. --- runtime/lua/man.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index f0306f4871..88535321ac 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -21,13 +21,15 @@ local function man_system(cmd, silent) local done = false local exit_code - local handle = vim.loop.spawn(cmd[1], { + local handle + handle = vim.loop.spawn(cmd[1], { args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, }, function(code) exit_code = code stdout:close() stderr:close() + handle:close() done = true end) @@ -52,7 +54,7 @@ local function man_system(cmd, silent) if not done then if handle then - vim.loop.shutdown(handle) + handle:close() stdout:close() stderr:close() end -- cgit From 14610332b2e851f9464a2d32a0ef3869dcff9834 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 23 Sep 2022 20:20:56 -0500 Subject: fix(lsp): use correct function name in deprecated message (#20308) fix: use correct function name in deprecated message --- runtime/lua/vim/lsp/buf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 2ce565e1d9..8567619228 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -404,7 +404,7 @@ end ---@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) - vim.deprecate('vim.lsp.buf.range_formatting', 'vim.lsp.formatexpr or vim.lsp.format', '0.9.0') + vim.deprecate('vim.lsp.buf.range_formatting', 'vim.lsp.formatexpr or vim.lsp.buf.format', '0.9.0') local params = util.make_given_range_params(start_pos, end_pos) params.options = util.make_formatting_params(options).options select_client('textDocument/rangeFormatting', function(client) -- cgit From 24b5449b3d9b39a436bb8fe935116afa15a9473e Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 24 Sep 2022 10:16:30 +0200 Subject: vim-patch:9.0.0562: HSL playlist files are not recognized (#20307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: HSL playlist files are not recognized. Solution: Add a pattern to recognize HSL palylist files. (Benoît Ryder, closes vim/vim#11204) https://github.com/vim/vim/commit/35fdd9a67d73d4750152c419d4193ebb6b6d6eee --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 5f58da7a34..c96ead323b 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -473,6 +473,7 @@ local extension = { hex = 'hex', ['h32'] = 'hex', hjson = 'hjson', + m3u8 = 'hlsplaylist', hog = 'hog', hws = 'hollywood', hoon = 'hoon', -- cgit From caf5738fa9cc12fd448a9c0787a3ebf0c8e696e9 Mon Sep 17 00:00:00 2001 From: shaunsingh Date: Sat, 24 Sep 2022 06:46:21 -0400 Subject: fix(lsp): create missing directory before creating file (#19835) Co-authored-by: Mathias Fussenegger --- runtime/lua/vim/lsp/util.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 1909dbd4d1..64512a9739 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -772,8 +772,11 @@ local function create_file(change) -- from spec: Overwrite wins over `ignoreIfExists` local fname = vim.uri_to_fname(change.uri) if not opts.ignoreIfExists or opts.overwrite then + vim.fn.mkdir(vim.fs.dirname(fname), 'p') local file = io.open(fname, 'w') - file:close() + if file then + file:close() + end end vim.fn.bufadd(fname) end -- cgit From f8b656c582c6f0d9cb35523ac2d2f3d0edbcd52b Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sat, 24 Sep 2022 12:59:37 +0200 Subject: vim-patch:9.0.0566: Nim files are not recognized (#20317) Problem: Nim files are not recognized. Solution: Add patterns for Nim files. (Nbiba Bedis, closes vim/vim#11205) https://github.com/vim/vim/commit/9fd1583c839c5e43b0d48ec815a79005a2364776 --- runtime/lua/vim/filetype.lua | 3 +++ 1 file changed, 3 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index c96ead323b..d8ede33dfa 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -704,6 +704,9 @@ local extension = { nanorc = 'nanorc', ncf = 'ncf', nginx = 'nginx', + nim = 'nim', + nims = 'nim', + nimble = 'nim', ninja = 'ninja', nix = 'nix', nqc = 'nqc', -- cgit From 3169fc54a129f6dd1c0baf2d404e50381cf48e8f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 25 Sep 2022 00:45:15 +0200 Subject: refactor(treesitter): rename x_position => x_pos "pos" has a long precedent as "position" in vim, and there is no reason to use a verbose name here. --- runtime/lua/vim/treesitter.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 04e12cbe0b..6cd00516bf 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -202,7 +202,7 @@ end ---@param col number Position column --- ---@return table[] List of captures `{ capture = "capture name", metadata = { ... } }` -function M.get_captures_at_position(bufnr, row, col) +function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end @@ -258,7 +258,7 @@ function M.get_captures_at_cursor(winnr) local bufnr = a.nvim_win_get_buf(winnr) local cursor = a.nvim_win_get_cursor(winnr) - local data = M.get_captures_at_position(bufnr, cursor[1] - 1, cursor[2]) + local data = M.get_captures_at_pos(bufnr, cursor[1] - 1, cursor[2]) local captures = {} @@ -278,7 +278,7 @@ end --- - ignore_injections boolean Ignore injected languages (default true) --- ---@return userdata |tsnode| under the cursor -function M.get_node_at_position(bufnr, row, col, opts) +function M.get_node_at_pos(bufnr, row, col, opts) if bufnr == 0 then bufnr = a.nvim_get_current_buf() end @@ -302,8 +302,7 @@ function M.get_node_at_cursor(winnr) local bufnr = a.nvim_win_get_buf(winnr) local cursor = a.nvim_win_get_cursor(winnr) - return M.get_node_at_position(bufnr, cursor[1] - 1, cursor[2], { ignore_injections = false }) - :type() + return M.get_node_at_pos(bufnr, cursor[1] - 1, cursor[2], { ignore_injections = false }):type() end --- Starts treesitter highlighting for a buffer -- cgit From 2a5692c64628ee0af3ef4931a774a2eb0a7e046f Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Sun, 25 Sep 2022 13:59:11 +0200 Subject: vim-patch:9.0.0583: only recognizing .m3u8 files is inconsistent (#20342) Problem: Only recognizing .m3u8 files is inconsistent. Solution: Also matc .m3u files. (issue vim/vim#11204) https://github.com/vim/vim/commit/b9725bc7f6427654eb4e35874034b0ec1b6b96b3 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d8ede33dfa..bd317baae9 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -473,6 +473,7 @@ local extension = { hex = 'hex', ['h32'] = 'hex', hjson = 'hjson', + m3u = 'hlsplaylist', m3u8 = 'hlsplaylist', hog = 'hog', hws = 'hollywood', -- cgit From 63be7651829f8b77c4974d08ebe09f7775e41a8a Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 25 Sep 2022 19:58:27 -0400 Subject: fix(docs): invalid :help links #20345 Fix those naughty single quotes. closes #20159 --- runtime/lua/vim/_editor.lua | 4 ++-- runtime/lua/vim/highlight.lua | 2 +- runtime/lua/vim/lsp.lua | 22 +++++++++++----------- runtime/lua/vim/lsp/buf.lua | 14 +++++++------- runtime/lua/vim/lsp/handlers.lua | 2 +- runtime/lua/vim/lsp/rpc.lua | 2 +- runtime/lua/vim/lsp/util.lua | 6 +++--- runtime/lua/vim/shared.lua | 2 +- runtime/lua/vim/treesitter/query.lua | 8 ++++---- 9 files changed, 31 insertions(+), 31 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index b8a7f71145..96a87b8bb9 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -401,7 +401,7 @@ end ---@param bufnr number of buffer ---@param pos1 (line, column) tuple marking beginning of region ---@param pos2 (line, column) tuple marking end of region ----@param regtype type of selection (:help setreg) +---@param regtype type of selection, see |setreg()| ---@param inclusive boolean indicating whether the selection is end-inclusive ---@return region lua table of the form {linenr = {startcol,endcol}} function vim.region(bufnr, pos1, pos2, regtype, inclusive) @@ -448,7 +448,7 @@ end --- Defers calling `fn` until `timeout` ms passes. --- --- Use to do a one-shot timer that calls `fn` ---- Note: The {fn} is |schedule_wrap|ped automatically, so API functions are +--- Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions are --- safe to call. ---@param fn Callback to call once `timeout` expires ---@param timeout Number of milliseconds to wait before calling `fn` diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index ddd504a0e0..0fde515bd9 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -41,7 +41,7 @@ end ---@param start first position (tuple {line,col}) ---@param finish second position (tuple {line,col}) ---@param opts table with options: --- - regtype type of range (:help setreg, default charwise) +-- - 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) diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 22933d8143..a64facf214 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -823,7 +823,7 @@ end --- }) --- --- ---- See |lsp.start_client| for all available options. The most important are: +--- See |vim.lsp.start_client()| for all available options. The most important are: --- --- `name` is an arbitrary name for the LSP client. It should be unique per --- language server. @@ -834,7 +834,7 @@ end --- --- `root_dir` path to the project root. --- By default this is used to decide if an existing client should be re-used. ---- The example above uses |vim.fs.find| and |vim.fs.dirname| to detect the +--- The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the --- root by traversing the file system upwards starting --- from the current directory until either a `pyproject.toml` or `setup.py` --- file is found. @@ -849,11 +849,11 @@ end --- --- --- To ensure a language server is only started for languages it can handle, ---- make sure to call |vim.lsp.start| within a |FileType| autocmd. +--- make sure to call |vim.lsp.start()| within a |FileType| autocmd. --- Either use |:au|, |nvim_create_autocmd()| or put the call in a --- `ftplugin/.lua` (See |ftplugin-name|) --- ----@param config table Same configuration as documented in |lsp.start_client()| +---@param config table Same configuration as documented in |vim.lsp.start_client()| ---@param opts nil|table Optional keyword arguments: --- - reuse_client (fun(client: client, config: table): boolean) --- Predicate used to decide if a client should be re-used. @@ -902,12 +902,12 @@ end --- --- ---@param cmd: (table|string|fun(dispatchers: table):table) command string or ---- list treated like |jobstart|. The command must launch the language server +--- 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 --- functions `request`, `notify`, `is_closing` and `terminate` ---- See |vim.lsp.rpc.request| and |vim.lsp.rpc.notify| ---- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect| +--- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()| +--- For TCP there is a built-in rpc client factory: |vim.lsp.rpc.connect()| --- ---@param cmd_cwd: (string, default=|getcwd()|) Directory to launch --- the `cmd` process. Not related to `root_dir`. @@ -963,7 +963,7 @@ end ---@param on_error Callback with parameters (code, ...), invoked --- when the client operation throws an error. `code` is a number describing --- the error. Other arguments may be passed depending on the error kind. See ---- |vim.lsp.rpc.client_errors| for possible errors. +--- `vim.lsp.rpc.client_errors` for possible errors. --- Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name. --- ---@param before_init Callback with parameters (initialize_params, config) @@ -999,8 +999,8 @@ end --- notifications to the server by the given number in milliseconds. No debounce --- occurs if nil --- - exit_timeout (number|boolean, default false): Milliseconds to wait for server to ---- exit cleanly after sending the 'shutdown' request before sending kill -15. ---- If set to false, nvim exits immediately after sending the 'shutdown' request to the server. +--- exit cleanly after sending the "shutdown" request before sending kill -15. +--- If set to false, nvim exits immediately after sending the "shutdown" request to the server. --- ---@param root_dir string Directory where the LSP --- server will base its workspaceFolders, rootUri, and rootPath @@ -1078,7 +1078,7 @@ function lsp.start_client(config) --- ---@param code (number) 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) local _ = log.error() diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8567619228..b9aaacb437 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -111,7 +111,7 @@ end --- about the context in which a completion was triggered (how it was triggered, --- and by which trigger character, if applicable) --- ----@see |vim.lsp.protocol.constants.CompletionTriggerKind| +---@see vim.lsp.protocol.constants.CompletionTriggerKind function M.completion(context) local params = util.make_position_params() params.context = context @@ -317,7 +317,7 @@ end --- ---@param options table|nil with valid `FormattingOptions` entries ---@param timeout_ms (number) Request timeout ----@see |vim.lsp.buf.formatting_seq_sync| +---@see |vim.lsp.buf.format()| function M.formatting_sync(options, timeout_ms) vim.notify_once( 'vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead', @@ -614,14 +614,14 @@ end --- Lists all the call sites of the symbol under the cursor in the --- |quickfix| window. If the symbol can resolve to multiple ---- items, the user can pick one in the |inputlist|. +--- items, the user can pick one in the |inputlist()|. function M.incoming_calls() call_hierarchy('callHierarchy/incomingCalls') end --- Lists all the items that are called by the symbol under the --- cursor in the |quickfix| window. If the symbol can resolve to ---- multiple items, the user can pick one in the |inputlist|. +--- multiple items, the user can pick one in the |inputlist()|. function M.outgoing_calls() call_hierarchy('callHierarchy/outgoingCalls') end @@ -730,9 +730,9 @@ end --- --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups --- to be defined or you won't be able to see the actual highlights. ---- |LspReferenceText| ---- |LspReferenceRead| ---- |LspReferenceWrite| +--- |hl-LspReferenceText| +--- |hl-LspReferenceRead| +--- |hl-LspReferenceWrite| function M.document_highlight() local params = util.make_position_params() request('textDocument/documentHighlight', params) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 624436bc9b..a32c59dd17 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -389,7 +389,7 @@ M['textDocument/implementation'] = location_handler ---@param config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |vim.api.nvim_open_win()| +--- - See |nvim_open_win()| function M.signature_help(_, result, ctx, config) config = config or {} config.focus_id = ctx.method diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 755c0ffc6f..842a0ce329 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -635,7 +635,7 @@ end --- Starts an LSP server process and create an LSP RPC client object to --- interact with it. Communication with the spawned process happens via stdio. For ---- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect| +--- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()| --- ---@param cmd (string) Command to start the LSP server. ---@param cmd_args (table) List of additional string arguments to pass to {cmd}. diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 64512a9739..dbc18963f9 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1504,7 +1504,7 @@ 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 |vim.api.nvim_open_win()|) +---@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 --- - wrap: (boolean, default true) wrap long lines @@ -1819,7 +1819,7 @@ end --- CAUTION: Modifies the input in-place! --- ---@param lines (table) list of lines ----@returns (string) filetype or 'markdown' if it was unchanged. +---@returns (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 @@ -1992,7 +1992,7 @@ function M.make_workspace_params(added, removed) end --- Returns indentation size. --- ----@see |shiftwidth| +---@see 'shiftwidth' ---@param bufnr (number|nil): Buffer handle, defaults to current ---@returns (number) indentation size function M.get_effective_tabstop(bufnr) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index de5f7240aa..c5c31b6ddf 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -302,7 +302,7 @@ end --- Merges recursively two or more map-like tables. --- ----@see |tbl_extend()| +---@see |vim.tbl_extend()| --- ---@param behavior string Decides what to do if a key is found in more than one map: --- - "error": raise an error diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index d1dc29969b..1e46daaccf 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -54,7 +54,7 @@ end --- Gets the list of files used to make up a query --- ---@param lang string Language to get query for ----@param query_name string Name of the query to load (e.g., 'highlights') +---@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) @@ -162,7 +162,7 @@ local explicit_queries = setmetatable({}, { --- set by plugins. --- ---@param lang string Language to use for the query ----@param query_name string Name of the query (e.g., 'highlights') +---@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) @@ -171,7 +171,7 @@ end --- Returns the runtime query {query_name} for {lang}. --- ---@param lang string Language to use for the query ----@param query_name string Name of the query (e.g. 'highlights') +---@param query_name string Name of the query (e.g. "highlights") --- ---@return Query Parsed query function M.get_query(lang, query_name) @@ -596,7 +596,7 @@ end --- Iterates the matches of self on a given range. --- --- Iterate over all matches within a {node}. The arguments are the same as ---- for |query:iter_captures()| but the iterated values are different: +--- for |Query:iter_captures()| but the iterated values are different: --- an (1-based) index of the pattern in the query, a table mapping --- capture indices to nodes, and metadata from any directives processing the match. --- If the query has more than one pattern, the capture table might be sparse -- cgit From 760a8754c07a62afa4d83f4e254b1d45ae8cfc65 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 27 Sep 2022 12:36:33 +0200 Subject: vim-patch:9.0.0599: latexmkrc files are not recognized Problem: Latexmkrc files are not recognized. Solution: Use Perl filetype for latexmkrc files. (closes vim/vim#11241) https://github.com/vim/vim/commit/cde031938537970938437cdbb235bc0da755ae4a --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index bd317baae9..642f783e78 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1496,6 +1496,8 @@ local filename = { ['/etc/shadow-'] = 'passwd', ['/etc/shadow'] = 'passwd', ['/etc/passwd.edit'] = 'passwd', + ['latexmkrc'] = 'perl', + ['.latexmkrc'] = 'perl', ['pf.conf'] = 'pf', ['main.cf'] = 'pfmain', pinerc = 'pine', -- cgit From e176f9dacf28f56e91fcbc92f392228588a46c85 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 27 Sep 2022 12:38:31 +0200 Subject: vim-patch:9.0.0600: GYP files are not recognized Problem: GYP files are not recognized. Solution: Recognize GYP files. (closes vim/vim#11242) https://github.com/vim/vim/commit/d32474229213276c64cb293885a975dcb406fbc9 --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 642f783e78..66b89a7078 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -448,6 +448,8 @@ local extension = { gsp = 'gsp', gjs = 'javascript.glimmer', gts = 'typescript.glimmer', + gyp = 'gyp', + gypi = 'gyp', hack = 'hack', hackpartial = 'hack', haml = 'haml', -- cgit From fe0727a1bfebdb8dc27bd8c276566f203b4d6a18 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Tue, 27 Sep 2022 13:03:28 +0200 Subject: vim-patch:9.0.0602: new TypeScript extensions are not recognized Problem: New TypeScript extensions are not recognized. Solution: Recognize .mts and .cts files. (closes vim/vim#11237) https://github.com/vim/vim/commit/7fc6c0e4dab4e80b9806a973936af54276468513 --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 66b89a7078..a7a34a54cd 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1021,6 +1021,8 @@ local extension = { ts = function(path, bufnr) return M.getlines(bufnr, 1):find('<%?xml') and 'xml' or 'typescript' end, + mts = 'typescript', + cts = 'typescript', tsx = 'typescriptreact', uc = 'uc', uit = 'uil', -- cgit From f46060c4cbc1efe100019075214def53fe4d47b3 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Tue, 27 Sep 2022 17:10:19 +0300 Subject: vim-patch:9.0.0604: luacheckrc file is not recognized (#20371) Problem: Luacheckrc file is not recognized. Solution: Use lua filetype for luacheckrc. (closes vim/vim#11236) https://github.com/vim/vim/commit/49c311c9b18e18c05f93728d1f8a552923a18423 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index a7a34a54cd..d584d2659d 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1455,6 +1455,7 @@ local filename = { ['.sawfishrc'] = 'lisp', ['/etc/login.access'] = 'loginaccess', ['/etc/login.defs'] = 'logindefs', + ['.luacheckrc'] = 'lua', ['lynx.cfg'] = 'lynx', ['m3overrides'] = 'm3build', ['m3makefile'] = 'm3build', -- cgit From 16336c486ecb5a60e85a870904316308c7d7fc3f Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 25 Sep 2022 02:20:47 +0200 Subject: feat(gen_help_html.lua): adapt to new parser - adapt to parser changes from https://github.com/vigoux/tree-sitter-vimdoc/pull/16 - numerous other generator improvements --- runtime/lua/vim/lsp/util.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index dbc18963f9..aea2a27f9e 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -907,8 +907,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers return end --The active signature. If omitted or the value lies outside the range of - --`signatures` the value defaults to zero or is ignored if `signatures.length - --=== 0`. Whenever possible implementors should make an active decision about + --`signatures` the value defaults to zero or is ignored if `signatures.length == 0`. + --Whenever possible implementors should make an active decision about --the active signature and shouldn't rely on a default value. local contents = {} local active_hl -- cgit From 94718e479da786cf75f9805470a6a108e8097dab Mon Sep 17 00:00:00 2001 From: Sergey Berezhnoy Date: Thu, 29 Sep 2022 17:25:07 +0300 Subject: fix(query): fix unnatural order for inherits in treesitter queries (#20298) close #20297 --- runtime/lua/vim/treesitter/query.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 1e46daaccf..7ca7384a88 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -125,11 +125,12 @@ function M.get_query_files(lang, query_name, is_included) io.close(file) end - local query_files = { base_query } + local query_files = {} for _, base_lang in ipairs(base_langs) do local base_files = M.get_query_files(base_lang, query_name, true) vim.list_extend(query_files, base_files) end + vim.list_extend(query_files, { base_query }) vim.list_extend(query_files, extensions) return query_files -- cgit From 33dd917d7fdc40483b3d18d0c7bcf5994b26fe86 Mon Sep 17 00:00:00 2001 From: Jonas Strittmatter <40792180+smjonas@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:26:19 +0200 Subject: fix(filetype): add missing return to changelog detection function (#20403) --- runtime/lua/vim/filetype.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d584d2659d..b61fc528f5 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -1734,7 +1734,7 @@ local pattern = { { priority = -1 }, }, ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr) - require('vim.filetype.detect').changelog(bufnr) + return require('vim.filetype.detect').changelog(bufnr) end), ['.*%.%.ch'] = 'chill', ['.*%.cmake%.in'] = 'cmake', -- cgit From df646572c53f55268a5dbb61628d7c3b302d5663 Mon Sep 17 00:00:00 2001 From: dundargoc <33953936+dundargoc@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:53:52 +0200 Subject: docs: fix typos (#20394) Co-authored-by: Raphael Co-authored-by: smjonas Co-authored-by: zeertzjq --- runtime/lua/vim/lsp/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index aea2a27f9e..88667caf68 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -476,7 +476,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- If the replacement is over the end of a line (i.e. e.end_col is out of bounds and the -- replacement text ends with a newline We can likely assume that the replacement is assumed -- to be meant to replace the newline with another newline and we need to make sure this - -- doens't add an extra empty line. E.g. when the last line to be replaced contains a '\r' + -- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r' -- in the file some servers (clangd on windows) will include that character in the line -- while nvim_buf_set_text doesn't count it as part of the line. if -- cgit From e54541f7f941427b408f8d5a7bab446c141b5f76 Mon Sep 17 00:00:00 2001 From: Mathias Fußenegger Date: Sat, 1 Oct 2022 11:35:36 +0200 Subject: refactor(lsp): remove deprecated lsp functions (#20421) --- runtime/lua/vim/lsp/buf.lua | 193 -------------------------------------------- 1 file changed, 193 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index b9aaacb437..8550fe253b 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -118,38 +118,6 @@ function M.completion(context) return request('textDocument/completion', params) end ----@private ---- If there is more than one client that supports the given method, ---- asks the user to select one. --- ----@returns The client that the user selected or nil -local function select_client(method, on_choice) - validate({ - on_choice = { on_choice, 'function', false }, - }) - local clients = vim.tbl_values(vim.lsp.buf_get_clients()) - clients = vim.tbl_filter(function(client) - return client.supports_method(method) - end, clients) - -- better UX when choices are always in the same order (between restarts) - table.sort(clients, function(a, b) - return a.name < b.name - end) - - if #clients > 1 then - vim.ui.select(clients, { - prompt = 'Select a language server:', - format_item = function(client) - return client.name - end, - }, on_choice) - elseif #clients < 1 then - on_choice(nil) - else - on_choice(clients[1]) - end -end - ---@private ---@return table {start={row, col}, end={row, col}} using (1, 0) indexing local function range_from_selection() @@ -283,139 +251,6 @@ function M.format(options) end end ---- Formats the current buffer. ---- ----@param options (table|nil) Can be used to specify FormattingOptions. ---- Some unspecified options will be automatically derived from the current ---- Neovim options. --- ----@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting -function M.formatting(options) - vim.notify_once( - 'vim.lsp.buf.formatting is deprecated. Use vim.lsp.buf.format { async = true } instead', - vim.log.levels.WARN - ) - local params = util.make_formatting_params(options) - local bufnr = api.nvim_get_current_buf() - select_client('textDocument/formatting', function(client) - if client == nil then - return - end - - return client.request('textDocument/formatting', params, nil, bufnr) - end) -end - ---- Performs |vim.lsp.buf.formatting()| synchronously. ---- ---- Useful for running on save, to make sure buffer is formatted prior to being ---- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example: ---- ----
---- autocmd BufWritePre  lua vim.lsp.buf.formatting_sync()
---- 
---- ----@param options table|nil with valid `FormattingOptions` entries ----@param timeout_ms (number) Request timeout ----@see |vim.lsp.buf.format()| -function M.formatting_sync(options, timeout_ms) - vim.notify_once( - 'vim.lsp.buf.formatting_sync is deprecated. Use vim.lsp.buf.format instead', - vim.log.levels.WARN - ) - local params = util.make_formatting_params(options) - local bufnr = api.nvim_get_current_buf() - select_client('textDocument/formatting', function(client) - if client == nil then - return - end - - local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr) - if result and result.result then - util.apply_text_edits(result.result, bufnr, client.offset_encoding) - elseif err then - vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN) - end - end) -end - ---- Formats the current buffer by sequentially requesting formatting from attached clients. ---- ---- Useful when multiple clients with formatting capability are attached. ---- ---- Since it's synchronous, can be used for running on save, to make sure buffer is formatted ---- prior to being saved. {timeout_ms} is passed on to the |vim.lsp.client| `request_sync` method. ---- Example: ----
---- vim.api.nvim_command[[autocmd BufWritePre  lua vim.lsp.buf.formatting_seq_sync()]]
---- 
---- ----@param options (table|nil) `FormattingOptions` entries ----@param timeout_ms (number|nil) Request timeout ----@param order (table|nil) List of client names. Formatting is requested from clients ----in the following order: first all clients that are not in the `order` list, then ----the remaining clients in the order as they occur in the `order` list. -function M.formatting_seq_sync(options, timeout_ms, order) - vim.notify_once( - 'vim.lsp.buf.formatting_seq_sync is deprecated. Use vim.lsp.buf.format instead', - vim.log.levels.WARN - ) - local clients = vim.tbl_values(vim.lsp.buf_get_clients()) - local bufnr = api.nvim_get_current_buf() - - -- sort the clients according to `order` - for _, client_name in pairs(order or {}) do - -- if the client exists, move to the end of the list - for i, client in pairs(clients) do - if client.name == client_name then - table.insert(clients, table.remove(clients, i)) - break - end - end - end - - -- loop through the clients and make synchronous formatting requests - for _, client in pairs(clients) do - if vim.tbl_get(client.server_capabilities, 'documentFormattingProvider') then - local params = util.make_formatting_params(options) - local result, err = client.request_sync( - 'textDocument/formatting', - params, - timeout_ms, - api.nvim_get_current_buf() - ) - if result and result.result then - util.apply_text_edits(result.result, bufnr, client.offset_encoding) - elseif err then - vim.notify( - string.format('vim.lsp.buf.formatting_seq_sync: (%s) %s', client.name, err), - vim.log.levels.WARN - ) - end - end - end -end - ---- Formats a given range. ---- ----@param options Table with valid `FormattingOptions` entries. ----@param start_pos ({number, number}, optional) mark-indexed position. ----Defaults to the start of the last visual selection. ----@param end_pos ({number, number}, optional) mark-indexed position. ----Defaults to the end of the last visual selection. -function M.range_formatting(options, start_pos, end_pos) - vim.deprecate('vim.lsp.buf.range_formatting', 'vim.lsp.formatexpr or vim.lsp.buf.format', '0.9.0') - local params = util.make_given_range_params(start_pos, end_pos) - params.options = util.make_formatting_params(options).options - select_client('textDocument/rangeFormatting', function(client) - if client == nil then - return - end - - return client.request('textDocument/rangeFormatting', params) - end) -end - --- Renames all references to the symbol under the cursor. --- ---@param new_name string|nil If not provided, the user will be prompted for a new @@ -943,34 +778,6 @@ function M.code_action(options) code_action_request(params, options) end ---- Performs |vim.lsp.buf.code_action()| for a given range. ---- ---- ----@param context table|nil `CodeActionContext` of the LSP specification: ---- - diagnostics: (table|nil) ---- LSP `Diagnostic[]`. Inferred from the current ---- position if not provided. ---- - only: (table|nil) ---- List of LSP `CodeActionKind`s used to filter the code actions. ---- Most language servers support values like `refactor` ---- or `quickfix`. ----@param start_pos ({number, number}, optional) mark-indexed position. ----Defaults to the start of the last visual selection. ----@param end_pos ({number, number}, optional) mark-indexed position. ----Defaults to the end of the last visual selection. -function M.range_code_action(context, start_pos, end_pos) - vim.deprecate('vim.lsp.buf.range_code_action', 'vim.lsp.buf.code_action', '0.9.0') - validate({ context = { context, 't', true } }) - context = context or {} - if not context.diagnostics then - local bufnr = api.nvim_get_current_buf() - context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr) - end - local params = util.make_given_range_params(start_pos, end_pos) - params.context = context - code_action_request(params) -end - --- Executes an LSP server command. --- ---@param command_params table A valid `ExecuteCommandParams` object -- cgit From a5e347ce1df65be37c56e84c2a243eeaeaeeea5f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 1 Oct 2022 23:36:11 +0800 Subject: refactor(diagnostic): remove deprecated function (#20423) --- runtime/lua/vim/diagnostic.lua | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 98dbe0779b..84091fbf0e 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -357,19 +357,6 @@ local function execute_scheduled_display(namespace, bufnr) M.show(namespace, bufnr, nil, args) end ---- @deprecated ---- Callback scheduled when leaving Insert mode. ---- ---- called from the Vimscript autocommand. ---- ---- See @ref schedule_display() ---- ----@private -function M._execute_scheduled_display(namespace, bufnr) - vim.deprecate('vim.diagnostic._execute_scheduled_display', nil, '0.9') - execute_scheduled_display(namespace, bufnr) -end - --- Table of autocmd events to fire the update for displaying new diagnostic information local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' } -- cgit From b075f49d9229b3e58a4d6677ed8e01db60687fa3 Mon Sep 17 00:00:00 2001 From: August Masquelier <31262046+levouh@users.noreply.github.com> Date: Tue, 4 Oct 2022 12:44:19 -0600 Subject: feat(lsp): add bufnr option to lsp.start (#20473) --- runtime/lua/vim/lsp.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index a64facf214..349c662258 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -860,6 +860,9 @@ end --- Used on all running clients. --- The default implementation re-uses a client if name --- and root_dir matches. +--- - bufnr (number) +--- Buffer handle to attach to if starting or re-using a +--- client (0 for current). ---@return number|nil client_id function lsp.start(config, opts) opts = opts or {} @@ -871,7 +874,10 @@ function lsp.start(config, opts) 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 - local bufnr = api.nvim_get_current_buf() + local bufnr = opts.bufnr + 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 _, client in pairs(clients) do if reuse_client(client, config) then -- cgit From 548a4e258777a405cc2b1225cab9a8292924407b Mon Sep 17 00:00:00 2001 From: Elizabeth Paź Date: Wed, 5 Oct 2022 13:21:45 +0200 Subject: docs(docstrings): fix runtime type annotations --- runtime/lua/vim/F.lua | 8 ++++++-- runtime/lua/vim/_editor.lua | 25 ++++++++++++++----------- runtime/lua/vim/shared.lua | 4 +++- 3 files changed, 23 insertions(+), 14 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua index bca5ddf68b..3e370c0a84 100644 --- a/runtime/lua/vim/F.lua +++ b/runtime/lua/vim/F.lua @@ -2,8 +2,12 @@ local F = {} --- Returns {a} if it is not nil, otherwise returns {b}. --- ----@param a ----@param b +---@generic A +---@generic B +--- +---@param a A +---@param b B +---@return A | B function F.if_nil(a, b) if a == nil then return b diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 96a87b8bb9..75df31004f 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -152,14 +152,15 @@ do --- --- ---@see |paste| + ---@alias paste_phase -1 | 1 | 2 | 3 --- - ---@param lines |readfile()|-style list of lines to paste. |channel-lines| - ---@param phase -1: "non-streaming" paste: the call contains all lines. + ---@param lines string[] # |readfile()|-style list of lines to paste. |channel-lines| + ---@param phase paste_phase -1: "non-streaming" paste: the call contains all lines. --- If paste is "streamed", `phase` indicates the stream state: --- - 1: starts the paste (exactly once) --- - 2: continues the paste (zero or more times) --- - 3: ends the paste (exactly once) - ---@returns false if client should cancel the paste. + ---@returns boolean # false if client should cancel the paste. function vim.paste(lines, phase) local now = vim.loop.now() local is_first_chunk = phase < 2 @@ -255,6 +256,8 @@ end ---@see |lua-loop-callbacks| ---@see |vim.schedule()| ---@see |vim.in_fast_event()| +---@param cb function +---@return function function vim.schedule_wrap(cb) return function(...) local args = vim.F.pack_len(...) @@ -399,11 +402,11 @@ end --- Get a table of lines with start, end columns for a region marked by two points --- ---@param bufnr number of buffer ----@param pos1 (line, column) tuple marking beginning of region ----@param pos2 (line, column) tuple marking end of region ----@param regtype type of selection, see |setreg()| +---@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 region lua table of the form {linenr = {startcol,endcol}} +---@return table region lua table of the form {linenr = {startcol,endcol}} function vim.region(bufnr, pos1, pos2, regtype, inclusive) if not vim.api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -450,9 +453,9 @@ end --- 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 Callback to call once `timeout` expires ----@param timeout Number of milliseconds to wait before calling `fn` ----@return timer luv timer object +---@param fn function Callback to call once `timeout` expires +---@param timeout integer Number of milliseconds to wait before calling `fn` +---@return table timer luv timer object function vim.defer_fn(fn, timeout) vim.validate({ fn = { fn, 'c', true } }) local timer = vim.loop.new_timer() @@ -758,7 +761,7 @@ end --- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) --- ---@see |vim.inspect()| ----@return given arguments. +---@return any # given arguments. function vim.pretty_print(...) local objects = {} for i = 1, select('#', ...) do diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index c5c31b6ddf..63a932479e 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -107,9 +107,11 @@ end --- ---@see |vim.gsplit()| --- +---@alias split_kwargs {plain: boolean, trimempty: boolean} | boolean | nil +--- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs table Keyword arguments: +---@param kwargs split_kwargs 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 -- cgit From c8d1b9a2d6ef910a9c1993875723387d522e6c4a Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Tue, 27 Sep 2022 22:44:01 +0200 Subject: docs: added proper annotations to functions in shared.lua --- runtime/lua/vim/shared.lua | 55 +++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 23 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 63a932479e..1d0a6d5406 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -14,8 +14,9 @@ local vim = vim or {} --- same functions as those in the input table. Userdata and threads are not --- copied and will throw an error. --- ----@param orig table Table to copy ----@return table Table of copied keys and (nested) values. +---@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) @@ -62,7 +63,7 @@ end)() ---@param s string String to split ---@param sep string Separator or pattern ---@param plain boolean If `true` use `sep` literally (passed to string.find) ----@return function Iterator over the split components +---@return fun():string Iterator over the split components function vim.gsplit(s, sep, plain) vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } }) @@ -111,11 +112,11 @@ end --- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs split_kwargs Keyword arguments: +---@param kwargs {plain: boolean, trimempty: boolean}|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 ----@return table List of split components +---@return string[] List of split components function vim.split(s, sep, kwargs) local plain local trimempty = false @@ -158,8 +159,9 @@ end --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@param t table Table ----@return table List of keys +---@param t table Table +---@generic T: table +---@return T[] List of keys function vim.tbl_keys(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -173,8 +175,9 @@ end --- Return a list of all values used in a table. --- However, the order of the return table of values is not guaranteed. --- ----@param t table Table ----@return table List of values +---@generic T +---@param t table Table +---@return T[] List of values function vim.tbl_values(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -187,8 +190,9 @@ end --- Apply a function to all values of a table. --- ----@param func function|table Function or callable table ----@param t table Table +---@generic T +---@param func fun(value: T): any Function +---@param t table Table ---@return table Table of transformed values function vim.tbl_map(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) @@ -202,9 +206,10 @@ end --- Filter a table using a predicate function --- ----@param func function|table Function or callable table ----@param t table Table ----@return table Table of filtered values +---@generic T +---@param func fun(value: T): boolean Function +---@param t table Table +---@return T[] Table of filtered values function vim.tbl_filter(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) @@ -306,12 +311,14 @@ end --- ---@see |vim.tbl_extend()| --- ----@param behavior string Decides what to do if a key is found in more than one map: +---@generic T1: table +---@generic T2: table +---@param behavior "error"|"keep"|"force" Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map ----@param ... table Two or more map-like tables ----@return table Merged table +---@param ... T2 Two or more map-like tables +---@return T1|T2 Merged table function vim.tbl_deep_extend(behavior, ...) return tbl_extend(behavior, true, ...) end @@ -407,11 +414,12 @@ end --- ---@see |vim.tbl_extend()| --- ----@param dst table List which will be modified and appended to +---@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 Start index on src. Defaults to 1 ----@param finish number Final index on src. Defaults to `#src` ----@return table dst +---@param start number|nil Start index on src. Defaults to 1 +---@param finish number|nil Final index on src. Defaults to `#src` +---@return T dst function vim.list_extend(dst, src, start, finish) vim.validate({ dst = { dst, 't' }, @@ -506,10 +514,11 @@ end --- Creates a copy of a table containing only elements from start to end (inclusive) --- ----@param list table Table +---@generic T +---@param list T[] Table ---@param start number Start range of slice ---@param finish number End range of slice ----@return table Copy of table sliced from start to finish (inclusive) +---@return T[] Copy of table sliced from start to finish (inclusive) function vim.list_slice(list, start, finish) local new_list = {} for i = start or 1, finish or #list do -- cgit From 24a1c7f556bba35a9c31c2fdd19cf4b8c00a4395 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Wed, 28 Sep 2022 09:11:21 +0200 Subject: feat: added support for optional params to lua2dox --- runtime/lua/vim/shared.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 1d0a6d5406..5566bd5e35 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -417,8 +417,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? number Start index on src. Defaults to 1 +---@param finish? number Final index on src. Defaults to `#src` ---@return T dst function vim.list_extend(dst, src, start, finish) vim.validate({ -- cgit From 1da7b4eb699fb04cc97dec389470fd0fbd64091d Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Wed, 28 Sep 2022 13:22:08 +0200 Subject: feat: added support for specifying types for lua2dox --- runtime/lua/vim/shared.lua | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 5566bd5e35..fa2af57cfc 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -63,7 +63,7 @@ end)() ---@param s string String to split ---@param sep string Separator or pattern ---@param plain boolean If `true` use `sep` literally (passed to string.find) ----@return fun():string Iterator over the split components +---@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 } }) @@ -112,7 +112,7 @@ end --- ---@param s string String to split ---@param sep string Separator or pattern ----@param kwargs {plain: boolean, trimempty: boolean}|nil Keyword arguments: +---@param kwargs? {plain: boolean, trimempty: boolean} (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 @@ -159,9 +159,9 @@ end --- ---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua --- ----@param t table Table +---@param t table (table) Table ---@generic T: table ----@return T[] List of keys +---@return T[] (list) List of keys function vim.tbl_keys(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -176,8 +176,8 @@ end --- However, the order of the return table of values is not guaranteed. --- ---@generic T ----@param t table Table ----@return T[] List of values +---@param t table (table) Table +---@return T[] (list) List of values function vim.tbl_values(t) assert(type(t) == 'table', string.format('Expected table, got %s', type(t))) @@ -191,8 +191,8 @@ end --- Apply a function to all values of a table. --- ---@generic T ----@param func fun(value: T): any Function ----@param t table Table +---@param func fun(value: T): any (function) Function +---@param t table (table) Table ---@return table Table of transformed values function vim.tbl_map(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) @@ -207,9 +207,9 @@ end --- Filter a table using a predicate function --- ---@generic T ----@param func fun(value: T): boolean Function ----@param t table Table ----@return T[] Table of filtered values +---@param func fun(value: T): boolean (function) Function +---@param t table (table) Table +---@return T[] (table) Table of filtered values function vim.tbl_filter(func, t) vim.validate({ func = { func, 'c' }, t = { t, 't' } }) @@ -313,12 +313,12 @@ end --- ---@generic T1: table ---@generic T2: table ----@param behavior "error"|"keep"|"force" Decides what to do if a key is found in more than one map: +---@param behavior "error"|"keep"|"force" (string) Decides what to do if a key is found in more than one map: --- - "error": raise an error --- - "keep": use value from the leftmost map --- - "force": use value from the rightmost map ---@param ... T2 Two or more map-like tables ----@return T1|T2 Merged table +---@return T1|T2 (table) Merged table function vim.tbl_deep_extend(behavior, ...) return tbl_extend(behavior, true, ...) end @@ -515,10 +515,10 @@ end --- Creates a copy of a table containing only elements from start to end (inclusive) --- ---@generic T ----@param list T[] Table +---@param list T[] (list) Table ---@param start number Start range of slice ---@param finish number End range of slice ----@return T[] Copy of table sliced from start to finish (inclusive) +---@return T[] (list) Copy of table sliced from start to finish (inclusive) function vim.list_slice(list, start, finish) local new_list = {} for i = start or 1, finish or #list do -- cgit From 0773a9ee3a21db54cd6b2376dd2e087bc09d5ea1 Mon Sep 17 00:00:00 2001 From: lvimuser <109605931+lvimuser@users.noreply.github.com> Date: Sat, 8 Oct 2022 05:22:25 -0300 Subject: feat(lsp): support window/showDocument (#19977) --- runtime/lua/vim/lsp/handlers.lua | 46 ++++++++++++++++++++ runtime/lua/vim/lsp/protocol.lua | 2 +- runtime/lua/vim/lsp/util.lua | 93 ++++++++++++++++++++++++++++------------ 3 files changed, 113 insertions(+), 28 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index a32c59dd17..d7d9ca7ce9 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -512,6 +512,52 @@ M['window/showMessage'] = function(_, result, ctx, _) return result end +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument +M['window/showDocument'] = function(_, result, ctx, _) + local uri = result.uri + + 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', '""', vim.fn.shellescape(uri) } + elseif vim.fn.has('macunix') == 1 then + cmd = { 'open', vim.fn.shellescape(uri) } + else + cmd = { 'xdg-open', vim.fn.shellescape(uri) } + end + + local ret = vim.fn.system(cmd) + if vim.v.shellerror ~= 0 then + return { + success = false, + error = { + code = protocol.ErrorCodes.UnknownErrorCode, + message = ret, + }, + } + end + + return { success = true } + end + + 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) + if not client then + err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method }) + return vim.NIL + end + + local location = { + uri = uri, + range = result.selection, + } + + local success = util.show_document(location, client.offset_encoding, true, result.takeFocus) + return { success = success or false } +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 27da60b4ae..4034753322 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -778,7 +778,7 @@ function protocol.make_client_capabilities() }, }, showDocument = { - support = false, + support = true, }, }, } diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 88667caf68..617d33f88c 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -110,6 +110,15 @@ local function split_lines(value) return split(value, '\n', true) end +---@private +local function create_window_without_focus() + local prev = vim.api.nvim_get_current_win() + vim.cmd.new() + local new = vim.api.nvim_get_current_win() + vim.api.nvim_set_current_win(prev) + return new +end + --- Convert byte index to `encoding` index. --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed @@ -1056,50 +1065,80 @@ function M.make_floating_popup_options(width, height, opts) } end ---- Jumps to a location. +--- Shows document and optionally jumps to the location. --- ---@param location table (`Location`|`LocationLink`) ----@param offset_encoding string utf-8|utf-16|utf-32 (required) ----@param reuse_win boolean Jump to existing window if buffer is already opened. ----@returns `true` if the jump succeeded -function M.jump_to_location(location, offset_encoding, reuse_win) +---@param offset_encoding "utf-8" | "utf-16" | "utf-32" +---@param opts table 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. +---@return boolean `true` if succeeded +function M.show_document(location, offset_encoding, opts) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then - return + return false end if offset_encoding == nil then - vim.notify_once( - 'jump_to_location must be called with valid offset encoding', - vim.log.levels.WARN - ) + vim.notify_once('show_document must be called with valid offset encoding', vim.log.levels.WARN) end local bufnr = vim.uri_to_bufnr(uri) - -- Save position in jumplist - vim.cmd("normal! m'") - -- Push a new item into tagstack - local from = { vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0 } - local items = { { tagname = vim.fn.expand(''), from = from } } - vim.fn.settagstack(vim.fn.win_getid(), { items = items }, 't') + opts = opts or {} + local focus = vim.F.if_nil(opts.focus, true) + if focus then + -- Save position in jumplist + vim.cmd("normal! m'") - --- Jump to new location (adjusting for UTF-16 encoding of characters) - local win = reuse_win and bufwinid(bufnr) - if win then + -- Push a new item into tagstack + local from = { vim.fn.bufnr('%'), vim.fn.line('.'), vim.fn.col('.'), 0 } + local items = { { tagname = vim.fn.expand(''), from = from } } + vim.fn.settagstack(vim.fn.win_getid(), { items = items }, 't') + end + + local win = opts.reuse_win and bufwinid(bufnr) + or focus and api.nvim_get_current_win() + or create_window_without_focus() + + api.nvim_buf_set_option(bufnr, 'buflisted', true) + api.nvim_win_set_buf(win, bufnr) + if focus then api.nvim_set_current_win(win) - else - api.nvim_buf_set_option(bufnr, 'buflisted', true) - api.nvim_set_current_buf(bufnr) end + + -- location may be Location or LocationLink local range = location.range or location.targetSelectionRange - local row = range.start.line - local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) - api.nvim_win_set_cursor(0, { row + 1, col }) - -- Open folds under the cursor - vim.cmd('normal! zv') + if range then + --- Jump to new location (adjusting for encoding of characters) + local row = range.start.line + local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) + api.nvim_win_set_cursor(win, { row + 1, col }) + api.nvim_win_call(win, function() + -- Open folds under the cursor + vim.cmd('normal! zv') + end) + end + return true end +--- Jumps to a location. +--- +---@param location table (`Location`|`LocationLink`) +---@param offset_encoding "utf-8" | "utf-16" | "utf-32" +---@param reuse_win boolean 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) + if offset_encoding == nil then + vim.notify_once( + 'jump_to_location must be called with valid offset encoding', + vim.log.levels.WARN + ) + end + + return M.show_document(location, offset_encoding, { reuse_win = reuse_win, focus = true }) +end + --- Previews a location in a floating window --- --- behavior depends on type of location: -- cgit From b05e10ef7265c257be79e6888cbf87bb6ba86845 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Sat, 8 Oct 2022 18:46:31 +0300 Subject: vim-patch:9.0.0692: PoE filter files are not recognized (#20542) Problem: PoE filter files are not recognized. Solution: Add a pattern to detect PoE filter files. (closes vim/vim#11305) https://github.com/vim/vim/commit/b7f52f5659c68b61ccc645ef866f8fd82361cd26 --- runtime/lua/vim/filetype.lua | 1 + 1 file changed, 1 insertion(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index b61fc528f5..36ea97fb21 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -774,6 +774,7 @@ local extension = { po = 'po', pot = 'po', pod = 'pod', + filter = 'poefilter', pk = 'poke', ps = 'postscr', epsi = 'postscr', -- cgit From 8c2226fc30931690186390d86f963cd43e6947ef Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 9 Oct 2022 12:40:56 +0200 Subject: fix(lua): properly configure luacheck and remove `local vim = ...` lines (#20551) --- runtime/lua/vim/_editor.lua | 2 -- runtime/lua/vim/_init_packages.lua | 3 --- runtime/lua/vim/_meta.lua | 3 --- runtime/lua/vim/lsp.lua | 1 - runtime/lua/vim/lsp/buf.lua | 1 - runtime/lua/vim/lsp/handlers.lua | 1 - runtime/lua/vim/lsp/rpc.lua | 1 - runtime/lua/vim/lsp/util.lua | 1 - runtime/lua/vim/shared.lua | 2 +- 9 files changed, 1 insertion(+), 14 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 75df31004f..ef1a07b267 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -36,8 +36,6 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util -local vim = assert(vim) - -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c for k, v in pairs({ diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua index 7e3c73667e..19c8608732 100644 --- a/runtime/lua/vim/_init_packages.lua +++ b/runtime/lua/vim/_init_packages.lua @@ -1,6 +1,3 @@ --- prevents luacheck from making lints for setting things on vim -local vim = assert(vim) - local pathtrails = {} vim._so_trails = {} for s in (package.cpath .. ';'):gmatch('[^;]*;') do diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index dada918d69..9c7972873e 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -1,6 +1,3 @@ --- prevents luacheck from making lints for setting things on vim -local vim = assert(vim) - local a = vim.api -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 349c662258..199964e24e 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -5,7 +5,6 @@ local protocol = require('vim.lsp.protocol') local util = require('vim.lsp.util') local sync = require('vim.lsp.sync') -local vim = vim 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, diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8550fe253b..c593e72d62 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -1,4 +1,3 @@ -local vim = vim local api = vim.api local validate = vim.validate local util = require('vim.lsp.util') diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index d7d9ca7ce9..93fd621161 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -1,7 +1,6 @@ local log = require('vim.lsp.log') local protocol = require('vim.lsp.protocol') local util = require('vim.lsp.util') -local vim = vim local api = vim.api local M = {} diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 842a0ce329..ff62623544 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -1,4 +1,3 @@ -local vim = vim local uv = vim.loop local log = require('vim.lsp.log') local protocol = require('vim.lsp.protocol') diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 617d33f88c..b0f9c1660e 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -1,6 +1,5 @@ local protocol = require('vim.lsp.protocol') local snippet = require('vim.lsp._snippet') -local vim = vim local validate = vim.validate local api = vim.api local list_extend = vim.list_extend diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index fa2af57cfc..4b3a681eb2 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -6,7 +6,7 @@ -- or the test suite. (Eventually the test suite will be run in a worker process, -- so this wouldn't be a separate case to consider) -local vim = vim or {} +vim = vim or {} --- 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. -- cgit From 09dffb9db7d16496e55e86f78ab60241533d86f6 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 9 Oct 2022 08:21:52 -0400 Subject: docs: various #12823 - increase python line-length limit from 88 => 100. - gen_help_html: fix bug in "tag" case (tbl_count => tbl_contains) ref #15632 fix #18215 fix #18479 fix #20527 fix #20532 Co-authored-by: Ben Weedon --- runtime/lua/vim/_editor.lua | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index ef1a07b267..28e5897aa9 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -12,21 +12,8 @@ -- Guideline: "If in doubt, put it in the runtime". -- -- Most functions should live directly in `vim.`, not in submodules. --- The only "forbidden" names are those claimed by legacy `if_lua`: --- $ vim --- :lua for k,v in pairs(vim) do print(k) end --- buffer --- open --- window --- lastline --- firstline --- type --- line --- eval --- dict --- beep --- list --- command +-- +-- Compatibility with Vim's `if_lua` is explicitly a non-goal. -- -- Reference (#6580): -- - https://github.com/luafun/luafun @@ -120,9 +107,7 @@ function vim._os_proc_children(ppid) return children end --- TODO(ZyX-I): Create compatibility layer. - ---- Return a human-readable representation of the given object. +--- Gets a human-readable representation of the given object. --- ---@see https://github.com/kikito/inspect.lua ---@see https://github.com/mpeterv/vinspect -- cgit From a7a83bc4c25d63f3ae0a7a56e5211df1444699c4 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Sun, 9 Oct 2022 18:19:43 +0200 Subject: fix(docs-html): update parser - Improve generated HTML by updating parser which includes fixes for single "'" and single "|": https://github.com/neovim/tree-sitter-vimdoc/pull/31 - Updated parser also fixes the conceal issue for "help" highlight queries https://github.com/neovim/tree-sitter-vimdoc/issues/23 by NOT including whitespace in nodes. - But this means we need to restore the getws() function which scrapes leading whitespace from the original input (buffer). --- runtime/lua/vim/shared.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 4b3a681eb2..f03d608e56 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -100,10 +100,10 @@ end --- --- Examples: ---
----  split(":aa::b:", ":")     --> {'','aa','','b',''}
----  split("axaby", "ab?")     --> {'','x','y'}
----  split("x*yz*o", "*", {plain=true})  --> {'x','yz','o'}
----  split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
+---  split(":aa::b:", ":")     => {'','aa','','b',''}
+---  split("axaby", "ab?")     => {'','x','y'}
+---  split("x*yz*o", "*", {plain=true})  => {'x','yz','o'}
+---  split("|x|y|z|", "|", {trimempty=true}) => {'x', 'y', 'z'}
 --- 
--- ---@see |vim.gsplit()| -- cgit From 4ccc57fd7a1445770a6cd7c85aa76a3a8928295b Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Mon, 10 Oct 2022 15:34:36 +0100 Subject: feat(man): add health check Fixes #20432 --- runtime/lua/man/health.lua | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 runtime/lua/man/health.lua (limited to 'runtime/lua') diff --git a/runtime/lua/man/health.lua b/runtime/lua/man/health.lua new file mode 100644 index 0000000000..11d7148216 --- /dev/null +++ b/runtime/lua/man/health.lua @@ -0,0 +1,20 @@ +local M = {} + +local report_ok = vim.fn['health#report_ok'] +local report_error = vim.fn['health#report_error'] + +local function check_runtime_file(name) + local path = vim.env.VIMRUNTIME .. '/' .. name + if vim.loop.fs_stat(path) then + report_error(string.format('%s detected. Please delete %s', name, path)) + else + report_ok(string.format('%s not in $VIMRUNTIME', name)) + end +end + +function M.check() + check_runtime_file('plugin/man.vim') + check_runtime_file('autoload/man.vim') +end + +return M -- cgit From 8781213f00a22e20abeb4282204e900db799f4b5 Mon Sep 17 00:00:00 2001 From: Christian Clason Date: Mon, 10 Oct 2022 17:51:31 +0200 Subject: vim-patch:9.0.0711: SubStation Alpha files are not recognized (#20577) Problem: SubStation Alpha files are not recognized. Solution: Add patterns for SubStation Alpha files. (closes vim/vim#11332) https://github.com/vim/vim/commit/084f2620ec7d08d6043de30436197c002fffe3ec --- runtime/lua/vim/filetype.lua | 2 ++ 1 file changed, 2 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 36ea97fb21..e204fe8ae2 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -970,6 +970,8 @@ local extension = { mot = 'srec', ['s19'] = 'srec', srt = 'srt', + ssa = 'ssa', + ass = 'ssa', st = 'st', imata = 'stata', ['do'] = 'stata', -- cgit From cba05c541d613dc05916b262c0d7d3a10ec08c67 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 11 Oct 2022 13:25:42 +0100 Subject: refactor(man): pass env directly to spawn() (#20591) --- runtime/lua/man.lua | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 88535321ac..8ca1bb90f5 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -12,7 +12,7 @@ local function man_error(msg) end -- Run a system command and timeout after 30 seconds. -local function man_system(cmd, silent) +local function man_system(args, silent, env) local stdout_data = {} local stderr_data = {} local stdout = vim.loop.new_pipe(false) @@ -22,9 +22,10 @@ local function man_system(cmd, silent) local exit_code local handle - handle = vim.loop.spawn(cmd[1], { - args = vim.list_slice(cmd, 2), + handle = vim.loop.spawn('man', { + args = args, stdio = { nil, stdout, stderr }, + env = env, }, function(code) exit_code = code stdout:close() @@ -44,7 +45,8 @@ local function man_system(cmd, silent) stdout:close() stderr:close() if not silent then - man_error(string.format('command error: %s', table.concat(cmd))) + local cmd = table.concat({ 'man', unpack(args) }, ' ') + man_error(string.format('command error: %s', cmd)) end end @@ -58,13 +60,13 @@ local function man_system(cmd, silent) stdout:close() stderr:close() end - man_error(string.format('command timed out: %s', table.concat(cmd, ' '))) + local cmd = table.concat({ 'man', unpack(args) }, ' ') + man_error(string.format('command timed out: %s', cmd)) end if exit_code ~= 0 and not silent then - man_error( - string.format("command error '%s': %s", table.concat(cmd, ' '), table.concat(stderr_data)) - ) + local cmd = table.concat({ 'man', unpack(args) }, ' ') + man_error(string.format("command error '%s': %s", cmd, table.concat(stderr_data))) end return table.concat(stdout_data) @@ -267,19 +269,15 @@ local function get_path(sect, name, silent) -- -- Finally, we can avoid relying on -S or -s here since they are very -- inconsistently supported. Instead, call -w with a section and a name. - local cmd + local args if sect == '' then - cmd = { 'man', find_arg, name } + args = { find_arg, name } else - cmd = { 'man', find_arg, sect, name } + args = { find_arg, sect, name } end - local lines = man_system(cmd, silent) - if lines == nil then - return nil - end - - local results = vim.split(lines, '\n', { trimempty = true }) + local lines = man_system(args, silent) + local results = vim.split(lines or {}, '\n', { trimempty = true }) if #results == 0 then return @@ -435,15 +433,17 @@ local function get_page(path, silent) else manwidth = api.nvim_win_get_width(0) end + + local args = localfile_arg and { '-l', path } or { path } + -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). -- http://comments.gmane.org/gmane.editors.vim.devel/29085 -- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. - local cmd = { 'env', 'MANPAGER=cat', 'MANWIDTH=' .. manwidth, 'MAN_KEEP_FORMATTING=1', 'man' } - if localfile_arg then - cmd[#cmd + 1] = '-l' - end - cmd[#cmd + 1] = path - return man_system(cmd, silent) + return man_system(args, silent, { + 'MANPAGER=cat', + 'MANWIDTH=' .. manwidth, + 'MAN_KEEP_FORMATTING=1', + }) end local function put_page(page) @@ -484,7 +484,7 @@ local function get_paths(sect, name, do_fallback) -- callers must try-catch this, as some `man` implementations don't support `s:find_arg` local ok, ret = pcall(function() local mandirs = - table.concat(vim.split(man_system({ 'man', find_arg }), '[:\n]', { trimempty = true }), ',') + table.concat(vim.split(man_system({ find_arg }), '[:\n]', { trimempty = true }), ',') local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) pcall(function() -- Prioritize the result from verify_exists as it obeys b:man_default_sects. -- cgit From b126b118405b90c30c47119862e6c040738ac540 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 11 Oct 2022 13:25:51 +0100 Subject: fix(man): support MacOS 13 MacOS 13 has changed its version of `man` to an version that doesn't properly support `man -w` (without arguments). In order to workaround this we simply fallback to $MANPATH. Fixes #20579 --- runtime/lua/man.lua | 94 ++++++++++++++++++++++++++++------------------------- 1 file changed, 49 insertions(+), 45 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua index 8ca1bb90f5..6477786dbb 100644 --- a/runtime/lua/man.lua +++ b/runtime/lua/man.lua @@ -1,6 +1,6 @@ local api, fn = vim.api, vim.fn -local find_arg = '-w' +local FIND_ARG = '-w' local localfile_arg = true -- Always use -l if possible. #6683 local buf_hls = {} @@ -12,7 +12,7 @@ local function man_error(msg) end -- Run a system command and timeout after 30 seconds. -local function man_system(args, silent, env) +local function system(cmd, silent, env) local stdout_data = {} local stderr_data = {} local stdout = vim.loop.new_pipe(false) @@ -22,8 +22,8 @@ local function man_system(args, silent, env) local exit_code local handle - handle = vim.loop.spawn('man', { - args = args, + handle = vim.loop.spawn(cmd[1], { + args = vim.list_slice(cmd, 2), stdio = { nil, stdout, stderr }, env = env, }, function(code) @@ -45,8 +45,8 @@ local function man_system(args, silent, env) stdout:close() stderr:close() if not silent then - local cmd = table.concat({ 'man', unpack(args) }, ' ') - man_error(string.format('command error: %s', cmd)) + local cmd_str = table.concat(cmd, ' ') + man_error(string.format('command error: %s', cmd_str)) end end @@ -60,13 +60,13 @@ local function man_system(args, silent, env) stdout:close() stderr:close() end - local cmd = table.concat({ 'man', unpack(args) }, ' ') - man_error(string.format('command timed out: %s', cmd)) + local cmd_str = table.concat(cmd, ' ') + man_error(string.format('command timed out: %s', cmd_str)) end if exit_code ~= 0 and not silent then - local cmd = table.concat({ 'man', unpack(args) }, ' ') - man_error(string.format("command error '%s': %s", cmd, table.concat(stderr_data))) + local cmd_str = table.concat(cmd, ' ') + man_error(string.format("command error '%s': %s", cmd_str, table.concat(stderr_data))) end return table.concat(stdout_data) @@ -269,14 +269,14 @@ local function get_path(sect, name, silent) -- -- Finally, we can avoid relying on -S or -s here since they are very -- inconsistently supported. Instead, call -w with a section and a name. - local args + local cmd if sect == '' then - args = { find_arg, name } + cmd = { 'man', FIND_ARG, name } else - args = { find_arg, sect, name } + cmd = { 'man', FIND_ARG, sect, name } end - local lines = man_system(args, silent) + local lines = system(cmd, silent) local results = vim.split(lines or {}, '\n', { trimempty = true }) if #results == 0 then @@ -342,7 +342,7 @@ end -- 2. If it still could not be found, then we try again without a section. -- 3. If still not found but $MANSECT is set, then we try again with $MANSECT -- unset. -local function verify_exists(sect, name) +local function verify_exists(sect, name, silent) if sect and sect ~= '' then local ret = get_path(sect, name, true) if ret then @@ -378,8 +378,10 @@ local function verify_exists(sect, name) end end - -- finally, if that didn't work, there is no hope - man_error('no manual entry for ' .. name) + if not silent then + -- finally, if that didn't work, there is no hope + man_error('no manual entry for ' .. name) + end end local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]]) @@ -434,12 +436,12 @@ local function get_page(path, silent) manwidth = api.nvim_win_get_width(0) end - local args = localfile_arg and { '-l', path } or { path } + local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path } -- Force MANPAGER=cat to ensure Vim is not recursively invoked (by man-db). -- http://comments.gmane.org/gmane.editors.vim.devel/29085 -- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces. - return man_system(args, silent, { + return system(cmd, silent, { 'MANPAGER=cat', 'MANWIDTH=' .. manwidth, 'MAN_KEEP_FORMATTING=1', @@ -480,38 +482,40 @@ local function format_candidate(path, psect) return '' end -local function get_paths(sect, name, do_fallback) - -- callers must try-catch this, as some `man` implementations don't support `s:find_arg` - local ok, ret = pcall(function() - local mandirs = - table.concat(vim.split(man_system({ find_arg }), '[:\n]', { trimempty = true }), ',') - local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) - pcall(function() - -- Prioritize the result from verify_exists as it obeys b:man_default_sects. - local first = verify_exists(sect, name) - paths = vim.tbl_filter(function(v) - return v ~= first - end, paths) - paths = { first, unpack(paths) } - end) - return paths - end) +local function move_elem_to_head(list, elem) + local list1 = vim.tbl_filter(function(v) + return v ~= elem + end, list) + return { elem, unpack(list1) } +end - if not ok then - if not do_fallback then - error(ret) - end +local function get_paths(sect, name) + -- Try several sources for getting the list man directories: + -- 1. `man -w` (works on most systems) + -- 2. `manpath` + -- 3. $MANPATH + local mandirs_raw = vim.F.npcall(system, { 'man', FIND_ARG }) + or vim.F.npcall(system, { 'manpath', '-q' }) + or vim.env.MANPATH - -- Fallback to a single path, with the page we're trying to find. - ok, ret = pcall(verify_exists, sect, name) + if not mandirs_raw then + man_error("Could not determine man directories from: 'man -w', 'manpath' or $MANPATH") + end - return { ok and ret or nil } + local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',') + local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true) + + -- Prioritize the result from verify_exists as it obeys b:man_default_sects. + local first = verify_exists(sect, name, true) + if first then + paths = move_elem_to_head(paths, first) end - return ret or {} + + return paths end local function complete(sect, psect, name) - local pages = get_paths(sect, name, false) + local pages = get_paths(sect, name) -- We remove duplicates in case the same manpage in different languages was found. return fn.uniq(fn.sort(vim.tbl_map(function(v) return format_candidate(v, psect) @@ -587,7 +591,7 @@ end function M.goto_tag(pattern, _, _) local sect, name = extract_sect_and_name_ref(pattern) - local paths = get_paths(sect, name, true) + local paths = get_paths(sect, name) local structured = {} for _, path in ipairs(paths) do -- cgit