mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 17:41:48 +00:00
fix(treesitter): make TSHighlighter.new() idempotent for an active buffer #41090
Problem:
Calling vim.treesitter.start() a second time on a buffer that already
has an active TSHighlighter creates a brand new instance instead of
reusing it, whether the parser tree is unchanged (e.g. calling start()
twice) or different (e.g. switching languages). Either way the old
instance is silently discarded without calling :destroy() on it, so
its on_bytes/on_changedtree/on_detach callbacks stay registered and
its buffer-local state (spelloptions, decoration namespace) is never
restored, both leaking indefinitely for an orphaned instance that
nothing references anymore.
Solution:
Return the existing instance when TSHighlighter.active[source] is
already set for the same parser tree, instead of unconditionally
constructing a new one. When the tree differs instead (e.g. a language
switch), destroy() the old instance first, matching stop() semantics,
before constructing the new one.
(cherry picked from commit 67839a72f7)
This commit is contained in:
committed by
github-actions[bot]
parent
1e6141de03
commit
ebcb61b0f9
@@ -90,12 +90,26 @@ TSHighlighter.__index = TSHighlighter
|
||||
--- - queries table overwrite queries used by the highlighter
|
||||
---@return vim.treesitter.highlighter Created highlighter object
|
||||
function TSHighlighter.new(tree, opts)
|
||||
local self = setmetatable({}, TSHighlighter)
|
||||
|
||||
if type(tree:source()) ~= 'number' then
|
||||
local source = tree:source()
|
||||
if type(source) ~= 'number' then
|
||||
error('TSHighlighter can not be used with a string parser source.')
|
||||
end
|
||||
|
||||
-- Calling start() again on an already-highlighted buffer must be a no-op: a second instance
|
||||
-- would register duplicate on_bytes/on_changedtree/on_detach callbacks that destroy() never
|
||||
-- unregisters, leaking callbacks that keep firing for the orphaned instance.
|
||||
local existing = TSHighlighter.active[source]
|
||||
if existing then
|
||||
if existing.tree == tree then
|
||||
return existing
|
||||
end
|
||||
-- Different tree (e.g. a language switch): the old instance would otherwise be silently
|
||||
-- orphaned with the same leaked callbacks, so destroy() it first, matching stop() semantics.
|
||||
existing:destroy()
|
||||
end
|
||||
|
||||
local self = setmetatable({}, TSHighlighter)
|
||||
|
||||
opts = opts or {} ---@type { queries: table<string,string> }
|
||||
self.tree = tree
|
||||
tree:register_cbs({
|
||||
@@ -136,9 +150,6 @@ function TSHighlighter.new(tree, opts)
|
||||
end,
|
||||
}, true)
|
||||
|
||||
local source = tree:source()
|
||||
assert(type(source) == 'number')
|
||||
|
||||
self.bufnr = source
|
||||
self.redraw_count = 0
|
||||
self._conceal_checked = {}
|
||||
|
||||
Reference in New Issue
Block a user