Skip to content

Commit a0198de

Browse files
committed
add restore bind
1 parent 1d35ebb commit a0198de

7 files changed

Lines changed: 198 additions & 62 deletions

File tree

doc/neo-tree.txt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,8 @@ MacOS:
993993

994994
Linux:
995995
- `gio trash`
996-
- Neo-tree's Lua implementation of the FreeDesktop XDG trash spec.
996+
- Neo-tree's Lua implementation of the FreeDesktop XDG trash spec at
997+
require('neo-tree.trash.freedesktop').
997998

998999
Windows: `PowerShell` commands.
9991000

@@ -1044,7 +1045,6 @@ falling back to the built-ins:
10441045
(Neo-tree will attempt to run every command in-order, and log output for each
10451046
command that returns with a non-zero error code).
10461047

1047-
10481048
3. Or a function that is entirely responsible for deletion, and returns a
10491049
(true/false, string) result tuple. Return false to defer to builtin handlers,
10501050
return true to succeed, error to stop.
@@ -1072,7 +1072,6 @@ command that returns with a non-zero error code).
10721072
})
10731073
<
10741074

1075-
10761075
FILTERED ITEMS *neo-tree-filtered-items*
10771076

10781077
The `filesystem` source has a `filtered_items` section in it's config that

lua/neo-tree/defaults.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,9 +459,9 @@ local config = {
459459
},
460460
["A"] = "add_directory", -- also accepts the config.show_path and config.insert_as options.
461461
["d"] = "delete",
462-
-- ["d"] = "trash",
463462
["T"] = "trash",
464463
["u"] = "undo", -- currently only supports trash.
464+
["U"] = "restore_from_trash", -- only works on files that are in the recycle bin
465465
["r"] = "rename",
466466
["y"] = "copy_to_clipboard",
467467
["x"] = "cut_to_clipboard",

lua/neo-tree/sources/common/commands.lua

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,44 @@ M.trash_visual = function(state, selected_nodes, callback)
788788
fs_actions.trash_nodes(paths_to_trash, callback, state)
789789
end
790790

791+
M.restore_from_trash = function(state, callback)
792+
local node = assert(state.tree:get_node())
793+
if node.type ~= "file" and node.type ~= "directory" then
794+
log.warn("The `trash` command can only be used on files and directories")
795+
return
796+
end
797+
if node:get_depth() == 1 then
798+
log.error(
799+
"Will not trash root node "
800+
.. node.path
801+
.. ", please back out of the current directory if you want to trash the root node."
802+
)
803+
return
804+
end
805+
fs_actions.restore_node_from_trash(node.path, callback, state)
806+
end
807+
808+
---@param callback fun(paths: string[])
809+
---@type neotree.TreeCommandVisual
810+
M.restore_from_trash_visual = function(state, selected_nodes, callback)
811+
local paths_to_trash = {}
812+
for _, node_to_trash in pairs(selected_nodes) do
813+
if node_to_trash:get_depth() == 1 then
814+
log.error(
815+
"Will not trash root node "
816+
.. node_to_trash.path
817+
.. ", please back out of the current directory if you want to restore the root node."
818+
)
819+
return
820+
end
821+
822+
if node_to_trash.type == "file" or node_to_trash.type == "directory" then
823+
table.insert(paths_to_trash, node_to_trash.path)
824+
end
825+
end
826+
fs_actions.restore_nodes_from_trash(paths_to_trash, callback, state)
827+
end
828+
791829
M.undo = function(state)
792830
---@alias neotree.State.UndoFunction fun()
793831
---@alias neotree.State.Undostack neotree.State.UndoFunction[]

lua/neo-tree/sources/filesystem/lib/fs_actions.lua

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -747,7 +747,7 @@ end
747747
M.trash_node = function(path, callback, state)
748748
local _, name = utils.split_path(path)
749749

750-
log.trace("Deleting node:", path)
750+
log.trace("Trashing node:", path)
751751
local stat = uv.fs_stat(path)
752752

753753
local do_trash = function()
@@ -793,22 +793,88 @@ end
793793
---@param callback fun(paths: string[])?
794794
---@param state neotree.State?
795795
M.trash_nodes = function(paths, callback, state)
796-
local msg = "Are you sure you want to delete " .. #paths .. " items?"
796+
local msg = "Are you sure you want to trash " .. #paths .. " items?"
797797
inputs.confirm(msg, function(confirmed)
798798
if not confirmed then
799799
return
800800
end
801801

802-
local success, err, restorer = trash.trash(paths)
802+
local success, err, restorefunc = trash.trash(paths)
803803
if not success then
804804
log.error(err)
805805
end
806806

807-
if state then
808-
table.insert(state.undostack, function()
809-
trash.restore(paths, restorer)
807+
if state and restorefunc then
808+
table.insert(state.undostack, restorefunc)
809+
end
810+
if callback then
811+
vim.schedule(function()
812+
callback(paths)
810813
end)
811814
end
815+
end)
816+
end
817+
818+
---@param path string
819+
---@param callback fun(path: string)?
820+
---@param state neotree.State?
821+
M.restore_node_from_trash = function(path, callback, state)
822+
local _, name = utils.split_path(path)
823+
824+
log.trace("Restoring node:", path)
825+
local stat = uv.fs_stat(path)
826+
827+
local do_trash = function()
828+
local complete = vim.schedule_wrap(function()
829+
events.fire_event(events.FILE_DELETED, path)
830+
if callback then
831+
callback(path)
832+
end
833+
end)
834+
835+
local event_result = events.fire_event(events.BEFORE_FILE_DELETE, path) or {}
836+
if event_result.handled then
837+
complete()
838+
return
839+
end
840+
841+
local paths = { path }
842+
local success, err = trash.restore(paths)
843+
if not success then
844+
log.error("Could not restore " .. path .. " from trash", err)
845+
return
846+
end
847+
848+
complete()
849+
end
850+
851+
local displayed_name = name
852+
if stat and stat.type == "directory" then
853+
displayed_name = name .. utils.path_separator
854+
end
855+
local msg = string.format("Are you sure you want to restore '%s'?", displayed_name)
856+
inputs.confirm(msg, function(confirmed)
857+
if confirmed then
858+
do_trash()
859+
end
860+
end)
861+
end
862+
863+
---@param paths string[]
864+
---@param callback fun(paths: string[])?
865+
---@param state neotree.State?
866+
M.restore_nodes_from_trash = function(paths, callback, state)
867+
local msg = "Are you sure you want to restore " .. #paths .. " items?"
868+
inputs.confirm(msg, function(confirmed)
869+
if not confirmed then
870+
return
871+
end
872+
873+
local success, err = trash.restore(paths)
874+
if not success then
875+
log.error(err)
876+
end
877+
812878
if callback then
813879
vim.schedule(function()
814880
callback(paths)

lua/neo-tree/trash/freedesktop.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ local function dir_is_writable(path)
1212
return stat and stat.type == "directory" and uv.fs_access(path, "w") or false
1313
end
1414

15-
---@param path string @param opts { recursive: boolean?, remove: boolean? }?
16-
---@param mode number?
15+
---@param path string
16+
---@param opts { recursive: boolean?, remove: boolean?, mode: number? }?
1717
---@return boolean success
1818
---@return string? err
19-
local function mkdir(path, opts, mode)
19+
local function mkdir(path, opts)
2020
opts = opts or {}
2121
local stat = uv.fs_stat(path)
2222
if stat then
@@ -32,12 +32,12 @@ local function mkdir(path, opts, mode)
3232
local parent_stat = uv.fs_stat(parent_path)
3333
if not parent_stat then
3434
if opts.recursive then
35-
mkdir(parent_path, opts, mode)
35+
mkdir(parent_path, opts)
3636
else
3737
return false, "parent dir of " .. path .. " does not exist"
3838
end
3939
end
40-
local res, err = uv.fs_mkdir(path, mode or tonumber("755", 8))
40+
local res, err = uv.fs_mkdir(path, opts.mode or tonumber("755", 8))
4141
res = res or false
4242
return res, err
4343
end

lua/neo-tree/trash/gio.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
local utils = require("neo-tree.utils")
2+
local log = require("neo-tree.log")
3+
local M = {}
4+
5+
---@param gvfs_paths string[]
6+
local restore = function(gvfs_paths)
7+
local restore_ok =
8+
utils.execute_command(vim.list_extend({ "gio", "trash", "--restore" }, gvfs_paths))
9+
return restore_ok
10+
end
11+
12+
---@type neotree.trash.FunctionGenerator
13+
M.generate_trashfunc = function(paths)
14+
if not utils.executable("gio") then
15+
return nil
16+
end
17+
local list_ok, before_list_output = utils.execute_command({ "gio", "trash", "--list" })
18+
if not list_ok then
19+
log.at.warn.format("Error with `gio trash --list`: %s", table.concat(before_list_output, "\n"))
20+
return nil
21+
end
22+
local trashmap = {}
23+
for _, line in ipairs(before_list_output) do
24+
local tab_index = line:find("\t", 1, true)
25+
if tab_index then
26+
local trash_filepath = line:sub(1, tab_index - 1)
27+
local original_filepath = line:sub(tab_index + 1)
28+
trashmap[trash_filepath] = original_filepath
29+
end
30+
end
31+
return function()
32+
local trash_ok, trash_output = utils.execute_command(vim.list_extend({ "gio", "trash" }, paths))
33+
if not trash_ok then
34+
log.at.warn.format("Error with `gio trash`: %s", table.concat(trash_output, "\n"))
35+
end
36+
37+
local after_trash_ok, after_list_output = utils.execute_command({ "gio", "trash", "--list" })
38+
if not after_trash_ok then
39+
log.at.warn.format(
40+
"Error with 2nd `gio trash --list`: %s",
41+
table.concat(after_list_output, "\n")
42+
)
43+
end
44+
local new_trash_items = {}
45+
for _, line in ipairs(after_list_output) do
46+
local tab_index = line:find("\t", 1, true)
47+
if tab_index then
48+
local trash_filepath = line:sub(1, tab_index - 1)
49+
if not trashmap[trash_filepath] then
50+
new_trash_items[#new_trash_items + 1] = trash_filepath
51+
end
52+
end
53+
end
54+
---@type neotree.trash._RestoreFunction
55+
local restorefunc = function()
56+
return restore(new_trash_items)
57+
end
58+
return true, restorefunc
59+
end
60+
end
61+
return M

0 commit comments

Comments
 (0)