avoid treesitter races in netrw previews

This commit is contained in:
Ray Elliott 2026-10-28 16:46:48 +00:00
commit fe94b30c31
4 changed files with 117 additions and 1 deletions

View file

@ -2,10 +2,12 @@ local M = {}
local uv = vim.uv
local states = {}
local preview_buffers = {}
local preview_group = vim.api.nvim_create_augroup('netrw_live_preview', { clear = true })
local DEBOUNCE_MS = 90
local MAX_ENTRIES = 200
local PREVIEW_BUFFER_CLEANUP_MS = 2000
local function is_valid_buffer(buf)
return type(buf) == 'number' and buf > 0 and vim.api.nvim_buf_is_valid(buf)
@ -53,6 +55,46 @@ local function close_timer(state, key)
state[key] = nil
end
local function restore_preview_buffer(buf, for_editing)
local metadata = preview_buffers[buf]
if not metadata or not is_valid_buffer(buf) then
return
end
vim.b[buf].netrw_live_preview_buffer = nil
vim.bo[buf].bufhidden = metadata.bufhidden
vim.bo[buf].buflisted = for_editing or metadata.buflisted
vim.bo[buf].syntax = metadata.syntax
if metadata.treesitter_active then
pcall(vim.treesitter.start, buf)
end
preview_buffers[buf] = nil
end
local function schedule_preview_buffer_cleanup(state)
if not state or state.preview_cleanup_scheduled then
return
end
state.preview_cleanup_scheduled = true
vim.defer_fn(function()
state.preview_cleanup_scheduled = false
for buf, metadata in pairs(preview_buffers) do
local is_preview = is_valid_buffer(buf) and vim.b[buf].netrw_live_preview_buffer == true
local is_visible = is_valid_buffer(buf) and #vim.fn.win_findbuf(buf) > 0
if not is_preview or not is_visible then
if is_preview and metadata.loaded then
restore_preview_buffer(buf, false)
end
if not is_preview then
preview_buffers[buf] = nil
end
end
end
end, PREVIEW_BUFFER_CLEANUP_MS)
end
local function close_preview(state)
if not state then
return
@ -61,6 +103,7 @@ local function close_preview(state)
local preview = find_preview_window(state.tab)
if preview then
pcall(vim.api.nvim_win_close, preview, false)
schedule_preview_buffer_cleanup(state)
end
end
@ -395,7 +438,48 @@ end
local function show_native_preview(state, entry)
state.native_preview_active = true
local existing_buf = entry.path and vim.fn.bufnr(entry.path) or -1
local existing = is_valid_buffer(existing_buf) and {
buf = existing_buf,
loaded = vim.api.nvim_buf_is_loaded(existing_buf),
buflisted = vim.bo[existing_buf].buflisted,
bufhidden = vim.bo[existing_buf].bufhidden,
syntax = vim.bo[existing_buf].syntax,
treesitter_active = vim.treesitter.highlighter.active[existing_buf] ~= nil,
} or nil
local ok, shown = pcall(native_preview, entry)
local preview = find_preview_window(state.tab)
if preview then
local preview_buf = vim.api.nvim_win_get_buf(preview)
local preview_only = #vim.fn.win_findbuf(preview_buf) == 1
if vim.bo[preview_buf].buftype == '' and preview_only then
preview_buffers[preview_buf] = preview_buffers[preview_buf] or {
loaded = existing ~= nil and existing.buf == preview_buf and existing.loaded,
buflisted = existing ~= nil and existing.buf == preview_buf and existing.buflisted or false,
bufhidden = existing ~= nil and existing.buf == preview_buf and existing.bufhidden or '',
syntax = existing ~= nil and existing.buf == preview_buf and existing.syntax or '',
treesitter_active = existing ~= nil
and existing.buf == preview_buf
and existing.loaded
and existing.treesitter_active
or vim.treesitter.highlighter.active[preview_buf] ~= nil,
}
vim.bo[preview_buf].bufhidden = 'hide'
vim.bo[preview_buf].buflisted = preview_buffers[preview_buf].buflisted
vim.b[preview_buf].netrw_live_preview_buffer = true
pcall(vim.treesitter.stop, preview_buf)
local filetype = vim.bo[preview_buf].filetype
if filetype ~= '' then
vim.bo[preview_buf].syntax = filetype
end
end
end
if is_valid_window(state.win) then
pcall(vim.api.nvim_set_current_win, state.win)
end
@ -445,7 +529,12 @@ local function preview_selected(state, allow_create)
end
local function schedule_preview(state)
if not state or not find_preview_window(state.tab) then
if not state then
return
end
if not find_preview_window(state.tab) then
schedule_preview_buffer_cleanup(state)
return
end
@ -459,6 +548,26 @@ local function schedule_preview(state)
end))
end
vim.api.nvim_create_autocmd('BufEnter', {
group = preview_group,
desc = 'Restore normal editing behavior for a file previously used as a netrw preview',
callback = function(args)
local buf = args.buf
if not is_valid_buffer(buf) or vim.b[buf].netrw_live_preview_buffer ~= true then
return
end
local win = vim.api.nvim_get_current_win()
if is_valid_window(win) and vim.wo[win].previewwindow then
return
end
if preview_buffers[buf] then
restore_preview_buffer(buf, true)
end
end,
})
local function state_for(buf)
local state = states[buf]
if not state then