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
omitted, the {start} and {stop} row values are used from the given node.
The iterator returns three values: a numeric id identifying the capture,
the captured node, and metadata from any directives processing the match.
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
The iterator returns four values: a numeric id identifying the capture,
the captured node, metadata from any directives processing the match, and
the match itself. The following example shows how to get captures by name: >lua
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
-- typically useful info about the 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_()`.
Return: ~
(`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata`)
capture id, capture node, metadata
(`fun(end_line: integer?): integer, TSNode, vim.treesitter.query.TSMetadata, table<integer, TSNode>`)
capture id, capture node, metadata, match
*Query:iter_matches()*
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')
---@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
---@field private _query vim.treesitter.Query?
@ -57,7 +57,6 @@ end
---@field next_row integer
---@field iter vim.treesitter.highlighter.Iter?
---@field highlighter_query vim.treesitter.highlighter.Query
---@field level integer Injection level
---@nodoc
---@class vim.treesitter.highlighter
@ -193,20 +192,12 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
return
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
-- for_each_tree traversal. This ensures that parents' highlight don't override children's.
table.insert(self._highlight_states, {
tstree = tstree,
next_row = 0,
iter = nil,
level = level,
highlighter_query = highlighter_query,
})
end)
@ -296,9 +287,6 @@ end
---@param is_spell_nav boolean
local function on_line_impl(self, buf, line, is_spell_nav)
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_start_row, _, root_end_row, _ = root_node:range()
@ -308,58 +296,54 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end
if state.iter == nil or state.next_row < line then
state.iter = state.highlighter_query
:query()
:iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true })
state.iter =
state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
end
while line >= state.next_row do
local pattern, match, metadata = state.iter()
local capture, node, metadata, match = state.iter(line)
if not match then
state.next_row = root_end_row + 1
local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
if node then
range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
end
local start_row, start_col, end_row, end_col = Range.unpack4(range)
for capture, nodes in pairs(match or {}) do
local capture_name = state.highlighter_query:query().captures[capture]
local spell, spell_pri_offset = get_spell(capture_name)
if capture then
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
local priority = (
tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
or vim.highlight.priorities.treesitter
) + 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
local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
for _, node in ipairs(nodes) do
local range = vim.treesitter.get_range(node, buf, metadata[capture])
local start_row, start_col, end_row, end_col = Range.unpack4(range)
local url = get_url(match, buf, capture, metadata)
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, {
end_line = end_row,
end_col = end_col,
hl_group = hl,
ephemeral = true,
priority = priority,
_subpriority = pattern_offset + pattern,
conceal = conceal,
spell = spell,
url = url,
})
end
if start_row > line then
state.next_row = start_row
end
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, {
end_line = end_row,
end_col = end_col,
hl_group = hl,
ephemeral = true,
priority = priority,
conceal = conceal,
spell = spell,
url = url,
})
end
end
if start_row > line then
state.next_row = start_row
end
end
end)
end

View File

@ -811,12 +811,13 @@ end
--- 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.
---
--- The iterator returns three values: a numeric id identifying the capture,
--- the captured node, and metadata from any directives processing the match.
--- The iterator returns four values: a numeric id identifying the capture,
--- the captured node, metadata from any directives processing the match,
--- and 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
--- -- typically useful info about the 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 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):
--- capture id, capture node, metadata
---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata, table<integer, TSNode>):
--- capture id, capture node, metadata, match
function Query:iter_captures(node, source, start, stop)
if type(source) == 'number' and source == 0 then
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)
end
return capture, captured_node, metadata
return capture, captured_node, metadata, match
end
return iter
end

View File

@ -762,6 +762,32 @@ describe('treesitter highlighting (C)', function()
]],
}
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)
describe('treesitter highlighting (lua)', function()