aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim/treesitter/highlighter.lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim/treesitter/highlighter.lua')
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua146
1 files changed, 102 insertions, 44 deletions
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 8fb591bc46..7bc6e5c019 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -4,9 +4,9 @@ local Range = require('vim.treesitter._range')
local ns = api.nvim_create_namespace('treesitter/highlighter')
----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, TSMetadata
+---@alias vim.treesitter.highlighter.Iter fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata
----@class vim.treesitter.highlighter.Query
+---@class (private) vim.treesitter.highlighter.Query
---@field private _query vim.treesitter.Query?
---@field private lang string
---@field private hl_cache table<integer,integer>
@@ -52,22 +52,24 @@ function TSHighlighterQuery:query()
return self._query
end
----@class vim.treesitter.highlighter.State
+---@class (private) vim.treesitter.highlighter.State
---@field tstree TSTree
---@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
---@field active table<integer,vim.treesitter.highlighter>
---@field bufnr integer
----@field orig_spelloptions string
+---@field private orig_spelloptions string
--- A map of highlight states.
--- This state is kept during rendering across each line update.
----@field _highlight_states vim.treesitter.highlighter.State[]
----@field _queries table<string,vim.treesitter.highlighter.Query>
----@field tree LanguageTree
----@field redraw_count integer
+---@field private _highlight_states vim.treesitter.highlighter.State[]
+---@field private _queries table<string,vim.treesitter.highlighter.Query>
+---@field tree vim.treesitter.LanguageTree
+---@field private redraw_count integer
local TSHighlighter = {
active = {},
}
@@ -78,7 +80,7 @@ TSHighlighter.__index = TSHighlighter
---
--- Creates a highlighter for `tree`.
---
----@param tree LanguageTree parser object to use for highlighting
+---@param tree vim.treesitter.LanguageTree parser object to use for highlighting
---@param opts (table|nil) Configuration of the highlighter:
--- - queries table overwrite queries used by the highlighter
---@return vim.treesitter.highlighter Created highlighter object
@@ -191,12 +193,20 @@ 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)
@@ -242,12 +252,53 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+--- @param match table<integer,TSNode[]>
+--- @param bufnr integer
+--- @param capture integer
+--- @param metadata vim.treesitter.query.TSMetadata
+--- @return string?
+local function get_url(match, bufnr, capture, metadata)
+ ---@type string|number|nil
+ local url = metadata[capture] and metadata[capture].url
+
+ if not url or type(url) == 'string' then
+ return url
+ end
+
+ if not match or not match[url] then
+ return
+ end
+
+ -- Assume there is only one matching node. If there is more than one, take the URL
+ -- from the first.
+ local other_node = match[url][1]
+
+ return vim.treesitter.get_node_text(other_node, bufnr, {
+ metadata = metadata[url],
+ })
+end
+
+--- @param capture_name string
+--- @return boolean?, integer
+local function get_spell(capture_name)
+ if capture_name == 'spell' then
+ return true, 0
+ elseif capture_name == 'nospell' then
+ -- Give nospell a higher priority so it always overrides spell captures.
+ return false, 1
+ end
+ return nil, 0
+end
+
---@param self vim.treesitter.highlighter
---@param buf integer
---@param line integer
---@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()
@@ -257,50 +308,57 @@ 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_captures(root_node, self.bufnr, line, root_end_row + 1)
+ state.iter = state.highlighter_query
+ :query()
+ :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true })
end
while line >= state.next_row do
- local capture, node, metadata = state.iter(line)
+ local pattern, match, metadata = state.iter()
- 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])
+ if not match then
+ state.next_row = root_end_row + 1
end
- local start_row, start_col, end_row, end_col = Range.unpack4(range)
-
- if capture then
- local hl = state.highlighter_query:get_hl_from_capture(capture)
+ for capture, nodes in pairs(match or {}) do
local capture_name = state.highlighter_query:query().captures[capture]
- local spell = nil ---@type boolean?
- if capture_name == 'spell' then
- spell = true
- elseif capture_name == 'nospell' then
- spell = false
- end
+ local spell, spell_pri_offset = get_spell(capture_name)
- -- 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 is_spell_nav or spell ~= nil) then
- local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter)
- + spell_pri_offset
- 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 = metadata.conceal,
- spell = spell,
- })
- end
- end
+ local hl = state.highlighter_query:get_hl_from_capture(capture)
- if start_row > line then
- state.next_row = start_row
+ -- 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)
+
+ 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
+ end
end
end
end)