693 lines
19 KiB
Lua
693 lines
19 KiB
Lua
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)
|
|
end
|
|
|
|
local function is_valid_window(win)
|
|
return type(win) == 'number' and win > 0 and vim.api.nvim_win_is_valid(win)
|
|
end
|
|
|
|
local function is_valid_tab(tab)
|
|
return type(tab) == 'number' and tab > 0 and vim.api.nvim_tabpage_is_valid(tab)
|
|
end
|
|
|
|
local function is_remote_path(path)
|
|
return type(path) == 'string' and path:match('^%a[%w+.-]*://') ~= nil
|
|
end
|
|
|
|
local function netrw_call(name, ...)
|
|
return pcall(vim.fn['netrw#Call'], name, ...)
|
|
end
|
|
|
|
local function find_preview_window(tab)
|
|
if not is_valid_tab(tab) then
|
|
return nil
|
|
end
|
|
|
|
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
|
|
if is_valid_window(win) and vim.wo[win].previewwindow then
|
|
return win
|
|
end
|
|
end
|
|
end
|
|
|
|
local function close_timer(state, key)
|
|
key = key or 'timer'
|
|
local timer = state and state[key]
|
|
if not timer then
|
|
return
|
|
end
|
|
|
|
pcall(timer.stop, timer)
|
|
if not timer:is_closing() then
|
|
pcall(timer.close, timer)
|
|
end
|
|
state[key] = nil
|
|
end
|
|
|
|
local function indent_guides_enabled(buf)
|
|
local ok, config = pcall(require, 'ibl.config')
|
|
if not ok or not config.config then
|
|
return nil
|
|
end
|
|
return config.get_config(buf).enabled
|
|
end
|
|
|
|
local function set_indent_guides(buf, enabled)
|
|
if enabled == nil then
|
|
return
|
|
end
|
|
|
|
local ok, ibl = pcall(require, 'ibl')
|
|
if ok and ibl.initialized then
|
|
ibl.setup_buffer(buf, { enabled = enabled })
|
|
end
|
|
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
|
|
vim.diagnostic.enable(metadata.diagnostics_enabled, { bufnr = buf })
|
|
set_indent_guides(buf, metadata.indent_guides_enabled)
|
|
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
|
|
end
|
|
|
|
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
|
|
|
|
local function normalize_local_path(path)
|
|
if type(path) ~= 'string' or path == '' then
|
|
return nil
|
|
end
|
|
|
|
local trimmed = path:gsub('/+$', '')
|
|
if trimmed == '' and path:sub(1, 1) == '/' then
|
|
trimmed = '/'
|
|
end
|
|
return vim.fs.normalize(trimmed)
|
|
end
|
|
|
|
local function selected_entry(buf, win)
|
|
if not is_valid_buffer(buf) or not is_valid_window(win) then
|
|
return nil, 'netrw buffer or window is no longer valid'
|
|
end
|
|
|
|
return vim.api.nvim_win_call(win, function()
|
|
local curdir = vim.b[buf].netrw_curdir
|
|
local ok_word, word = netrw_call('NetrwGetWord')
|
|
if not ok_word or type(word) ~= 'string' or word == '' then
|
|
return nil, 'netrw could not identify the selected entry'
|
|
end
|
|
|
|
if is_remote_path(curdir) then
|
|
return {
|
|
remote = true,
|
|
word = word,
|
|
}
|
|
end
|
|
|
|
local path
|
|
if vim.w[win].netrw_liststyle == 3 and type(vim.w[win].netrw_treetop) == 'string' then
|
|
local ok_tree, tree_path = netrw_call('NetrwTreePath', vim.w[win].netrw_treetop)
|
|
if not ok_tree or type(tree_path) ~= 'string' or tree_path == '' then
|
|
return nil, 'netrw could not resolve the selected tree path'
|
|
end
|
|
|
|
if word == '../' then
|
|
path = vim.fs.dirname(normalize_local_path(tree_path))
|
|
elseif word:sub(-1) == '/' then
|
|
path = tree_path
|
|
else
|
|
path = vim.fs.joinpath(tree_path, (word:gsub('[@*]$', '')))
|
|
end
|
|
else
|
|
local name = word:gsub('/$', ''):gsub('[@*]$', '')
|
|
path = vim.fs.isabs(name) and name or vim.fs.joinpath(curdir, name)
|
|
end
|
|
|
|
return {
|
|
path = normalize_local_path(path),
|
|
remote = false,
|
|
word = word,
|
|
}
|
|
end)
|
|
end
|
|
|
|
local function human_size(bytes)
|
|
if type(bytes) ~= 'number' then
|
|
return '—'
|
|
end
|
|
|
|
local units = { 'B', 'K', 'M', 'G', 'T' }
|
|
local value = bytes
|
|
local unit = 1
|
|
while value >= 1024 and unit < #units do
|
|
value = value / 1024
|
|
unit = unit + 1
|
|
end
|
|
|
|
if unit == 1 then
|
|
return ('%d%s'):format(value, units[unit])
|
|
end
|
|
|
|
local formatted = ('%.1f'):format(value):gsub('%.0$', '')
|
|
return formatted .. units[unit]
|
|
end
|
|
|
|
local function modified_time(stat)
|
|
local seconds = stat and stat.mtime and stat.mtime.sec
|
|
if type(seconds) ~= 'number' then
|
|
return '—'
|
|
end
|
|
|
|
return os.date('%Y-%m-%d %H:%M', seconds)
|
|
end
|
|
|
|
local function visible_in_netrw(name, entry_type)
|
|
local hide_mode = tonumber(vim.g.netrw_hide) or 0
|
|
local pattern = vim.g.netrw_list_hide
|
|
if hide_mode == 0 or type(pattern) ~= 'string' or pattern == '' then
|
|
return true
|
|
end
|
|
|
|
local candidate = name .. (entry_type == 'directory' and '/' or '')
|
|
local hidden = vim.fn.match(candidate, pattern) >= 0
|
|
return (hide_mode == 1 and not hidden) or (hide_mode == 2 and hidden)
|
|
end
|
|
|
|
local function entry_details(directory, name, hinted_type)
|
|
local path = vim.fs.joinpath(directory, name)
|
|
local stat = uv.fs_lstat(path)
|
|
local entry_type = stat and stat.type or hinted_type or 'unknown'
|
|
local display_name = name
|
|
local size = '—'
|
|
|
|
if entry_type == 'directory' then
|
|
display_name = display_name .. '/'
|
|
elseif entry_type == 'file' then
|
|
size = human_size(stat and stat.size)
|
|
elseif entry_type == 'link' then
|
|
local target = uv.fs_readlink(path)
|
|
display_name = display_name .. '@'
|
|
if target then
|
|
display_name = display_name .. ' -> ' .. target
|
|
end
|
|
end
|
|
|
|
return {
|
|
name = name,
|
|
display_name = display_name,
|
|
path = path,
|
|
type = entry_type,
|
|
size = size,
|
|
bytes = entry_type == 'file' and stat and stat.size or 0,
|
|
modified = modified_time(stat),
|
|
}
|
|
end
|
|
|
|
local function plural(count, singular, plural_form)
|
|
return ('%d %s'):format(count, count == 1 and singular or plural_form)
|
|
end
|
|
|
|
local function directory_lines(path)
|
|
local scan, scan_error = uv.fs_scandir(path)
|
|
if not scan then
|
|
return {
|
|
path .. '/',
|
|
'',
|
|
'Unable to read directory:',
|
|
tostring(scan_error or 'unknown error'),
|
|
}
|
|
end
|
|
|
|
local entries = {}
|
|
local counts = { directory = 0, file = 0, link = 0, other = 0 }
|
|
local total_file_size = 0
|
|
|
|
while true do
|
|
local name, hinted_type = uv.fs_scandir_next(scan)
|
|
if not name then
|
|
break
|
|
end
|
|
|
|
if visible_in_netrw(name, hinted_type) then
|
|
local entry = entry_details(path, name, hinted_type)
|
|
if entry.type == 'directory' then
|
|
counts.directory = counts.directory + 1
|
|
elseif entry.type == 'file' then
|
|
counts.file = counts.file + 1
|
|
total_file_size = total_file_size + (entry.bytes or 0)
|
|
elseif entry.type == 'link' then
|
|
counts.link = counts.link + 1
|
|
else
|
|
counts.other = counts.other + 1
|
|
end
|
|
table.insert(entries, entry)
|
|
end
|
|
end
|
|
|
|
local ranks = { directory = 1, file = 2, link = 3 }
|
|
table.sort(entries, function(a, b)
|
|
local a_rank = ranks[a.type] or 4
|
|
local b_rank = ranks[b.type] or 4
|
|
if a_rank ~= b_rank then
|
|
return a_rank < b_rank
|
|
end
|
|
return a.name:lower() < b.name:lower()
|
|
end)
|
|
|
|
local lines = {
|
|
path == '/' and '/' or path .. '/',
|
|
'',
|
|
('%-5s %8s %-16s %s'):format('Type', 'Size', 'Modified', 'Name'),
|
|
('%-5s %8s %-16s %s'):format('-----', '--------', '----------------', '----'),
|
|
}
|
|
|
|
for index, entry in ipairs(entries) do
|
|
if index > MAX_ENTRIES then
|
|
break
|
|
end
|
|
local label = entry.type == 'directory' and 'dir'
|
|
or entry.type == 'file' and 'file'
|
|
or entry.type == 'link' and 'link'
|
|
or 'other'
|
|
table.insert(lines, ('%-5s %8s %-16s %s'):format(
|
|
label,
|
|
entry.size,
|
|
entry.modified,
|
|
entry.display_name
|
|
))
|
|
end
|
|
|
|
if #entries == 0 then
|
|
table.insert(lines, '(empty)')
|
|
elseif #entries > MAX_ENTRIES then
|
|
table.insert(lines, ('… showing first %d of %d entries'):format(MAX_ENTRIES, #entries))
|
|
end
|
|
|
|
table.insert(lines, '')
|
|
local summary = {
|
|
plural(counts.directory, 'directory', 'directories'),
|
|
plural(counts.file, 'file', 'files'),
|
|
}
|
|
if counts.link > 0 then
|
|
table.insert(summary, plural(counts.link, 'link', 'links'))
|
|
end
|
|
if counts.other > 0 then
|
|
table.insert(summary, plural(counts.other, 'other entry', 'other entries'))
|
|
end
|
|
table.insert(lines, table.concat(summary, ', ') .. ', ' .. human_size(total_file_size) .. ' in immediate files')
|
|
|
|
return lines
|
|
end
|
|
|
|
local function symlink_lines(path, stat)
|
|
local target, read_error = uv.fs_readlink(path)
|
|
return {
|
|
path .. '@',
|
|
'',
|
|
'Type: symbolic link',
|
|
'Target: ' .. tostring(target or read_error or 'unavailable'),
|
|
'Modified: ' .. modified_time(stat),
|
|
'',
|
|
'The target is not traversed in directory previews.',
|
|
}
|
|
end
|
|
|
|
local function unavailable_lines(path, error_message)
|
|
return {
|
|
tostring(path or '(unknown entry)'),
|
|
'',
|
|
'Preview unavailable:',
|
|
tostring(error_message or 'entry no longer exists'),
|
|
}
|
|
end
|
|
|
|
local function ensure_summary_buffer(state)
|
|
if is_valid_buffer(state.summary_buf) then
|
|
return state.summary_buf
|
|
end
|
|
|
|
local buf = vim.api.nvim_create_buf(false, true)
|
|
vim.bo[buf].buftype = 'nofile'
|
|
vim.bo[buf].bufhidden = 'wipe'
|
|
vim.bo[buf].swapfile = false
|
|
vim.bo[buf].filetype = 'netrw-preview'
|
|
vim.b[buf].netrw_directory_preview = true
|
|
state.summary_buf = buf
|
|
return buf
|
|
end
|
|
|
|
local function set_summary_lines(buf, lines)
|
|
vim.bo[buf].readonly = false
|
|
vim.bo[buf].modifiable = true
|
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
vim.bo[buf].modified = false
|
|
vim.bo[buf].modifiable = false
|
|
vim.bo[buf].readonly = true
|
|
end
|
|
|
|
local function configure_summary_window(win)
|
|
vim.wo[win].previewwindow = true
|
|
vim.wo[win].number = false
|
|
vim.wo[win].relativenumber = false
|
|
vim.wo[win].cursorline = false
|
|
vim.wo[win].wrap = false
|
|
vim.wo[win].list = false
|
|
vim.wo[win].spell = false
|
|
vim.wo[win].signcolumn = 'no'
|
|
end
|
|
|
|
local function show_summary(state, netrw_win, lines)
|
|
local buf = ensure_summary_buffer(state)
|
|
set_summary_lines(buf, lines)
|
|
|
|
local preview = find_preview_window(state.tab)
|
|
if preview then
|
|
local ok = pcall(vim.api.nvim_win_set_buf, preview, buf)
|
|
if not ok then
|
|
return false
|
|
end
|
|
else
|
|
local ok, created = pcall(vim.api.nvim_open_win, buf, false, {
|
|
split = 'below',
|
|
win = netrw_win,
|
|
noautocmd = true,
|
|
})
|
|
if not ok then
|
|
return false
|
|
end
|
|
preview = created
|
|
end
|
|
|
|
configure_summary_window(preview)
|
|
vim.api.nvim_win_set_cursor(preview, { 1, 0 })
|
|
return true
|
|
end
|
|
|
|
local function native_preview(entry)
|
|
local path = entry.path
|
|
if entry.remote then
|
|
-- Match netrw's own `p` mapping arguments for local and remote listings.
|
|
local ok_path, resolved = netrw_call('NetrwBrowseChgDir', 1, entry.word, 1, 1)
|
|
if not ok_path then
|
|
return false
|
|
end
|
|
path = resolved
|
|
end
|
|
|
|
if type(path) ~= 'string' or path == '' then
|
|
return false
|
|
end
|
|
|
|
local ok = netrw_call('NetrwPreview', path)
|
|
return ok
|
|
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,
|
|
diagnostics_enabled = vim.diagnostic.is_enabled({ bufnr = existing_buf }),
|
|
indent_guides_enabled = indent_guides_enabled(existing_buf),
|
|
} 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,
|
|
diagnostics_enabled = existing ~= nil
|
|
and existing.buf == preview_buf
|
|
and existing.diagnostics_enabled
|
|
or vim.diagnostic.is_enabled({ bufnr = preview_buf }),
|
|
indent_guides_enabled = existing ~= nil
|
|
and existing.buf == preview_buf
|
|
and existing.indent_guides_enabled
|
|
or indent_guides_enabled(preview_buf),
|
|
}
|
|
|
|
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)
|
|
vim.diagnostic.enable(false, { bufnr = preview_buf })
|
|
vim.bo[preview_buf].syntax = ''
|
|
set_indent_guides(preview_buf, false)
|
|
end
|
|
end
|
|
|
|
if is_valid_window(state.win) then
|
|
pcall(vim.api.nvim_set_current_win, state.win)
|
|
end
|
|
state.native_preview_active = false
|
|
return ok and shown
|
|
end
|
|
|
|
local function preview_selected(state, allow_create)
|
|
if not state or not is_valid_buffer(state.buf) or not is_valid_window(state.win) then
|
|
return
|
|
end
|
|
|
|
if vim.api.nvim_win_get_buf(state.win) ~= state.buf then
|
|
return
|
|
end
|
|
|
|
if not find_preview_window(state.tab) and not allow_create then
|
|
return
|
|
end
|
|
|
|
local entry, resolve_error = selected_entry(state.buf, state.win)
|
|
if not entry then
|
|
show_summary(state, state.win, unavailable_lines(nil, resolve_error))
|
|
return
|
|
end
|
|
|
|
if entry.remote then
|
|
show_native_preview(state, entry)
|
|
return
|
|
end
|
|
|
|
local stat, stat_error = uv.fs_lstat(entry.path)
|
|
if not stat then
|
|
show_summary(state, state.win, unavailable_lines(entry.path, stat_error))
|
|
return
|
|
end
|
|
|
|
if stat.type == 'directory' then
|
|
show_summary(state, state.win, directory_lines(entry.path))
|
|
elseif stat.type == 'link' then
|
|
show_summary(state, state.win, symlink_lines(entry.path, stat))
|
|
elseif stat.type == 'file' then
|
|
show_native_preview(state, entry)
|
|
else
|
|
show_summary(state, state.win, unavailable_lines(entry.path, 'unsupported entry type: ' .. stat.type))
|
|
end
|
|
end
|
|
|
|
local function schedule_preview(state)
|
|
if not state then
|
|
return
|
|
end
|
|
|
|
if not find_preview_window(state.tab) then
|
|
schedule_preview_buffer_cleanup(state)
|
|
return
|
|
end
|
|
|
|
if not state.timer then
|
|
state.timer = uv.new_timer()
|
|
end
|
|
|
|
state.timer:stop()
|
|
state.timer:start(DEBOUNCE_MS, 0, vim.schedule_wrap(function()
|
|
preview_selected(state, false)
|
|
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
|
|
state = { buf = buf }
|
|
states[buf] = state
|
|
end
|
|
return state
|
|
end
|
|
|
|
local function schedule_leave_cleanup(state)
|
|
close_timer(state, 'leave_timer')
|
|
state.leave_timer = uv.new_timer()
|
|
state.leave_timer:start(20, 0, vim.schedule_wrap(function()
|
|
close_timer(state, 'leave_timer')
|
|
|
|
if state.native_preview_active then
|
|
return
|
|
end
|
|
|
|
local current_win = vim.api.nvim_get_current_win()
|
|
local still_in_explorer = is_valid_window(current_win)
|
|
and current_win == state.win
|
|
and vim.api.nvim_win_get_buf(current_win) == state.buf
|
|
if not still_in_explorer then
|
|
close_preview(state)
|
|
end
|
|
end))
|
|
end
|
|
|
|
function M.setup(buf)
|
|
if vim.fn.has('nvim-0.12') == 0 or not is_valid_buffer(buf) then
|
|
return false
|
|
end
|
|
|
|
if vim.b[buf].netrw_live_preview_configured then
|
|
return true
|
|
end
|
|
|
|
if vim.fn.exists('*netrw#Call') ~= 1 then
|
|
return false
|
|
end
|
|
|
|
vim.b[buf].netrw_live_preview_configured = true
|
|
local state = state_for(buf)
|
|
|
|
vim.keymap.set('n', 'p', function()
|
|
state.win = vim.api.nvim_get_current_win()
|
|
state.tab = vim.api.nvim_get_current_tabpage()
|
|
preview_selected(state, true)
|
|
end, {
|
|
buffer = buf,
|
|
desc = 'Netrw: Preview selection and enable live updates',
|
|
nowait = true,
|
|
silent = true,
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd('CursorMoved', {
|
|
group = preview_group,
|
|
buffer = buf,
|
|
desc = 'Update an existing netrw preview after cursor movement',
|
|
callback = function()
|
|
state.win = vim.api.nvim_get_current_win()
|
|
state.tab = vim.api.nvim_get_current_tabpage()
|
|
schedule_preview(state)
|
|
end,
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd({ 'BufLeave', 'WinLeave' }, {
|
|
group = preview_group,
|
|
buffer = buf,
|
|
desc = 'Close the netrw preview when leaving the explorer',
|
|
callback = function()
|
|
close_timer(state)
|
|
schedule_leave_cleanup(state)
|
|
end,
|
|
})
|
|
|
|
vim.api.nvim_create_autocmd('BufWipeout', {
|
|
group = preview_group,
|
|
buffer = buf,
|
|
once = true,
|
|
desc = 'Release netrw preview resources',
|
|
callback = function()
|
|
close_timer(state)
|
|
close_timer(state, 'leave_timer')
|
|
close_preview(state)
|
|
states[buf] = nil
|
|
end,
|
|
})
|
|
|
|
return true
|
|
end
|
|
|
|
return M
|