feat(explorer): recursive copy and copying of selected items with c

This commit is contained in:
Folke Lemaitre 2025-02-02 20:32:51 +01:00
parent 5c5b40b5d0
commit 2528fcb02c
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
3 changed files with 107 additions and 39 deletions

View file

@ -413,8 +413,9 @@ function M:select_key(item)
return item._select_key
end
---@param items snacks.picker.Item[]
---@param items? snacks.picker.Item[]
function M:set_selected(items)
items = items or {}
self.selected = items
self.selected_map = {}
for _, item in ipairs(items) do

View file

@ -407,6 +407,16 @@ function M.setup(opts)
})
end
---@param prompt string
---@param fn fun()
function M.confirm(prompt, fn)
Snacks.picker.select({ "Yes", "No" }, { prompt = prompt }, function(_, idx)
if idx == 1 then
fn()
end
end)
end
---@type table<string, snacks.picker.Action.spec>
M.actions = {
explorer_update = function(picker)
@ -499,57 +509,58 @@ M.actions = {
---@type string[]
local paths = vim.tbl_map(Snacks.picker.util.path, picker:selected())
if #paths == 0 then
Snacks.notify.warn("No files selected to move")
return
Snacks.notify.warn("No files selected to move. Renaming instead.")
return M.actions.explorer_rename(picker, picker:current())
end
local target = state:dir()
local what = #paths == 1 and vim.fn.fnamemodify(paths[1], ":p:~:.") or #paths .. " files"
local t = vim.fn.fnamemodify(target, ":p:~:.")
Snacks.picker.select({ "Yes", "No" }, { prompt = "Move " .. what .. " to " .. t .. "?" }, function(_, idx)
if idx == 1 then
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)
end
state:update()
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)
end
picker.list:set_selected() -- clear selection
state:update()
end)
end,
explorer_copy = function(picker, item)
if not item then
return
end
if item.dir then
Snacks.notify.warn("Cannot copy directories")
local state = M.get_state(picker)
---@type string[]
local paths = vim.tbl_map(Snacks.picker.util.path, picker:selected())
-- Copy selection
if #paths > 0 then
local dir = state:dir()
Snacks.picker.util.copy(paths, dir)
state:open(dir)
picker.list:set_selected() -- clear selection
state:update()
return
end
local state = M.get_state(picker)
Snacks.input({
prompt = "Copy to",
}, function(value)
if not value or value:find("^%s$") then
return
end
local dir = state:dir()
local path = vim.fs.normalize(dir .. "/" .. value)
vim.fn.mkdir(vim.fs.dirname(path), "p")
state:open(dir)
if uv.fs_stat(path) then
Snacks.notify.warn("File already exists:\n- `" .. path .. "`")
local dir = vim.fs.dirname(item.file)
local to = vim.fs.normalize(dir .. "/" .. value)
if uv.fs_stat(to) then
Snacks.notify.warn("File already exists:\n- `" .. to .. "`")
return
end
uv.fs_copyfile(item.file, path, function(err)
if err then
Snacks.notify.error("Failed to copy `" .. item.file .. "` to `" .. path .. "`:\n- " .. err)
end
state:update()
end)
Snacks.picker.util.copy_path(item.file, to)
state:open(dir)
state:update()
end)
end,
explorer_del = function(picker)
@ -560,16 +571,14 @@ M.actions = {
return
end
local what = #paths == 1 and vim.fn.fnamemodify(paths[1], ":p:~:.") or #paths .. " files"
Snacks.picker.select({ "Yes", "No" }, { prompt = "Delete " .. what .. "?" }, function(_, idx)
if idx == 1 then
for _, path in ipairs(paths) do
local ok, err = pcall(vim.fn.delete, path, "rf")
if not ok then
Snacks.notify.error("Failed to delete `" .. path .. "`:\n- " .. err)
end
M.confirm("Delete " .. what .. "?", function(_, idx)
for _, path in ipairs(paths) do
local ok, err = pcall(vim.fn.delete, path, "rf")
if not ok then
Snacks.notify.error("Failed to delete `" .. path .. "`:\n- " .. err)
end
state:update()
end
state:update()
end)
end,
explorer_focus = function(picker)
@ -597,7 +606,6 @@ M.actions = {
end,
confirm = function(picker, item, action)
local state = M.get_state(picker)
local item = picker:current()
if not item then
return
elseif item.dir then

View file

@ -444,4 +444,63 @@ function M.dir(item)
return vim.fn.isdirectory(path) == 1 and path or vim.fs.dirname(path)
end
---@param paths string[]
---@param dir string
function M.copy(paths, dir)
dir = vim.fs.normalize(dir)
paths = vim.tbl_map(vim.fs.normalize, paths) ---@type string[]
for _, path in ipairs(paths) do
local name = vim.fn.fnamemodify(path, ":t")
local to = dir .. "/" .. name
M.copy_path(path, to)
end
end
---@param from string
---@param to string
function M.copy_path(from, to)
if not uv.fs_stat(from) then
Snacks.notify.error(("File `%s` does not exist"):format(from))
return
end
if vim.fn.isdirectory(from) == 1 then
M.copy_dir(from, to)
else
M.copy_file(from, to)
end
end
---@param from string
---@param to string
function M.copy_file(from, to)
if vim.fn.filereadable(from) == 0 then
Snacks.notify.error(("File `%s` is not readable"):format(from))
return
end
if uv.fs_stat(to) then
Snacks.notify.error(("File `%s` already exists"):format(to))
return
end
local dir = vim.fs.dirname(to)
vim.fn.mkdir(dir, "p")
local ok, err = uv.fs_copyfile(from, to, { excl = true, ficlone = true })
if not ok then
Snacks.notify.error(("Failed to copy file:\n - from: `%s`\n- to: `%s`\n%s"):format(from, to, err))
end
end
---@param from string
---@param to string
function M.copy_dir(from, to)
if vim.fn.isdirectory(from) == 0 then
Snacks.notify.error(("Directory `%s` does not exist"):format(from))
return
end
vim.fn.mkdir(to, "p")
for fname in vim.fs.dir(from, { follow = false }) do
local path = from .. "/" .. fname
M.copy_path(path, to .. "/" .. fname)
end
end
return M