Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 95 additions & 3 deletions doc/neo-tree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Configuration ............... |neo-tree-configuration|
Setup ..................... |neo-tree-setup|
Source Selector ........... |neo-tree-source-selector|
Filtered Items ............ |neo-tree-filtered-items|
Trash ..................... |neo-tree-trash|
Preview Mode .............. |neo-tree-preview-mode|
Hijack Netrw Behavior ..... |neo-tree-netrw-hijack|
Component Configs ......... |neo-tree-component-configs|
Expand Down Expand Up @@ -307,7 +308,11 @@ A = add_directory: Create a new directory, in this mode it does not
command. Also accepts `config.show_path` options

d = delete: Delete the selected file or directory.
Supports visual selection.~
Supports visual selection.

T = trash: Trash the selected file or directory.
See |neo-tree-trash| to configure.
Supports visual selection.

i = show_file_details Show file details in a popup window, such as size
and last modified date.
Expand Down Expand Up @@ -798,7 +803,7 @@ wish to remove a default mapping without overriding it with your own function,
assign it the string "none". This will cause it to be skipped and allow any
existing global mappings to work.

NOTE: SOME OPTIONS ARE ONLY DOCUMENTED IN THE DEFAULT CONFIG!~
NOTE: SOME OPTIONS ARE ONLY DOCUMENTED IN THE DEFAULT CONFIG! ~
Run `:lua require("neo-tree").paste_default_config()` to dump the fully
commented default config in your current file. Even if you don't want to use
that config as your starting point, you still may want to dump it to a blank
Expand Down Expand Up @@ -975,8 +980,95 @@ In addition to `"tab"` and `"window"`, you can also set the target to `"global"`
for either option, which is the same as using the |cd| command. Setting the target
to `"none"` will prevent neo-tree from setting vim's cwd for that position.

TRASH *neo-tree-trash*

Neo-tree can use existing commands on your system to trash files. By default,
this is bound to `T`.

The default commands that neo-tree tries to use, in order, are:

MacOS: `trash`, or `osascript` commands.

Linux: `gio trash`, `trash`, and `kioclient/kioclient5`.

Windows: `trash`, or `PowerShell` commands.

You can configure the command used by specifying `trash.command` in `setup()`:

>lua
require("neo-tree").setup({
trash = {
command = nil -- the default - uses built-ins.
}
})
<

This option takes three types of commands:

1. A string array listing an `rm`-like trash command to use. In otherwords, a
CLI tool where the invocation of the command looks like `trash ...FILES`.

>lua
require("neo-tree").setup({
trash = {
command = { "your-trash-cli", "--put" }
}
})
<

2. Or a function that either returns a list of commands to execute that will
result in the deletion of the items:

>lua
require("neo-tree").setup({
trash = {
---@param paths string[]
---@return string[][] commands
command = function(paths)
if not require("neo-tree.utils").executable("mv") then
return nil -- defer to built-ins
end
local cmds = {}
for i, p in ipairs(paths) do
cmds[#cmds+1] = { "mv", p, ("%s/trash"):format(vim.env.HOME) }
end
return cmds
end
}
})
<

(if any of these commands fail, the rest are aborted).

3. Or a function that is entirely responsible for deletion, and returns a
(true/false, string) result tuple. Return false to defer to builtin handlers,
return true to succeed, error to stop.

>lua
require("neo-tree").setup({
trash = {
---@type neotree.trash.FunctionGenerator fun(paths: string[]):(fun():boolean,string?)?
command = function(paths)
if not setup then
return nil -- defer to built-ins
end
return function()
for i, p in ipairs(paths) do
-- ... logic to trash the given paths
if something_failed then
return false, err
end
end

return true
end
end
}
})
<


FILTERED ITEMS *neo-tree-filtered-items*
FILTERED ITEMS *neo-tree-filtered-items*

The `filesystem` source has a `filtered_items` section in it's config that
allows you to specify what files and folders should be hidden. By default, any
Expand Down
5 changes: 5 additions & 0 deletions lua/neo-tree/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ local config = {
sort_function = nil , -- uses a custom function for sorting files and directories in the tree
use_popups_for_input = true, -- If false, inputs will use vim.ui.input() instead of custom floats.
use_default_mappings = true,
trash = {
cmd = nil -- by default: powershell script on windows, `trash` or `osascript` on macOS, and `gio trash` or `trash` (like trash-cli) on other Unixes
},
-- source_selector provides clickable tabs to switch between sources.
source_selector = {
winbar = false, -- toggle to show selector on winbar
Expand Down Expand Up @@ -451,6 +454,8 @@ local config = {
},
["A"] = "add_directory", -- also accepts the config.show_path and config.insert_as options.
["d"] = "delete",
-- ["d"] = "trash",
["T"] = "trash",
["r"] = "rename",
["y"] = "copy_to_clipboard",
["x"] = "cut_to_clipboard",
Expand Down
52 changes: 51 additions & 1 deletion lua/neo-tree/health/init.lua
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
local typecheck = require("neo-tree.health.typecheck")
local utils = require("neo-tree.utils")
local health = vim.health

local M = {}
Expand All @@ -21,8 +22,24 @@ local check_dependency = function(modname, repo, optional)
health.ok(repo .. " is installed")
end

---@param path string
---@param desc string?
---@return boolean success
local check_executable = function(path, desc)
if utils.executable(path) then
health.ok(("`%s` is executable"):format(path))
return true
end
local warning = ("`%s` not found"):format(path)
if desc then
warning = table.concat({ warning, desc }, " ")
end
health.warn(warning)
return false
end

function M.check()
health.start("Dependencies")
health.start("Required dependencies")
check_dependency("plenary", "nvim-lua/plenary.nvim")
check_dependency("nui.tree", "MunifTanjim/nui.nvim")

Expand All @@ -42,6 +59,27 @@ function M.check()
health.start("Configuration")
local config = require("neo-tree").ensure_config()
M.check_config(config)

health.start("Trash executables (prioritized in descending order)")
if utils.is_windows then
check_executable("trash", "(from https://github.com/sindresorhus/trash#cli or similar)")
if not check_executable("pwsh", "(https://github.com/PowerShell/PowerShell)") then
check_executable("powershell", "(builtin Windows PowerShell)")
end
elseif utils.is_macos then
check_executable("trash", "(builtin)")
check_executable("osascript", "(builtin)")
else
if check_executable("gio", "(from glib2)") then
if not utils.execute_command({ "gio", "trash", "--list" }) then
health.warn("`gio trash` --list failed, maybe you need `gvfs` installed?")
end
end
check_executable("trash", "(from https://github.com/andreafrancia/trash-cli or similar)")
if not check_executable("kioclient") then
check_executable("kioclient5")
end
end
end

local validate = typecheck.validate
Expand All @@ -59,6 +97,9 @@ function M.check_config(config)
function(cfg)
---@class neotree.health.Validator.Generators
local v = {
---@generic T
---@param validator neotree.health.Validator<T>
---@return fun(arr: T[])
array = function(validator)
---@generic T
---@param arr T[]
Expand Down Expand Up @@ -177,6 +218,15 @@ function M.check_config(config)
validate("sort_function", cfg.sort_function, "function", true)
validate("use_popups_for_input", cfg.use_popups_for_input, "boolean")
validate("use_default_mappings", cfg.use_default_mappings, "boolean")
validate("trash", cfg.trash, function(trash)
validate("cmd", trash.cmd, function(cmd)
if type(cmd) == "function" then
return true -- TODO: maybe better validation here
elseif type(cmd) == "table" then
v.array("string")(cmd)
end
end, true)
end)
validate("source_selector", cfg.source_selector, function(ss)
validate("winbar", ss.winbar, "boolean")
validate("statusline", ss.statusline, "boolean")
Expand Down
13 changes: 9 additions & 4 deletions lua/neo-tree/log.lua
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ log_maker.new = function(config)
or table.concat({ config.plugin_short, prefix }, " ")

local title_opts = { title = config.plugin_short }

---@param message string
---@param level vim.log.levels
local notify = vim.schedule_wrap(function(message, level)
Expand Down Expand Up @@ -205,9 +206,7 @@ log_maker.new = function(config)

-- Output to console
if config.use_console and can_log_to_console then
vim.schedule(function()
notify(msg, log_level)
end)
notify(msg, log_level)
end
end
end
Expand Down Expand Up @@ -262,6 +261,9 @@ log_maker.new = function(config)
log.error = logfunc(Levels.ERROR, make_string)
---Unused, kept around for compatibility at the moment. Remove in v4.0.
log.fatal = logfunc(Levels.FATAL, make_string)

log.notify = notify
log.levels = Levels
-- tree-sitter queries recognize any .format and highlight it w/ string.format highlights
---@type table<string, { format: fun(fmt: string?, ...: any) }>
log.at = {
Expand Down Expand Up @@ -374,8 +376,11 @@ log_maker.new = function(config)
else
errmsg = "assertion failed!"
end
local old = config.use_console
config.use_console = false
log.error(errmsg)
return assert(v, errmsg)
config.use_console = old
error(errmsg, 2)
end

---@param context string
Expand Down
40 changes: 39 additions & 1 deletion lua/neo-tree/sources/common/commands.lua
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,7 @@ M.delete = function(state, callback)
fs_actions.delete_node(node.path, callback)
end

---@param callback function
---@param callback fun(path: string)
---@type neotree.TreeCommandVisual
M.delete_visual = function(state, selected_nodes, callback)
local paths_to_delete = {}
Expand All @@ -725,6 +725,44 @@ M.delete_visual = function(state, selected_nodes, callback)
fs_actions.delete_nodes(paths_to_delete, callback)
end

M.trash = function(state)
local node = assert(state.tree:get_node())
if node.type ~= "file" and node.type ~= "directory" then
log.warn("The `trash` command can only be used on files and directories")
return
end
if node:get_depth() == 1 then
log.error(
"Will not trash root node "
.. node.path
.. ", please back out of the current directory if you want to trash the root node."
)
return
end
fs_actions.trash_node(node.path)
end

---@param callback fun(path: string)
---@type neotree.TreeCommandVisual
M.trash_visual = function(state, selected_nodes, callback)
local paths_to_trash = {}
for _, node_to_trash in pairs(selected_nodes) do
if node_to_trash:get_depth() == 1 then
log.error(
"Will not trash root node "
.. node_to_trash.path
.. ", please back out of the current directory if you want to trash the root node."
)
return
end

if node_to_trash.type == "file" or node_to_trash.type == "directory" then
table.insert(paths_to_trash, node_to_trash.path)
end
end
fs_actions.trash_nodes(paths_to_trash, callback)
end

M.preview = function(state)
Preview.show(state)
end
Expand Down
Loading
Loading