mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
fix(treesitter): scope highlight state per window
**Problem:** There is a lot of distracting highlight flickering when editing a buffer with multiple open windows. This is because the parsing/highlighting state is shared across all windows. **Solution:** Greatly reduce flicker in window splits by scoping the highlighter state object and the `parsing` state object to each individual window, so there is no cross-window interference.
This commit is contained in:

committed by
Christian Clason

parent
8d3b7b57c8
commit
8183eb32e1
@@ -63,15 +63,16 @@ end
|
|||||||
---@field active table<integer,vim.treesitter.highlighter>
|
---@field active table<integer,vim.treesitter.highlighter>
|
||||||
---@field bufnr integer
|
---@field bufnr integer
|
||||||
---@field private orig_spelloptions string
|
---@field private orig_spelloptions string
|
||||||
--- A map of highlight states.
|
--- A map from window ID to highlight states.
|
||||||
--- This state is kept during rendering across each line update.
|
--- This state is kept during rendering across each line update.
|
||||||
---@field private _highlight_states vim.treesitter.highlighter.State[]
|
---@field private _highlight_states table<integer, vim.treesitter.highlighter.State[]>
|
||||||
---@field private _queries table<string,vim.treesitter.highlighter.Query>
|
---@field private _queries table<string,vim.treesitter.highlighter.Query>
|
||||||
---@field _conceal_line boolean?
|
---@field _conceal_line boolean?
|
||||||
---@field _conceal_checked table<integer, boolean>
|
---@field _conceal_checked table<integer, boolean>
|
||||||
---@field tree vim.treesitter.LanguageTree
|
---@field tree vim.treesitter.LanguageTree
|
||||||
---@field private redraw_count integer
|
---@field private redraw_count integer
|
||||||
---@field parsing boolean true if we are parsing asynchronously
|
--- A map from window ID to whether we are currently parsing that window asynchronously
|
||||||
|
---@field parsing table<integer, boolean>
|
||||||
local TSHighlighter = {
|
local TSHighlighter = {
|
||||||
active = {},
|
active = {},
|
||||||
}
|
}
|
||||||
@@ -132,6 +133,7 @@ function TSHighlighter.new(tree, opts)
|
|||||||
self._conceal_checked = {}
|
self._conceal_checked = {}
|
||||||
self._queries = {}
|
self._queries = {}
|
||||||
self._highlight_states = {}
|
self._highlight_states = {}
|
||||||
|
self.parsing = {}
|
||||||
|
|
||||||
-- Queries for a specific language can be overridden by a custom
|
-- Queries for a specific language can be overridden by a custom
|
||||||
-- string query... if one is not provided it will be looked up by file.
|
-- string query... if one is not provided it will be looked up by file.
|
||||||
@@ -185,11 +187,12 @@ function TSHighlighter:destroy()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param win integer
|
||||||
---@param srow integer
|
---@param srow integer
|
||||||
---@param erow integer exclusive
|
---@param erow integer exclusive
|
||||||
---@private
|
---@private
|
||||||
function TSHighlighter:prepare_highlight_states(srow, erow)
|
function TSHighlighter:prepare_highlight_states(win, srow, erow)
|
||||||
self._highlight_states = {}
|
self._highlight_states[win] = {}
|
||||||
|
|
||||||
self.tree:for_each_tree(function(tstree, tree)
|
self.tree:for_each_tree(function(tstree, tree)
|
||||||
if not tstree then
|
if not tstree then
|
||||||
@@ -212,7 +215,7 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
|
|||||||
|
|
||||||
-- _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[win], {
|
||||||
tstree = tstree,
|
tstree = tstree,
|
||||||
next_row = 0,
|
next_row = 0,
|
||||||
iter = nil,
|
iter = nil,
|
||||||
@@ -221,10 +224,11 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param win integer
|
||||||
---@param fn fun(state: vim.treesitter.highlighter.State)
|
---@param fn fun(state: vim.treesitter.highlighter.State)
|
||||||
---@package
|
---@package
|
||||||
function TSHighlighter:for_each_highlight_state(fn)
|
function TSHighlighter:for_each_highlight_state(win, fn)
|
||||||
for _, state in ipairs(self._highlight_states) do
|
for _, state in ipairs(self._highlight_states[win] or {}) do
|
||||||
fn(state)
|
fn(state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -308,13 +312,14 @@ local function get_spell(capture_name)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@param self vim.treesitter.highlighter
|
---@param self vim.treesitter.highlighter
|
||||||
|
---@param win integer
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param line integer
|
---@param line integer
|
||||||
---@param on_spell boolean
|
---@param on_spell boolean
|
||||||
---@param on_conceal boolean
|
---@param on_conceal boolean
|
||||||
local function on_line_impl(self, buf, line, on_spell, on_conceal)
|
local function on_line_impl(self, win, buf, line, on_spell, on_conceal)
|
||||||
self._conceal_checked[line] = true
|
self._conceal_checked[line] = true
|
||||||
self:for_each_highlight_state(function(state)
|
self:for_each_highlight_state(win, function(state)
|
||||||
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()
|
||||||
|
|
||||||
@@ -394,23 +399,24 @@ local function on_line_impl(self, buf, line, on_spell, on_conceal)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param _win integer
|
---@param win integer
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param line integer
|
---@param line integer
|
||||||
function TSHighlighter._on_line(_, _win, buf, line, _)
|
function TSHighlighter._on_line(_, win, buf, line, _)
|
||||||
local self = TSHighlighter.active[buf]
|
local self = TSHighlighter.active[buf]
|
||||||
if not self then
|
if not self then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
on_line_impl(self, buf, line, false, false)
|
on_line_impl(self, win, buf, line, false, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
|
---@param win integer
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param srow integer
|
---@param srow integer
|
||||||
---@param erow integer
|
---@param erow integer
|
||||||
function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
|
function TSHighlighter._on_spell_nav(_, win, buf, srow, _, erow, _)
|
||||||
local self = TSHighlighter.active[buf]
|
local self = TSHighlighter.active[buf]
|
||||||
if not self then
|
if not self then
|
||||||
return
|
return
|
||||||
@@ -418,30 +424,31 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
|
|||||||
|
|
||||||
-- Do not affect potentially populated highlight state. Here we just want a temporary
|
-- Do not affect potentially populated highlight state. Here we just want a temporary
|
||||||
-- empty state so the C code can detect whether the region should be spell checked.
|
-- empty state so the C code can detect whether the region should be spell checked.
|
||||||
local highlight_states = self._highlight_states
|
local highlight_states = self._highlight_states[win]
|
||||||
self:prepare_highlight_states(srow, erow)
|
self:prepare_highlight_states(win, srow, erow)
|
||||||
|
|
||||||
for row = srow, erow do
|
for row = srow, erow do
|
||||||
on_line_impl(self, buf, row, true, false)
|
on_line_impl(self, win, buf, row, true, false)
|
||||||
end
|
end
|
||||||
self._highlight_states = highlight_states
|
self._highlight_states[win] = highlight_states
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
|
---@param win integer
|
||||||
---@param buf integer
|
---@param buf integer
|
||||||
---@param row integer
|
---@param row integer
|
||||||
function TSHighlighter._on_conceal_line(_, _, buf, row)
|
function TSHighlighter._on_conceal_line(_, win, buf, row)
|
||||||
local self = TSHighlighter.active[buf]
|
local self = TSHighlighter.active[buf]
|
||||||
if not self or not self._conceal_line or self._conceal_checked[row] then
|
if not self or not self._conceal_line or self._conceal_checked[row] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Do not affect potentially populated highlight state.
|
-- Do not affect potentially populated highlight state.
|
||||||
local highlight_states = self._highlight_states
|
local highlight_states = self._highlight_states[win]
|
||||||
self.tree:parse({ row, row })
|
self.tree:parse({ row, row })
|
||||||
self:prepare_highlight_states(row, row)
|
self:prepare_highlight_states(win, row, row)
|
||||||
on_line_impl(self, buf, row, false, true)
|
on_line_impl(self, win, buf, row, false, true)
|
||||||
self._highlight_states = highlight_states
|
self._highlight_states[win] = highlight_states
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
@@ -466,33 +473,31 @@ function TSHighlighter._on_win(_, win, buf, topline, botline)
|
|||||||
if not self then
|
if not self then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
self.parsing = self.parsing
|
self.parsing[win] = self.parsing[win]
|
||||||
or nil
|
or nil
|
||||||
== self.tree:parse({ topline, botline + 1 }, function(_, trees)
|
== self.tree:parse({ topline, botline + 1 }, function(_, trees)
|
||||||
if trees and self.parsing then
|
if trees and self.parsing[win] then
|
||||||
self.parsing = false
|
self.parsing[win] = false
|
||||||
api.nvim__redraw({ win = win, valid = false, flush = false })
|
api.nvim__redraw({ win = win, valid = false, flush = false })
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
if not self.parsing then
|
if not self.parsing[win] then
|
||||||
self.redraw_count = self.redraw_count + 1
|
self.redraw_count = self.redraw_count + 1
|
||||||
self:prepare_highlight_states(topline, botline)
|
self:prepare_highlight_states(win, topline, botline)
|
||||||
else
|
else
|
||||||
self:for_each_highlight_state(function(state)
|
self:for_each_highlight_state(win, function(state)
|
||||||
-- TODO(ribru17): Inefficient. Eventually all marks should be applied in on_buf, and all
|
-- TODO(ribru17): Inefficient. Eventually all marks should be applied in on_buf, and all
|
||||||
-- non-folded ranges of each open window should be merged, and iterators should only be
|
-- non-folded ranges of each open window should be merged, and iterators should only be
|
||||||
-- created over those regions. This would also fix #31777.
|
-- created over those regions. This would also fix #31777.
|
||||||
--
|
--
|
||||||
-- Currently this is not possible because the parser discards previously parsed injection
|
-- Currently this is not possible because the parser discards previously parsed injection
|
||||||
-- trees upon parsing a different region.
|
-- trees upon parsing a different region.
|
||||||
--
|
|
||||||
-- It would also be nice if rather than re-querying extmarks for old trees, we could tell the
|
|
||||||
-- decoration provider to not clear previous ephemeral marks for this redraw cycle.
|
|
||||||
state.iter = nil
|
state.iter = nil
|
||||||
state.next_row = 0
|
state.next_row = 0
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return #self._highlight_states > 0
|
local hl_states = self._highlight_states[win] or {}
|
||||||
|
return #hl_states > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
api.nvim_set_decoration_provider(ns, {
|
api.nvim_set_decoration_provider(ns, {
|
||||||
|
Reference in New Issue
Block a user