feat(explorer): deal with existing buffers when renaming / deleting files. Closes #1315

This commit is contained in:
Folke Lemaitre 2025-02-19 22:18:21 +01:00
parent 5f2864e8c9
commit 6614a2c84f
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
3 changed files with 69 additions and 41 deletions

View file

@ -12,6 +12,7 @@ M.meta = {
---@class snacks.bufdelete.Opts
---@field buf? number Buffer to delete. Defaults to the current buffer
---@field file? string Delete buffer by file name. If provided, `buf` is ignored
---@field force? boolean Delete the buffer even if it is modified
---@field filter? fun(buf: number): boolean Filter buffers to delete
---@field wipe? boolean Wipe the buffer instead of deleting it (see `:h :bwipeout`)
@ -37,6 +38,12 @@ function M.delete(opts)
end
local buf = opts.buf or 0
if opts.file then
buf = vim.fn.bufnr(opts.file)
if buf == -1 then
return
end
end
buf = buf == 0 and vim.api.nvim_get_current_buf() or buf
vim.api.nvim_buf_call(buf, function()

View file

@ -182,7 +182,7 @@ function M.actions.explorer_rename(picker, item)
return
end
Snacks.rename.rename_file({
file = item.file,
from = item.file,
on_rename = function(new, old)
Tree:refresh(vim.fs.dirname(old))
Tree:refresh(vim.fs.dirname(new))
@ -205,12 +205,7 @@ function M.actions.explorer_move(picker)
M.confirm("Move " .. what .. " to " .. t .. "?", function()
for _, from in ipairs(paths) do
local to = target .. "/" .. vim.fn.fnamemodify(from, ":t")
Snacks.rename.on_rename_file(from, to, function()
local ok, err = pcall(vim.fn.rename, from, to)
if not ok then
Snacks.notify.error("Failed to move `" .. from .. "`:\n- " .. err)
end
end)
Snacks.rename.rename_file({ from = from, to = to })
Tree:refresh(vim.fs.dirname(from))
end
Tree:refresh(target)
@ -263,7 +258,9 @@ function M.actions.explorer_del(picker)
M.confirm("Delete " .. what .. "?", function()
for _, path in ipairs(paths) do
local ok, err = pcall(vim.fn.delete, path, "rf")
if not ok then
if ok then
Snacks.bufdelete({ file = path, force = true })
else
Snacks.notify.error("Failed to delete `" .. path .. "`:\n- " .. err)
end
Tree:refresh(vim.fs.dirname(path))

View file

@ -5,55 +5,79 @@ M.meta = {
desc = "LSP-integrated file renaming with support for plugins like [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) and [mini.files](https://github.com/echasnovski/mini.files).",
}
local uv = vim.uv or vim.loop
---@param path string
local function realpath(path)
return vim.fs.normalize(uv.fs_realpath(path) or path)
end
-- Prompt for the new filename,
-- Renames the provided file, or the current buffer's file.
-- Prompt for the new filename if `to` is not provided.
-- do the rename, and trigger LSP handlers
---@param opts? {file?: string, on_rename?: fun(new:string, old:string)}
---@param opts? {from?: string, to?:string, on_rename?: fun(to:string, from:string, ok:boolean)}
function M.rename_file(opts)
opts = opts or {}
local buf = vim.api.nvim_get_current_buf()
if opts.file then
buf = vim.fn.bufadd(opts.file)
end
local old = assert(realpath(vim.api.nvim_buf_get_name(buf)))
local root = assert(realpath(uv.cwd() or "."))
local from = vim.fn.fnamemodify(opts.from or opts.file or vim.api.nvim_buf_get_name(0), ":p")
local to = opts.to and vim.fn.fnamemodify(opts.to, ":p") or nil
if old:find(root, 1, true) ~= 1 then
root = vim.fn.fnamemodify(old, ":p:h")
local function rename()
assert(to, "to is required")
M.on_rename_file(from, to, function()
local ok = M._rename(from, to)
if opts.on_rename then
opts.on_rename(to, from, ok)
end
end)
end
local extra = old:sub(#root + 2)
if to then
return rename()
end
local root = vim.fn.getcwd()
if from:find(root, 1, true) ~= 1 then
root = vim.fn.fnamemodify(from, ":p:h")
end
local extra = from:sub(#root + 2)
vim.ui.input({
prompt = "New File Name: ",
default = extra,
completion = "file",
}, function(new)
if not new or new == "" or new == extra then
}, function(value)
if not value or value == "" or value == extra then
return
end
new = vim.fs.normalize(root .. "/" .. new)
vim.fn.mkdir(vim.fs.dirname(new), "p")
M.on_rename_file(old, new, function()
vim.fn.rename(old, new)
if not opts.on_rename then
vim.cmd.edit(new)
end
vim.api.nvim_buf_delete(buf, { force = true })
vim.fn.delete(old)
if opts.on_rename then
opts.on_rename(new, old)
end
end)
to = vim.fs.normalize(root .. "/" .. value)
rename()
end)
end
--- Rename a file and update buffers
---@param from string
---@param to string
---@return boolean ok
function M._rename(from, to)
from = vim.fn.fnamemodify(from, ":p")
to = vim.fn.fnamemodify(to, ":p")
-- rename the file
local ret = vim.fn.rename(from, to)
if ret ~= 0 then
Snacks.notify.error("Failed to rename file: `" .. from .. "`")
return false
end
-- replace buffer in all windows
local from_buf = vim.fn.bufnr(from)
if from_buf >= 0 then
local to_buf = vim.fn.bufadd(to)
vim.bo[to_buf].buflisted = true
for _, win in ipairs(vim.fn.win_findbuf(from_buf)) do
vim.api.nvim_win_call(win, function()
vim.cmd("buffer " .. to_buf)
end)
end
vim.api.nvim_buf_delete(from_buf, { force = true })
end
return true
end
--- Lets LSP clients know that a file has been renamed
---@param from string
---@param to string