mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 03:18:16 +00:00
perf(treesitter): don't block when finding injection ranges
**Problem:** Currently, parsing is asynchronous, but it involves a (sometimes lengthy) step which finds all injection ranges for a tree by iterating over that language's injection queries. This causes edits in large files to be extremely slow, and also causes a long stutter during the initial parse of a large file. **Solution:** Break up the injection query iteration over multiple event loop iterations.
This commit is contained in:

committed by
Christian Clason

parent
2e0a563828
commit
cbad2c6628
@@ -343,6 +343,8 @@ PERFORMANCE
|
|||||||
• |LanguageTree:parse()| now only runs the injection query on the provided
|
• |LanguageTree:parse()| now only runs the injection query on the provided
|
||||||
range (as long as the language does not have a combined injection),
|
range (as long as the language does not have a combined injection),
|
||||||
significantly improving |treesitter-highlight| performance.
|
significantly improving |treesitter-highlight| performance.
|
||||||
|
• Treesitter injection query iteration is now asynchronous, making edits in
|
||||||
|
large buffers with combined injections much quicker.
|
||||||
|
|
||||||
PLUGINS
|
PLUGINS
|
||||||
|
|
||||||
|
@@ -422,12 +422,10 @@ function LanguageTree:_parse_regions(range, thread_state)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- @private
|
--- @private
|
||||||
--- @param range Range|true
|
--- @param injections_by_lang table<string, Range6[][]>
|
||||||
--- @return number
|
function LanguageTree:_add_injections(injections_by_lang)
|
||||||
function LanguageTree:_add_injections(range)
|
|
||||||
local seen_langs = {} ---@type table<string,boolean>
|
local seen_langs = {} ---@type table<string,boolean>
|
||||||
|
|
||||||
local query_time, injections_by_lang = tcall(self._get_injections, self, range)
|
|
||||||
for lang, injection_regions in pairs(injections_by_lang) do
|
for lang, injection_regions in pairs(injections_by_lang) do
|
||||||
local has_lang = pcall(language.add, lang)
|
local has_lang = pcall(language.add, lang)
|
||||||
|
|
||||||
@@ -451,8 +449,6 @@ function LanguageTree:_add_injections(range)
|
|||||||
self:remove_child(lang)
|
self:remove_child(lang)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return query_time
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- @param range boolean|Range?
|
--- @param range boolean|Range?
|
||||||
@@ -625,7 +621,18 @@ function LanguageTree:_parse(range, thread_state)
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
query_time = self:_add_injections(range)
|
---@type fun(self: vim.treesitter.LanguageTree, thread_state: ParserThreadState): table<string, Range6[][]>?
|
||||||
|
local get_injections = coroutine.wrap(self._get_injections)
|
||||||
|
local injections_by_lang
|
||||||
|
query_time, injections_by_lang = tcall(get_injections, self, range, thread_state)
|
||||||
|
while not injections_by_lang do
|
||||||
|
coroutine.yield()
|
||||||
|
query_time, injections_by_lang = tcall(get_injections, self, range, thread_state)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:_add_injections(injections_by_lang)
|
||||||
|
|
||||||
|
thread_state.timeout = thread_state.timeout and math.max(thread_state.timeout - query_time, 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:_log({
|
self:_log({
|
||||||
@@ -1002,8 +1009,9 @@ end
|
|||||||
--- instead of using the entire nodes range.
|
--- instead of using the entire nodes range.
|
||||||
--- @private
|
--- @private
|
||||||
--- @param range Range|true
|
--- @param range Range|true
|
||||||
|
--- @param thread_state ParserThreadState
|
||||||
--- @return table<string, Range6[][]>
|
--- @return table<string, Range6[][]>
|
||||||
function LanguageTree:_get_injections(range)
|
function LanguageTree:_get_injections(range, thread_state)
|
||||||
if not self._injection_query or #self._injection_query.captures == 0 then
|
if not self._injection_query or #self._injection_query.captures == 0 then
|
||||||
self._processed_injection_range = entire_document_range
|
self._processed_injection_range = entire_document_range
|
||||||
return {}
|
return {}
|
||||||
@@ -1011,6 +1019,7 @@ function LanguageTree:_get_injections(range)
|
|||||||
|
|
||||||
---@type table<integer,vim.treesitter.languagetree.Injection>
|
---@type table<integer,vim.treesitter.languagetree.Injection>
|
||||||
local injections = {}
|
local injections = {}
|
||||||
|
local start = vim.uv.hrtime()
|
||||||
|
|
||||||
local full_scan = range == true or self._injection_query.has_combined_injections
|
local full_scan = range == true or self._injection_query.has_combined_injections
|
||||||
|
|
||||||
@@ -1032,6 +1041,12 @@ function LanguageTree:_get_injections(range)
|
|||||||
else
|
else
|
||||||
self:_log('match from injection query failed for pattern', pattern)
|
self:_log('match from injection query failed for pattern', pattern)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Check the current function duration against the timeout, if it exists.
|
||||||
|
if thread_state.timeout and vim.uv.hrtime() - start > thread_state.timeout * 1000000 then
|
||||||
|
coroutine.yield()
|
||||||
|
start = vim.uv.hrtime()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user