fix(treesitter): avoid computing fold levels for empty buffer

Problem:  Computing fold levels for an empty buffer (somehow) breaks the
          parser state, resulting in a broken highlighter and foldexpr.
          Cached foldexpr parser is invalid after filetype has changed.
Solution: Avoid computing fold levels for empty buffer.
          Clear cached foldinfos upon `FileType`.
This commit is contained in:
Luuk van Baal
2025-02-16 00:07:08 +01:00
committed by Christian Clason
parent a0b52e7cb3
commit bc1018a8d3
3 changed files with 79 additions and 9 deletions

View File

@ -75,7 +75,15 @@ local function compute_folds_levels(bufnr, info, srow, erow, callback)
erow = erow or api.nvim_buf_line_count(bufnr)
local parser = info.parser
if not parser then
if
not parser
-- Parsing an empty buffer results in problems with the parsing state,
-- resulting in both a broken highlighter and foldexpr.
or api.nvim_buf_line_count(bufnr) == 1
and api.nvim_buf_call(bufnr, function()
return vim.fn.line2byte(1) <= 0
end)
then
return
end
@ -380,7 +388,7 @@ function M.foldexpr(lnum)
if not foldinfos[bufnr] then
foldinfos[bufnr] = FoldInfo.new(bufnr)
api.nvim_create_autocmd({ 'BufUnload', 'VimEnter' }, {
api.nvim_create_autocmd({ 'BufUnload', 'VimEnter', 'FileType' }, {
buffer = bufnr,
once = true,
callback = function()

View File

@ -150,6 +150,58 @@ describe('swapfile detection', function()
rmdir(swapdir)
end)
it('redrawing during prompt does not break treesitter', function()
local testfile = 'Xtest_swapredraw.lua'
write_file(
testfile,
[[
vim.o.foldmethod = 'expr'
vim.o.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
vim.defer_fn(function()
vim.api.nvim__redraw({ valid = false })
end, 500)
pcall(vim.cmd.edit, 'Xtest_swapredraw.lua')
]]
)
exec(init)
command('edit! ' .. testfile)
command('preserve')
local nvim2 = n.new_session(true, { args = { '--clean', '--embed' }, merge = false })
set_session(nvim2)
local screen2 = Screen.new(100, 40)
screen2:add_extra_attr_ids({
[100] = { foreground = Screen.colors.NvimLightGrey2 },
[101] = { foreground = Screen.colors.NvimLightGreen },
[102] = {
foreground = Screen.colors.NvimLightGrey4,
background = Screen.colors.NvimDarkGrey1,
},
[104] = { foreground = Screen.colors.NvimLightCyan },
[105] = { foreground = Screen.colors.NvimDarkGrey4 },
[106] = {
foreground = Screen.colors.NvimDarkGrey3,
background = Screen.colors.NvimLightGrey3,
},
})
exec(init)
command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog).
feed(':edit ' .. testfile .. '<CR>')
feed('E:source<CR>')
screen2:sleep(1000)
feed('E')
screen2:expect([[
{100:^vim.o.foldmethod} {100:=} {101:'expr'} |
{100:vim.o.foldexpr} {100:=} {101:'v:lua.vim.treesitter.foldexpr()'} |
{102:+-- 3 lines: vim.defer_fn(function()·······························································}|
{104:pcall}{100:(vim.cmd.edit,} {101:'Xtest_swapredraw.lua'}{100:)} |
|
{105:~ }|*33
{106:Xtest_swapredraw.lua 1,1 All}|
|
]])
nvim2:close()
end)
it('always show swapfile dialog #8840 #9027', function()
local testfile = 'Xtest_swapdialog_file1'

View File

@ -811,17 +811,19 @@ t2]])
]]
-- foldexpr will return '0' for all lines
local levels = get_fold_levels() ---@type integer[]
eq(19, #levels)
for lnum, level in ipairs(levels) do
eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level))
local function expect_no_folds()
local levels = get_fold_levels() ---@type integer[]
eq(19, #levels)
for lnum, level in ipairs(levels) do
eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level))
end
end
expect_no_folds()
-- reload buffer as c filetype to simulate new parser being found
feed('GA// vim: ft=c<Esc>')
command([[write | edit]])
eq({
local foldlevels = {
[1] = '>1',
[2] = '1',
[3] = '1',
@ -841,6 +843,14 @@ t2]])
[17] = '3',
[18] = '2',
[19] = '1',
}, get_fold_levels())
}
eq(foldlevels, get_fold_levels())
-- only changing filetype should change the parser again
command('set ft=some_filetype_without_treesitter_parser')
expect_no_folds()
command('set ft=c')
eq(foldlevels, get_fold_levels())
end)
end)