diff options
Diffstat (limited to 'runtime/lua/vim/treesitter/highlighter.lua')
-rw-r--r-- | runtime/lua/vim/treesitter/highlighter.lua | 194 |
1 files changed, 108 insertions, 86 deletions
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 496193c6ed..388680259a 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -2,50 +2,25 @@ local api = vim.api local query = vim.treesitter.query local Range = require('vim.treesitter._range') ----@alias TSHlIter fun(end_line: integer|nil): integer, TSNode, TSMetadata - ----@class TSHighlightState ----@field next_row integer ----@field iter TSHlIter|nil - ----@class TSHighlighter ----@field active table<integer,TSHighlighter> ----@field bufnr integer ----@field orig_spelloptions string ----@field _highlight_states table<TSTree,TSHighlightState> ----@field _queries table<string,TSHighlighterQuery> ----@field tree LanguageTree ----@field redraw_count integer -local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {} -TSHighlighter.__index = TSHighlighter +local ns = api.nvim_create_namespace('treesitter/highlighter') ---- @nodoc -TSHighlighter.active = TSHighlighter.active or {} +---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata ----@class TSHighlighterQuery ----@field _query Query|nil ----@field hl_cache table<integer,integer> +---@class (private) vim.treesitter.highlighter.Query +---@field private _query vim.treesitter.Query? +---@field private lang string +---@field private hl_cache table<integer,integer> local TSHighlighterQuery = {} TSHighlighterQuery.__index = TSHighlighterQuery -local ns = api.nvim_create_namespace('treesitter/highlighter') - ---@private +---@param lang string +---@param query_string string? +---@return vim.treesitter.highlighter.Query function TSHighlighterQuery.new(lang, query_string) - local self = setmetatable({}, { __index = TSHighlighterQuery }) - - self.hl_cache = setmetatable({}, { - __index = function(table, capture) - local name = self._query.captures[capture] - local id = 0 - if not vim.startswith(name, '_') then - id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang) - end - - rawset(table, capture, id) - return id - end, - }) + local self = setmetatable({}, TSHighlighterQuery) + self.lang = lang + self.hl_cache = {} if query_string then self._query = query.parse(lang, query_string) @@ -57,18 +32,57 @@ function TSHighlighterQuery.new(lang, query_string) end ---@package +---@param capture integer +---@return integer? +function TSHighlighterQuery:get_hl_from_capture(capture) + if not self.hl_cache[capture] then + local name = self._query.captures[capture] + local id = 0 + if not vim.startswith(name, '_') then + id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. self.lang) + end + self.hl_cache[capture] = id + end + + return self.hl_cache[capture] +end + +---@package function TSHighlighterQuery:query() return self._query end +---@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 + +---@nodoc +---@class vim.treesitter.highlighter +---@field active table<integer,vim.treesitter.highlighter> +---@field bufnr integer +---@field private orig_spelloptions string +--- A map of highlight states. +--- This state is kept during rendering across each line update. +---@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 = {}, +} + +TSHighlighter.__index = TSHighlighter + ---@package --- --- 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 TSHighlighter Created highlighter object +---@return vim.treesitter.highlighter Created highlighter object function TSHighlighter.new(tree, opts) local self = setmetatable({}, TSHighlighter) @@ -98,15 +112,12 @@ function TSHighlighter.new(tree, opts) end, }, true) - self.bufnr = tree:source() --[[@as integer]] - self.edit_count = 0 + local source = tree:source() + assert(type(source) == 'number') + + self.bufnr = source self.redraw_count = 0 - self.line_count = {} - -- A map of highlight states. - -- This state is kept during rendering across each line update. self._highlight_states = {} - - ---@type table<string,TSHighlighterQuery> self._queries = {} -- Queries for a specific language can be overridden by a custom @@ -144,11 +155,9 @@ end --- @nodoc --- Removes all internal references to the highlighter function TSHighlighter:destroy() - if TSHighlighter.active[self.bufnr] then - TSHighlighter.active[self.bufnr] = nil - end + TSHighlighter.active[self.bufnr] = nil - if vim.api.nvim_buf_is_loaded(self.bufnr) then + if 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 @@ -157,23 +166,49 @@ function TSHighlighter:destroy() end end ----@package ----@param tstree TSTree ----@return TSHighlightState -function TSHighlighter:get_highlight_state(tstree) - if not self._highlight_states[tstree] then - self._highlight_states[tstree] = { +---@param srow integer +---@param erow integer exclusive +---@private +function TSHighlighter:prepare_highlight_states(srow, erow) + self._highlight_states = {} + + self.tree:for_each_tree(function(tstree, tree) + if not tstree then + return + end + + local root_node = tstree:root() + local root_start_row, _, root_end_row, _ = root_node:range() + + -- Only consider trees within the visible range + if root_start_row > erow or root_end_row < srow then + return + end + + local highlighter_query = self:get_query(tree:lang()) + + -- Some injected languages may not have highlight queries. + if not highlighter_query:query() then + return + 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, - } - end - - return self._highlight_states[tstree] + highlighter_query = highlighter_query, + }) + end) end ----@private -function TSHighlighter:reset_highlight_state() - self._highlight_states = {} +---@param fn fun(state: vim.treesitter.highlighter.State) +---@package +function TSHighlighter:for_each_highlight_state(fn) + for _, state in ipairs(self._highlight_states) do + fn(state) + end end ---@package @@ -197,10 +232,9 @@ function TSHighlighter:on_changedtree(changes) end --- Gets the query used for @param lang --- ---@package ---@param lang string Language used by the highlighter. ----@return TSHighlighterQuery +---@return vim.treesitter.highlighter.Query function TSHighlighter:get_query(lang) if not self._queries[lang] then self._queries[lang] = TSHighlighterQuery.new(lang) @@ -209,35 +243,23 @@ function TSHighlighter:get_query(lang) return self._queries[lang] end ----@param self TSHighlighter +---@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.tree:for_each_tree(function(tstree, tree) - if not tstree then - return - end - - local root_node = tstree:root() + self:for_each_highlight_state(function(state) + local root_node = state.tstree:root() local root_start_row, _, root_end_row, _ = root_node:range() - -- Only worry about trees within the line range + -- Only consider trees that contain this line if root_start_row > line or root_end_row < line then return end - local state = self:get_highlight_state(tstree) - local highlighter_query = self:get_query(tree:lang()) - - -- Some injected languages may not have highlight queries. - if not highlighter_query:query() then - return - end - if state.iter == nil or state.next_row < line then state.iter = - highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) + state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1) end while line >= state.next_row do @@ -250,9 +272,9 @@ local function on_line_impl(self, buf, line, is_spell_nav) local start_row, start_col, end_row, end_col = Range.unpack4(range) if capture then - local hl = highlighter_query.hl_cache[capture] + local hl = state.highlighter_query:get_hl_from_capture(capture) - local capture_name = highlighter_query:query().captures[capture] + local capture_name = state.highlighter_query:query().captures[capture] local spell = nil ---@type boolean? if capture_name == 'spell' then spell = true @@ -308,7 +330,7 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) return end - self:reset_highlight_state() + self:prepare_highlight_states(srow, erow) for row = srow, erow do on_line_impl(self, buf, row, true) @@ -326,7 +348,7 @@ function TSHighlighter._on_win(_, _win, buf, topline, botline) return false end self.tree:parse({ topline, botline + 1 }) - self:reset_highlight_state() + self:prepare_highlight_states(topline, botline + 1) self.redraw_count = self.redraw_count + 1 return true end |