aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-25 17:57:01 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-01-25 17:57:01 +0000
commit9837de570c5972f98e74848edc97c297a13136ea (patch)
treecc948611912d116a3f98a744e690d3d7b6e2f59a /runtime/lua/vim/treesitter
parentc367400b73d207833d51e09d663f969ffab37531 (diff)
parent3c48d3c83fc21dbc0841f9210f04bdb073d73cd1 (diff)
downloadrneovim-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.lua19
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua30
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua4
-rw-r--r--runtime/lua/vim/treesitter/playground.lua186
-rw-r--r--runtime/lua/vim/treesitter/query.lua14
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]