fix(treesitter): revert to using iter_captures in highlighter

Fixes #27895
This commit is contained in:
Lewis Russell 2024-03-17 18:02:40 +00:00 committed by Lewis Russell
parent 77a9f3395b
commit 3b29b39e6d
4 changed files with 68 additions and 57 deletions

View File

@ -1132,10 +1132,10 @@ Query:iter_captures({node}, {source}, {start}, {stop})
i.e., to get syntax highlight matches in the current viewport). When i.e., to get syntax highlight matches in the current viewport). When
omitted, the {start} and {stop} row values are used from the given node. omitted, the {start} and {stop} row values are used from the given node.
The iterator returns three values: a numeric id identifying the capture, The iterator returns four values: a numeric id identifying the capture,
the captured node, and metadata from any directives processing the match. the captured node, metadata from any directives processing the match, and
The following example shows how to get captures by name: >lua the match itself. The following example shows how to get captures by name: >lua
for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do for id, node, metadata, match in query:iter_captures(tree:root(), bufnr, first, last) do
local name = query.captures[id] -- name of the capture in the query local name = query.captures[id] -- name of the capture in the query
-- typically useful info about the node: -- typically useful info about the node:
local type = node:type() -- type of the captured node local type = node:type() -- type of the captured node
@ -1154,8 +1154,8 @@ Query:iter_captures({node}, {source}, {start}, {stop})
Defaults to `node:end_()`. Defaults to `node:end_()`.
Return: ~ Return: ~
(`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata`) (`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, table<integer, TSNode>`)
capture id, capture node, metadata capture id, capture node, metadata, match
*Query:iter_matches()* *Query:iter_matches()*
Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) Query:iter_matches({node}, {source}, {start}, {stop}, {opts})

View File

@ -4,7 +4,7 @@ local Range = require('vim.treesitter._range')
local ns = api.nvim_create_namespace('treesitter/highlighter') local ns = api.nvim_create_namespace('treesitter/highlighter')
---@alias vim.treesitter.highlighter.Iter fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata ---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table<integer, TSNode[]>
---@class (private) vim.treesitter.highlighter.Query ---@class (private) vim.treesitter.highlighter.Query
---@field private _query vim.treesitter.Query? ---@field private _query vim.treesitter.Query?
@ -57,7 +57,6 @@ end
---@field next_row integer ---@field next_row integer
---@field iter vim.treesitter.highlighter.Iter? ---@field iter vim.treesitter.highlighter.Iter?
---@field highlighter_query vim.treesitter.highlighter.Query ---@field highlighter_query vim.treesitter.highlighter.Query
---@field level integer Injection level
---@nodoc ---@nodoc
---@class vim.treesitter.highlighter ---@class vim.treesitter.highlighter
@ -193,20 +192,12 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
return return
end end
local level = 0
local t = tree
while t do
t = t:parent()
level = level + 1
end
-- _highlight_states should be a list so that the highlights are added in the same order as -- _highlight_states should be a list so that the highlights are added in the same order as
-- for_each_tree traversal. This ensures that parents' highlight don't override children's. -- for_each_tree traversal. This ensures that parents' highlight don't override children's.
table.insert(self._highlight_states, { table.insert(self._highlight_states, {
tstree = tstree, tstree = tstree,
next_row = 0, next_row = 0,
iter = nil, iter = nil,
level = level,
highlighter_query = highlighter_query, highlighter_query = highlighter_query,
}) })
end) end)
@ -296,9 +287,6 @@ end
---@param is_spell_nav boolean ---@param is_spell_nav boolean
local function on_line_impl(self, buf, line, is_spell_nav) local function on_line_impl(self, buf, line, is_spell_nav)
self:for_each_highlight_state(function(state) self:for_each_highlight_state(function(state)
-- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark
-- so injections always appear over base highlights.
local pattern_offset = state.level * 1000
local root_node = state.tstree:root() local root_node = state.tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range() local root_start_row, _, root_end_row, _ = root_node:range()
@ -308,58 +296,54 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end end
if state.iter == nil or state.next_row < line then if state.iter == nil or state.next_row < line then
state.iter = state.highlighter_query state.iter =
:query() state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
:iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true })
end end
while line >= state.next_row do while line >= state.next_row do
local pattern, match, metadata = state.iter() local capture, node, metadata, match = state.iter(line)
if not match then local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
state.next_row = root_end_row + 1 if node then
range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
end end
local start_row, start_col, end_row, end_col = Range.unpack4(range)
for capture, nodes in pairs(match or {}) do if capture then
local capture_name = state.highlighter_query:query().captures[capture]
local spell, spell_pri_offset = get_spell(capture_name)
local hl = state.highlighter_query:get_hl_from_capture(capture) local hl = state.highlighter_query:get_hl_from_capture(capture)
local capture_name = state.highlighter_query:query().captures[capture]
local spell, spell_pri_offset = get_spell(capture_name)
-- The "priority" attribute can be set at the pattern level or on a particular capture -- The "priority" attribute can be set at the pattern level or on a particular capture
local priority = ( local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority) tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
or vim.highlight.priorities.treesitter or vim.highlight.priorities.treesitter
) + spell_pri_offset ) + spell_pri_offset
local url = get_url(match, buf, capture, metadata)
-- The "conceal" attribute can be set at the pattern level or on a particular capture -- The "conceal" attribute can be set at the pattern level or on a particular capture
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
for _, node in ipairs(nodes) do local url = get_url(match, buf, capture, metadata)
local range = vim.treesitter.get_range(node, buf, metadata[capture])
local start_row, start_col, end_row, end_col = Range.unpack4(range)
if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
api.nvim_buf_set_extmark(buf, ns, start_row, start_col, { api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
end_line = end_row, end_line = end_row,
end_col = end_col, end_col = end_col,
hl_group = hl, hl_group = hl,
ephemeral = true, ephemeral = true,
priority = priority, priority = priority,
_subpriority = pattern_offset + pattern, conceal = conceal,
conceal = conceal, spell = spell,
spell = spell, url = url,
url = url, })
})
end
if start_row > line then
state.next_row = start_row
end
end end
end end
if start_row > line then
state.next_row = start_row
end
end end
end) end)
end end

View File

@ -811,12 +811,13 @@ end
--- as the {node}, i.e., to get syntax highlight matches in the current --- as the {node}, i.e., to get syntax highlight matches in the current
--- viewport). When omitted, the {start} and {stop} row values are used from the given node. --- viewport). When omitted, the {start} and {stop} row values are used from the given node.
--- ---
--- The iterator returns three values: a numeric id identifying the capture, --- The iterator returns four values: a numeric id identifying the capture,
--- the captured node, and metadata from any directives processing the match. --- the captured node, metadata from any directives processing the match,
--- and the match itself.
--- The following example shows how to get captures by name: --- The following example shows how to get captures by name:
--- ---
--- ```lua --- ```lua
--- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do --- for id, node, metadata, match in query:iter_captures(tree:root(), bufnr, first, last) do
--- local name = query.captures[id] -- name of the capture in the query --- local name = query.captures[id] -- name of the capture in the query
--- -- typically useful info about the node: --- -- typically useful info about the node:
--- local type = node:type() -- type of the captured node --- local type = node:type() -- type of the captured node
@ -830,8 +831,8 @@ end
---@param start? integer Starting line for the search. Defaults to `node:start()`. ---@param start? integer Starting line for the search. Defaults to `node:start()`.
---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`. ---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`.
--- ---
---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata): ---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table<integer, TSNode>):
--- capture id, capture node, metadata --- capture id, capture node, metadata, match
function Query:iter_captures(node, source, start, stop) function Query:iter_captures(node, source, start, stop)
if type(source) == 'number' and source == 0 then if type(source) == 'number' and source == 0 then
source = api.nvim_get_current_buf() source = api.nvim_get_current_buf()
@ -856,7 +857,7 @@ function Query:iter_captures(node, source, start, stop)
self:apply_directives(match, match.pattern, source, metadata) self:apply_directives(match, match.pattern, source, metadata)
end end
return capture, captured_node, metadata return capture, captured_node, metadata, match
end end
return iter return iter
end end

View File

@ -762,6 +762,32 @@ describe('treesitter highlighting (C)', function()
]], ]],
} }
end) end)
it('gives higher priority to more specific captures #27895', function()
insert([[
void foo(int *bar);
]])
local query = [[
"*" @operator
(parameter_declaration
declarator: (pointer_declarator) @variable.parameter)
]]
exec_lua([[
local query = ...
vim.treesitter.query.set('c', 'highlights', query)
vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c'))
]], query)
screen:expect{grid=[[
void foo(int {4:*}{11:bar}); |
^ |
{1:~ }|*15
|
]]}
end)
end) end)
describe('treesitter highlighting (lua)', function() describe('treesitter highlighting (lua)', function()