fix(dir): keep a listing buffer when the initial listing fails

Problem:  A failed initial directory listing discards the listing state:
          the user is left in an empty modifiable non-listing buffer, and
          the BufEnter autocmd retries the failing open, repeating the
          error on every re-entry.
Solution: Render the failure as an empty listing with state and handlers
          intact: the error is reported once and "R" or :edit retries the
          listing. Keep the error on the listing state so a failed
          listing is distinguishable from an empty one; the next list
          clears it.
This commit is contained in:
erdivartanovich
2026-08-03 18:31:15 +08:00
parent a3eff66e31
commit d80121327f
2 changed files with 60 additions and 8 deletions

View File

@@ -235,8 +235,13 @@ describe('nvim.dir', function()
exec_lua(function()
require('nvim.dir').open(0, 'custom://error', {
list = function(_, _, cb)
cb('simulated error')
list = function(buf, _, cb)
vim.b[buf].custom_fail = (vim.b[buf].custom_fail or 0) + 1
if vim.b[buf].custom_fail == 1 then
cb('simulated error')
else
cb(nil, { { name = 'recovered.txt', dir = false } })
end
end,
open = function() end,
open_parent = function() end,
@@ -244,7 +249,49 @@ describe('nvim.dir', function()
end)
ok(exec_capture('messages'):find('simulated error', 1, true) ~= nil)
eq(false, exec_lua([[return vim.b.nvim_dir ~= nil]]))
-- A failed initial load renders an empty listing that "R" can retry.
eq('simulated error', exec_lua([[return vim.b.nvim_dir.err]]))
eq({ '' }, lines())
eq('nowrite', bufopt('buftype'))
eq(false, bufopt('modifiable'))
feed('R')
poke_eventloop()
eq({ 'recovered.txt' }, lines())
eq(vim.NIL, exec_lua([[return vim.b.nvim_dir.err]]))
end)
it('failed directory open does not repeat the error on every BufEnter', function()
t.skip(t.is_os('win'), 'directory permissions are POSIX-only')
t.skip(vim.uv.getuid() == 0, 'root ignores directory permissions')
n.clear({ args = { '--clean' } })
make_fixture()
local locked = root .. '/locked'
t.mkdir(locked)
finally(function()
vim.uv.fs_chmod(locked, tonumber('755', 8))
end)
vim.uv.fs_chmod(locked, 0)
edit(root)
api.nvim_win_set_cursor(0, { line_of('locked/'), 0 })
feed('<CR>')
poke_eventloop()
local function count_errors()
local _, n_errors = exec_capture('messages'):gsub('EACCES', '')
return n_errors
end
eq(1, count_errors())
ok(exec_lua([[return vim.b.nvim_dir.err]]):find('EACCES', 1, true) ~= nil)
eq('nowrite', bufopt('buftype'))
edit(file)
feed('<C-^>')
poke_eventloop()
eq(1, count_errors())
vim.uv.fs_chmod(locked, tonumber('755', 8))
t.write_file(locked .. '/inside.txt', 'inside', true)
feed('R')
poke_eventloop()
eq({ 'inside.txt' }, lines())
end)
it('reloads custom listing providers', function()
@@ -587,6 +634,7 @@ describe('nvim.dir', function()
poke_eventloop()
ok(exec_capture('messages'):find('ENOENT', 1, true) ~= nil)
ok(exec_lua([[return vim.b.nvim_dir.err]]):find('ENOENT', 1, true) ~= nil)
assert_directory(subdir)
end)