perf(treesitter): don't fetch parser for each fold line

**Problem:** The treesitter `foldexpr` calls `get_parser()` for each
line in the buffer when calculating folds. This can be incredibly slow
for buffers where a parser cannot be found (because the result is not
cached), and exponentially more so when the user has many
`runtimepath`s.

**Solution:** Only fetch the parser when it is needed; that is, only
when initializing fold data for a buffer.

Co-authored-by: Jongwook Choi <wookayin@gmail.com>
Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
This commit is contained in:
Riley Bruins
2025-01-01 11:33:45 -08:00
committed by Christian Clason
parent b67fcd0488
commit d9ee0d2984
3 changed files with 102 additions and 9 deletions

View File

@@ -19,14 +19,19 @@ local api = vim.api
---The range on which to evaluate foldexpr.
---When in insert mode, the evaluation is deferred to InsertLeave.
---@field foldupdate_range? Range2
---
---The treesitter parser associated with this buffer.
---@field parser? vim.treesitter.LanguageTree
local FoldInfo = {}
FoldInfo.__index = FoldInfo
---@private
function FoldInfo.new()
---@param bufnr integer
function FoldInfo.new(bufnr)
return setmetatable({
levels0 = {},
levels = {},
parser = ts.get_parser(bufnr, nil, { error = false }),
}, FoldInfo)
end
@@ -69,7 +74,10 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections)
srow = srow or 0
erow = erow or api.nvim_buf_line_count(bufnr)
local parser = assert(ts.get_parser(bufnr, nil, { error = false }))
local parser = info.parser
if not parser then
return
end
parser:parse(parse_injections and { srow, erow } or nil)
@@ -347,13 +355,21 @@ function M.foldexpr(lnum)
lnum = lnum or vim.v.lnum
local bufnr = api.nvim_get_current_buf()
local parser = ts.get_parser(bufnr, nil, { error = false })
if not parser then
return '0'
end
if not foldinfos[bufnr] then
foldinfos[bufnr] = FoldInfo.new()
foldinfos[bufnr] = FoldInfo.new(bufnr)
api.nvim_create_autocmd('BufUnload', {
buffer = bufnr,
once = true,
callback = function()
foldinfos[bufnr] = nil
end,
})
local parser = foldinfos[bufnr].parser
if not parser then
return '0'
end
compute_folds_levels(bufnr, foldinfos[bufnr])
parser:register_cbs({
@@ -383,7 +399,7 @@ api.nvim_create_autocmd('OptionSet', {
or foldinfos[buf] and { buf }
or {}
for _, bufnr in ipairs(bufs) do
foldinfos[bufnr] = FoldInfo.new()
foldinfos[bufnr] = FoldInfo.new(bufnr)
api.nvim_buf_call(bufnr, function()
compute_folds_levels(bufnr, foldinfos[bufnr])
end)