mirror of
https://github.com/neovim/neovim.git
synced 2026-08-17 12:52:52 +00:00
feat(dir): user can sort/filter listings, DirReadPost event #41138
Problem: Directory listing entries cannot be customized (filtered, reordered). Listings are read by a BufReadCmd, which suppresses BufReadPost, so they are the only buffers with no post-read event to hook. Solution: Introduce a post-render User autocmd `DirReadPost`, marking the dir buffer writable for the duration and before the cursor is placed, so handlers can sort or filter it with ordinary commands. Document common recipes
This commit is contained in:
@@ -316,6 +316,8 @@ EVENTS
|
||||
• |SessionWritePre| event emits just before |:mksession|.
|
||||
• |TextPutPre| and |TextPutPost| are triggered before/after putting text.
|
||||
• |TabMoved| is triggered when tabs are reordered.
|
||||
• |DirReadPost| is triggered after each |dir| listing is rendered, so it can
|
||||
be sorted or filtered. See |dir-render|.
|
||||
|
||||
HIGHLIGHTS
|
||||
|
||||
|
||||
@@ -88,6 +88,42 @@ first entry instead of the one it was left on. "wipe" discards the buffer
|
||||
itself, leaving no |alternate-file|; see 'bufhidden'.
|
||||
|
||||
|
||||
Reshaping the listing *dir-render*
|
||||
|
||||
*DirReadPost*
|
||||
A |User| autocommand fired after each listing is rendered, including reloads.
|
||||
The buffer is writable for the duration, so a handler can sort or filter it
|
||||
with ordinary commands, and the cursor is placed afterwards.
|
||||
|
||||
Handlers may reorder or remove lines, but each remaining line must still be an
|
||||
entry name: |dir-buffer-mappings| resolve the line under the cursor against the
|
||||
buffer name, so rewriting line text makes <CR> open a nonexistent path.
|
||||
|
||||
Sort directories last: >vim
|
||||
autocmd User DirReadPost silent keeppatterns sort r /\/$/
|
||||
<
|
||||
Hide dot-prefixed entries: >vim
|
||||
autocmd User DirReadPost silent keeppatterns g/^\./d _
|
||||
<
|
||||
Sort by modification time, newest first: >lua
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'DirReadPost',
|
||||
callback = function(args)
|
||||
local dir = vim.api.nvim_buf_get_name(args.buf)
|
||||
local names = vim.api.nvim_buf_get_lines(args.buf, 0, -1, true)
|
||||
local mtime = {} --- @type table<string, integer>
|
||||
for _, name in ipairs(names) do
|
||||
local stat = vim.uv.fs_stat(vim.fs.joinpath(dir, name))
|
||||
mtime[name] = stat and stat.mtime.sec or 0
|
||||
end
|
||||
table.sort(names, function(a, b)
|
||||
return mtime[a] > mtime[b]
|
||||
end)
|
||||
vim.api.nvim_buf_set_lines(args.buf, 0, -1, true, names)
|
||||
end,
|
||||
})
|
||||
<
|
||||
|
||||
Replacing the directory browser *dir-disable*
|
||||
|
||||
To use another directory browser for the current session, delete the
|
||||
|
||||
@@ -187,6 +187,27 @@ local function set_maps(buf)
|
||||
map('R', '<Plug>(nvim-dir-reload)')
|
||||
end
|
||||
|
||||
--- Let handlers reshape the rendered listing. Unlocks the buffer for the duration and
|
||||
--- restores it afterwards, so a handler can sort, filter, or delete lines with ordinary
|
||||
--- commands.
|
||||
---@param buf integer
|
||||
---@return boolean
|
||||
local function exec_render_autocmd(buf)
|
||||
if not set_buf_options(buf, { { 'readonly', false }, { 'modifiable', true } }) then
|
||||
return false
|
||||
end
|
||||
-- Errors in handlers are reported by the autocmd machinery; swallow them here so the
|
||||
-- buffer is never left writable.
|
||||
pcall(api.nvim_buf_call, buf, function()
|
||||
api.nvim_exec_autocmds('User', { pattern = 'DirReadPost', modeline = false })
|
||||
end)
|
||||
return set_buf_options(buf, {
|
||||
{ 'modified', false },
|
||||
{ 'readonly', true },
|
||||
{ 'modifiable', false },
|
||||
})
|
||||
end
|
||||
|
||||
---@param buf integer
|
||||
local function setup_render_autocmds(buf)
|
||||
api.nvim_clear_autocmds({ group = listing_group, buffer = buf })
|
||||
@@ -246,14 +267,19 @@ function load(buf, name, provider, restore_view, setup, select)
|
||||
end
|
||||
return
|
||||
end
|
||||
current_state.err, current_state.provider = err, provider
|
||||
vim.b[buf].nvim_dir = current_state
|
||||
|
||||
-- Runs before the cursor is placed, so handlers may reorder entries.
|
||||
if not exec_render_autocmd(buf) then
|
||||
return
|
||||
end
|
||||
if restore_view and api.nvim_get_current_buf() == buf then
|
||||
vim.fn.winrestview(restore_view)
|
||||
end
|
||||
if select and api.nvim_get_current_buf() == buf then
|
||||
select_entry(select)
|
||||
end
|
||||
current_state.err, current_state.provider = err, provider
|
||||
vim.b[buf].nvim_dir = current_state
|
||||
|
||||
if not setup then
|
||||
return
|
||||
|
||||
@@ -332,6 +332,83 @@ describe('nvim.dir', function()
|
||||
eq({ 'file2.txt' }, lines())
|
||||
end)
|
||||
|
||||
it('fires DirReadPost for every listing', function()
|
||||
make_fixture()
|
||||
n.clear({ args = { '--clean' } })
|
||||
exec_lua(function()
|
||||
vim.g.renders = 0
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'DirReadPost',
|
||||
callback = function(args)
|
||||
vim.g.renders = vim.g.renders + 1
|
||||
vim.g.render_lines = vim.api.nvim_buf_get_lines(args.buf, 0, -1, true)
|
||||
vim.g.render_writable = vim.bo[args.buf].modifiable and not vim.bo[args.buf].readonly
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
edit(root)
|
||||
eq(1, exec_lua('return vim.g.renders'))
|
||||
eq({ 'subdir/', '.hidden', 'alpha.txt' }, exec_lua('return vim.g.render_lines'))
|
||||
-- Writable while handlers run, locked again afterwards.
|
||||
eq(true, exec_lua('return vim.g.render_writable'))
|
||||
eq(false, bufopt('modifiable'))
|
||||
eq(true, bufopt('readonly'))
|
||||
eq(false, bufopt('modified'))
|
||||
|
||||
feed('R')
|
||||
poke_eventloop()
|
||||
eq(2, exec_lua('return vim.g.renders'))
|
||||
|
||||
edit(root)
|
||||
eq(3, exec_lua('return vim.g.renders'))
|
||||
|
||||
api.nvim_win_set_cursor(0, { line_of('subdir/'), 0 })
|
||||
feed('<CR>')
|
||||
poke_eventloop()
|
||||
assert_directory(subdir)
|
||||
eq(4, exec_lua('return vim.g.renders'))
|
||||
end)
|
||||
|
||||
it('keeps the listing readonly when a handler errors', function()
|
||||
make_fixture()
|
||||
n.clear({ args = { '--clean' } })
|
||||
exec_lua(function()
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'DirReadPost',
|
||||
callback = function()
|
||||
error('handler error')
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
edit(root)
|
||||
|
||||
eq({ 'subdir/', '.hidden', 'alpha.txt' }, lines())
|
||||
eq(false, bufopt('modifiable'))
|
||||
eq(true, bufopt('readonly'))
|
||||
end)
|
||||
|
||||
it('selects the origin entry after a handler reorders', function()
|
||||
make_fixture()
|
||||
n.clear({ args = { '--clean' } })
|
||||
exec_lua(function()
|
||||
vim.api.nvim_create_autocmd('User', {
|
||||
pattern = 'DirReadPost',
|
||||
callback = function()
|
||||
vim.cmd('silent keeppatterns sort!')
|
||||
end,
|
||||
})
|
||||
end)
|
||||
|
||||
edit(subdir)
|
||||
feed('-')
|
||||
poke_eventloop()
|
||||
|
||||
eq({ 'subdir/', 'alpha.txt', '.hidden' }, lines())
|
||||
eq('subdir/', api.nvim_get_current_line())
|
||||
end)
|
||||
|
||||
it('ignores callbacks from replaced listings', function()
|
||||
n.clear({ args = { '--clean' } })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user