diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 17:57:01 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2023-01-25 17:57:01 +0000 |
commit | 9837de570c5972f98e74848edc97c297a13136ea (patch) | |
tree | cc948611912d116a3f98a744e690d3d7b6e2f59a /runtime/lua/vim/treesitter | |
parent | c367400b73d207833d51e09d663f969ffab37531 (diff) | |
parent | 3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff) | |
download | rneovim-9837de570c5972f98e74848edc97c297a13136ea.tar.gz rneovim-9837de570c5972f98e74848edc97c297a13136ea.tar.bz2 rneovim-9837de570c5972f98e74848edc97c297a13136ea.zip |
Merge remote-tracking branch 'upstream/master' into colorcolchar
Diffstat (limited to 'runtime/lua/vim/treesitter')
-rw-r--r-- | runtime/lua/vim/treesitter/health.lua | 19 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 30 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/playground.lua | 186 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/query.lua | 14 |
5 files changed, 232 insertions, 21 deletions
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index 4995c80a02..c0a1eca0ce 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -1,5 +1,6 @@ local M = {} local ts = vim.treesitter +local health = require('vim.health') --- Lists the parsers currently installed --- @@ -10,27 +11,23 @@ end --- Performs a healthcheck for treesitter integration function M.check() - local report_info = vim.fn['health#report_info'] - local report_ok = vim.fn['health#report_ok'] - local report_error = vim.fn['health#report_error'] local parsers = M.list_parsers() - report_info(string.format('Runtime ABI version : %d', ts.language_version)) + health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version)) for _, parser in pairs(parsers) do local parsername = vim.fn.fnamemodify(parser, ':t:r') - local is_loadable, ret = pcall(ts.language.require_language, parsername) - if not is_loadable then - report_error(string.format('Impossible to load parser for %s: %s', parsername, ret)) + if not is_loadable or not ret then + health.report_error( + string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?') + ) elseif ret then local lang = ts.language.inspect_language(parsername) - report_ok( - string.format('Loaded parser for %s: ABI version %d', parsername, lang._abi_version) + health.report_ok( + string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser) ) - else - report_error(string.format('Unable to load parser for %s', parsername)) end end end diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 83a26aff13..d77a0d0d03 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -88,7 +88,10 @@ function TSHighlighter.new(tree, opts) end end + self.orig_spelloptions = vim.bo[self.bufnr].spelloptions + vim.bo[self.bufnr].syntax = '' + vim.b[self.bufnr].ts_highlight = true TSHighlighter.active[self.bufnr] = self @@ -114,6 +117,14 @@ function TSHighlighter:destroy() if TSHighlighter.active[self.bufnr] then TSHighlighter.active[self.bufnr] = nil end + + if vim.api.nvim_buf_is_loaded(self.bufnr) then + vim.bo[self.bufnr].spelloptions = self.orig_spelloptions + vim.b[self.bufnr].ts_highlight = nil + if vim.g.syntax_on == 1 then + a.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr }) + end + end end ---@private @@ -164,7 +175,7 @@ function TSHighlighter:get_query(lang) end ---@private -local function on_line_impl(self, buf, line, spell) +local function on_line_impl(self, buf, line, is_spell_nav) self.tree:for_each_tree(function(tstree, tree) if not tstree then return @@ -201,17 +212,26 @@ local function on_line_impl(self, buf, line, spell) local start_row, start_col, end_row, end_col = node:range() local hl = highlighter_query.hl_cache[capture] - local is_spell = highlighter_query:query().captures[capture] == 'spell' + local capture_name = highlighter_query:query().captures[capture] + local spell = nil + if capture_name == 'spell' then + spell = true + elseif capture_name == 'nospell' then + spell = false + end + + -- Give nospell a higher priority so it always overrides spell captures. + local spell_pri_offset = capture_name == 'nospell' and 1 or 0 - if hl and end_row >= line and (not spell or is_spell) then + if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then a.nvim_buf_set_extmark(buf, ns, start_row, start_col, { end_line = end_row, end_col = end_col, hl_group = hl, ephemeral = true, - priority = tonumber(metadata.priority) or 100, -- Low but leaves room below + priority = (tonumber(metadata.priority) or 100) + spell_pri_offset, -- Low but leaves room below conceal = metadata.conceal, - spell = is_spell, + spell = spell, }) end if start_row > line then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index e9d70c4204..a1e96f8ef2 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -608,7 +608,9 @@ end ---@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)) + if tree then + return tree:root():named_descendant_for_range(unpack(range)) + end end --- Gets the appropriate language that contains {range}. diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua new file mode 100644 index 0000000000..bb073290c6 --- /dev/null +++ b/runtime/lua/vim/treesitter/playground.lua @@ -0,0 +1,186 @@ +local api = vim.api + +local M = {} + +---@class Playground +---@field ns number API namespace +---@field opts table Options table with the following keys: +--- - anon (boolean): If true, display anonymous nodes +--- - lang (boolean): If true, display the language alongside each node +--- +---@class Node +---@field id number Node id +---@field text string Node text +---@field named boolean True if this is a named (non-anonymous) node +---@field depth number Depth of the node within the tree +---@field lnum number Beginning line number of this node in the source buffer +---@field col number Beginning column number of this node in the source buffer +---@field end_lnum number Final line number of this node in the source buffer +---@field end_col number Final column number of this node in the source buffer +---@field lang string Source language of this node + +--- Traverse all child nodes starting at {node}. +--- +--- This is a recursive function. The {depth} parameter indicates the current recursion level. +--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed +--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order +--- they were visited. +--- +--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each +--- injected language has a series of trees nested within the primary language's tree, and the root +--- node of each of these trees is contained within a node in the primary tree. The {injections} +--- table maps nodes in the primary tree to root nodes of injected trees. +--- +---@param node userdata Starting node to begin traversal |tsnode| +---@param depth number Current recursion depth +---@param lang string Language of the tree currently being traversed +---@param injections table Mapping of node ids to root nodes of injected language trees (see +--- explanation above) +---@param tree Node[] Output table containing a list of tables each representing a node in the tree +---@private +local function traverse(node, depth, lang, injections, tree) + local injection = injections[node:id()] + if injection then + traverse(injection.root, depth, injection.lang, injections, tree) + end + + for child, field in node:iter_children() do + local type = child:type() + local lnum, col, end_lnum, end_col = child:range() + local named = child:named() + local text + if named then + if field then + text = string.format('%s: (%s)', field, type) + else + text = string.format('(%s)', type) + end + else + text = string.format('"%s"', type:gsub('\n', '\\n')) + end + + table.insert(tree, { + id = child:id(), + text = text, + named = named, + depth = depth, + lnum = lnum, + col = col, + end_lnum = end_lnum, + end_col = end_col, + lang = lang, + }) + + traverse(child, depth + 1, lang, injections, tree) + end + + return tree +end + +--- Create a new Playground object. +--- +---@param bufnr number Source buffer number +---@param lang string|nil Language of source buffer +--- +---@return Playground|nil +---@return string|nil Error message, if any +--- +---@private +function M.new(self, bufnr, lang) + local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) + if not ok then + return nil, 'No parser available for the given buffer' + end + + -- For each child tree (injected language), find the root of the tree and locate the node within + -- the primary tree that contains that root. Add a mapping from the node in the primary tree to + -- the root in the child tree to the {injections} table. + local root = parser:parse()[1]:root() + local injections = {} + parser:for_each_child(function(child, lang_) + child:for_each_tree(function(tree) + local r = tree:root() + local node = root:named_descendant_for_range(r:range()) + if node then + injections[node:id()] = { + lang = lang_, + root = r, + } + end + end) + end) + + local nodes = traverse(root, 0, parser:lang(), injections, {}) + + local named = {} + for _, v in ipairs(nodes) do + if v.named then + named[#named + 1] = v + end + end + + local t = { + ns = api.nvim_create_namespace(''), + nodes = nodes, + named = named, + opts = { + anon = false, + lang = false, + }, + } + + setmetatable(t, self) + self.__index = self + return t +end + +--- Write the contents of this Playground into {bufnr}. +--- +---@param bufnr number Buffer number to write into. +---@private +function M.draw(self, bufnr) + vim.bo[bufnr].modifiable = true + local lines = {} + for _, item in self:iter() do + lines[#lines + 1] = table.concat({ + string.rep(' ', item.depth), + item.text, + item.lnum == item.end_lnum + and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col) + or string.format( + ' [%d:%d-%d:%d]', + item.lnum + 1, + item.col + 1, + item.end_lnum + 1, + item.end_col + ), + self.opts.lang and string.format(' %s', item.lang) or '', + }) + end + api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.bo[bufnr].modifiable = false +end + +--- Get node {i} from this Playground object. +--- +--- The node number is dependent on whether or not anonymous nodes are displayed. +--- +---@param i number Node number to get +---@return Node +---@private +function M.get(self, i) + local t = self.opts.anon and self.nodes or self.named + return t[i] +end + +--- Iterate over all of the nodes in this Playground object. +--- +---@return function Iterator over all nodes in this Playground +---@return table +---@return number +---@private +function M.iter(self) + return ipairs(self.opts.anon and self.nodes or self.named) +end + +return M diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 7ca7384a88..dbf134573d 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -419,7 +419,8 @@ local directive_handlers = { --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler function(match:string, pattern:string, bufnr:number, predicate:function) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[]) +--- - see |vim.treesitter.query.add_directive()| for argument meanings function M.add_predicate(name, handler, force) if predicate_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -436,7 +437,12 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler function(match:string, pattern:string, bufnr:number, predicate:function, metadata:table) +---@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table) +--- - match: see |treesitter-query| +--- - node-level data are accessible via `match[capture_id]` +--- - pattern: see |treesitter-query| +--- - predicate: list of strings containing the full directive being called, e.g. +--- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }` function M.add_directive(name, handler, force) if directive_handlers[name] and not force then error(string.format('Overriding %s', name)) @@ -549,7 +555,7 @@ end --- 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: ---- <pre> +--- <pre>lua --- 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 --- -- typically useful info about the node: @@ -603,7 +609,7 @@ end --- 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 is an example iterating over all captures in every match: ---- <pre> +--- <pre>lua --- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do --- for id, node in pairs(match) do --- local name = query.captures[id] |