Skip to content

Commit

Permalink
feat: trash support for linux and mac (#165)
Browse files Browse the repository at this point in the history
* wip: skeleton code for trash adapter

* refactor: split trash implementation for mac and linux

* fix: ensure we create the .Trash/$uid dir

* feat: code complete linux trash implementation

* doc: write up trash features

* feat: code complete mac trash implementation

* cleanup: remove previous, terrible, undocumented trash feature

* fix: always disabled trash

* feat: show original path of trashed files

* doc: add a note about calling actions directly

* fix: bugs in trash implementation

* fix: schedule_wrap in mac trash

* doc: fix typo and line wrapping

* fix: parsing of arguments to :Oil command

* doc: small documentation tweaks

* doc: fix awkward wording in the toggle_trash action

* fix: warning on Windows when delete_to_trash = true

* feat: :Oil --trash can open specific trash directories

* fix: show all trash files in device root

* fix: trash mtime should be sortable

* fix: shorten_path handles optional trailing slash

* refactor: overhaul the UI

* fix: keep trash original path vtext from stacking

* refactor: replace disable_changes with an error filter

* fix: shorten path names in home directory relative to root

* doc: small README format changes

* cleanup: remove unnecessary preserve_undo logic

* test: add a functional test for the freedesktop trash adapter

* test: more functional tests for trash

* fix: schedule a callback to avoid main loop error

* refactor: clean up mutator logic

* doc: some comments and type annotations
  • Loading branch information
stevearc authored Nov 5, 2023
1 parent d8f0d91 commit 6175bd6
Show file tree
Hide file tree
Showing 27 changed files with 1,578 additions and 227 deletions.
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ oil.nvim supports all the usual plugin managers
<summary>Packer</summary>

```lua
require('packer').startup(function()
use {
'stevearc/oil.nvim',
config = function() require('oil').setup() end
}
require("packer").startup(function()
use({
"stevearc/oil.nvim",
config = function()
require("oil").setup()
end,
})
end)
```

Expand All @@ -57,9 +59,9 @@ end)
<summary>Paq</summary>

```lua
require "paq" {
{'stevearc/oil.nvim'};
}
require("paq")({
{ "stevearc/oil.nvim" },
})
```

</details>
Expand Down Expand Up @@ -154,8 +156,6 @@ require("oil").setup({
delete_to_trash = false,
-- Skip the confirmation popup for simple operations
skip_confirm_for_simple_edits = false,
-- Change this to customize the command used when deleting to trash
trash_command = "trash-put",
-- Selecting a new/moved/renamed file or directory will prompt you to save changes first
prompt_save_on_select_new_entry = true,
-- Oil will automatically delete hidden buffers after this delay
Expand Down Expand Up @@ -184,6 +184,7 @@ require("oil").setup({
["gs"] = "actions.change_sort",
["gx"] = "actions.open_external",
["g."] = "actions.toggle_hidden",
["g\\"] = "actions.toggle_trash",
},
-- Set to false to disable all of the above keymaps
use_default_keymaps = true,
Expand Down Expand Up @@ -277,7 +278,7 @@ nvim oil-ssh://[username@]hostname[:port]/[path]

This may look familiar. In fact, this is the same url format that netrw uses.

Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).
Note that at the moment the ssh adapter does not support Windows machines, and it requires the server to have a `/bin/sh` binary as well as standard unix commands (`ls`, `rm`, `mv`, `mkdir`, `chmod`, `cp`, `touch`, `ln`, `echo`).

## API

Expand Down
54 changes: 51 additions & 3 deletions doc/oil.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ CONTENTS *oil-content
3. Columns |oil-columns|
4. Actions |oil-actions|
5. Highlights |oil-highlights|
6. Trash |oil-trash|

--------------------------------------------------------------------------------
OPTIONS *oil-options*
Expand Down Expand Up @@ -45,8 +46,6 @@ OPTIONS *oil-option
delete_to_trash = false,
-- Skip the confirmation popup for simple operations
skip_confirm_for_simple_edits = false,
-- Change this to customize the command used when deleting to trash
trash_command = "trash-put",
-- Selecting a new/moved/renamed file or directory will prompt you to save changes first
prompt_save_on_select_new_entry = true,
-- Oil will automatically delete hidden buffers after this delay
Expand Down Expand Up @@ -75,6 +74,7 @@ OPTIONS *oil-option
["gs"] = "actions.change_sort",
["gx"] = "actions.open_external",
["g."] = "actions.toggle_hidden",
["g\\"] = "actions.toggle_trash",
},
-- Set to false to disable all of the above keymaps
use_default_keymaps = true,
Expand Down Expand Up @@ -343,6 +343,8 @@ birthtime *column-birthtim
ACTIONS *oil-actions*

These are actions that can be used in the `keymaps` section of config options.
You can also call them directly with
`require("oil.actions").action_name.callback()`

cd *actions.cd*
:cd to the current oil directory
Expand Down Expand Up @@ -408,11 +410,14 @@ tcd *actions.tc
toggle_hidden *actions.toggle_hidden*
Toggle hidden files and directories

toggle_trash *actions.toggle_trash*
Jump to and from the trash for the current directory

--------------------------------------------------------------------------------
HIGHLIGHTS *oil-highlights*

OilDir *hl-OilDir*
Directories in an oil buffer
Directory names in an oil buffer

OilDirIcon *hl-OilDirIcon*
Icon for directories
Expand All @@ -423,6 +428,9 @@ OilSocket *hl-OilSocke
OilLink *hl-OilLink*
Soft links in an oil buffer

OilLinkTarget *hl-OilLinkTarget*
The target of a soft link

OilFile *hl-OilFile*
Normal files in an oil buffer

Expand All @@ -441,5 +449,45 @@ OilCopy *hl-OilCop
OilChange *hl-OilChange*
Change action in the oil preview window

OilRestore *hl-OilRestore*
Restore (from the trash) action in the oil preview window

OilPurge *hl-OilPurge*
Purge (Permanently delete a file from trash) action in the oil preview
window

OilTrash *hl-OilTrash*
Trash (delete a file to trash) action in the oil preview window

OilTrashSourcePath *hl-OilTrashSourcePath*
Virtual text that shows the original path of file in the trash

--------------------------------------------------------------------------------
TRASH *oil-trash*


Oil has built-in support for using the system trash. When
`delete_to_trash = true`, any deleted files will be sent to the trash instead
of being permanently deleted. You can browse the trash for a directory using
the `toggle_trash` action (bound to `g\` by default). You can view all files
in the trash with `:Oil --trash /`.

To restore files, simply delete them from the trash and put them in the desired
destination, the same as any other file operation. If you delete files from the
trash they will be permanently deleted (purged).

Linux:
Oil supports the FreeDesktop trash specification.
https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html
All features should work.

Mac:
Oil has limited support for MacOS due to the proprietary nature of the
implementation. The trash bin can only be viewed as a single dir
(instead of being able to see files that were trashed from a directory).

Windows:
Oil does not yet support the Windows trash. PRs are welcome!

================================================================================
vim:tw=80:ts=2:ft=help:norl:syntax=help:
32 changes: 32 additions & 0 deletions lua/oil/actions.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
local oil = require("oil")
local util = require("oil.util")

-- TODO remove after https://github.com/folke/neodev.nvim/pull/163 lands
---@diagnostic disable: inject-field

local M = {}

M.show_help = {
Expand Down Expand Up @@ -302,6 +305,35 @@ M.change_sort = {
end,
}

M.toggle_trash = {
desc = "Jump to and from the trash for the current directory",
callback = function()
local fs = require("oil.fs")
local bufname = vim.api.nvim_buf_get_name(0)
local scheme, path = util.parse_url(bufname)
local bufnr = vim.api.nvim_get_current_buf()
local url
if scheme == "oil://" then
url = "oil-trash://" .. path
elseif scheme == "oil-trash://" then
url = "oil://" .. path
-- The non-linux trash implementations don't support per-directory trash,
-- so jump back to the stored source buffer.
if not fs.is_linux then
local src_bufnr = vim.b.oil_trash_toggle_src
if src_bufnr and vim.api.nvim_buf_is_valid(src_bufnr) then
url = vim.api.nvim_buf_get_name(src_bufnr)
end
end
else
vim.notify("No trash found for buffer", vim.log.levels.WARN)
return
end
vim.cmd.edit({ args = { url } })
vim.b.oil_trash_toggle_src = bufnr
end,
}

---List actions for documentation generation
---@private
M._get_actions = function()
Expand Down
32 changes: 25 additions & 7 deletions lua/oil/adapters/files.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local permissions = require("oil.adapters.files.permissions")
local trash = require("oil.adapters.files.trash")
local util = require("oil.util")
local uv = vim.uv or vim.loop

local M = {}

local FIELD_NAME = constants.FIELD_NAME
Expand Down Expand Up @@ -147,7 +148,11 @@ if not fs.is_windows then
}
end

local current_year = vim.fn.strftime("%Y")
local current_year
-- Make sure we run this import-time effect in the main loop (mostly for tests)
vim.schedule(function()
current_year = vim.fn.strftime("%Y")
end)

for _, time_key in ipairs({ "ctime", "mtime", "atime", "birthtime" }) do
file_columns[time_key] = {
Expand Down Expand Up @@ -436,7 +441,12 @@ M.render_action = function(action)
elseif action.type == "delete" then
local _, path = util.parse_url(action.url)
assert(path)
return string.format("DELETE %s", M.to_short_os_path(path, action.entry_type))
local short_path = M.to_short_os_path(path, action.entry_type)
if config.delete_to_trash then
return string.format(" TRASH %s", short_path)
else
return string.format("DELETE %s", short_path)
end
elseif action.type == "move" or action.type == "copy" then
local dest_adapter = config.get_adapter_by_scheme(action.dest_url)
if dest_adapter == M then
Expand All @@ -451,7 +461,7 @@ M.render_action = function(action)
M.to_short_os_path(dest_path, action.entry_type)
)
else
-- We should never hit this because we don't implement supported_adapters_for_copy
-- We should never hit this because we don't implement supported_cross_adapter_actions
error("files adapter doesn't support cross-adapter move/copy")
end
else
Expand Down Expand Up @@ -494,7 +504,15 @@ M.perform_action = function(action, cb)
assert(path)
path = fs.posix_to_os_path(path)
if config.delete_to_trash then
trash.recursive_delete(path, cb)
if config.trash_command then
vim.notify_once(
"Oil now has native support for trash. Remove the `trash_command` from your config to try it out!",
vim.log.levels.WARN
)
trash.recursive_delete(path, cb)
else
require("oil.adapters.trash").delete_to_trash(path, cb)
end
else
fs.recursive_delete(action.entry_type, path, cb)
end
Expand All @@ -507,9 +525,9 @@ M.perform_action = function(action, cb)
assert(dest_path)
src_path = fs.posix_to_os_path(src_path)
dest_path = fs.posix_to_os_path(dest_path)
fs.recursive_move(action.entry_type, src_path, dest_path, vim.schedule_wrap(cb))
fs.recursive_move(action.entry_type, src_path, dest_path, cb)
else
-- We should never hit this because we don't implement supported_adapters_for_copy
-- We should never hit this because we don't implement supported_cross_adapter_actions
cb("files adapter doesn't support cross-adapter move")
end
elseif action.type == "copy" then
Expand All @@ -523,7 +541,7 @@ M.perform_action = function(action, cb)
dest_path = fs.posix_to_os_path(dest_path)
fs.recursive_copy(action.entry_type, src_path, dest_path, cb)
else
-- We should never hit this because we don't implement supported_adapters_for_copy
-- We should never hit this because we don't implement supported_cross_adapter_actions
cb("files adapter doesn't support cross-adapter copy")
end
else
Expand Down
2 changes: 1 addition & 1 deletion lua/oil/adapters/ssh.lua
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ M.perform_action = function(action, cb)
end
end

M.supported_adapters_for_copy = { files = true }
M.supported_cross_adapter_actions = { files = "copy" }

---@param bufnr integer
M.read_file = function(bufnr)
Expand Down
9 changes: 9 additions & 0 deletions lua/oil/adapters/trash.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
local fs = require("oil.fs")

if fs.is_mac then
return require("oil.adapters.trash.mac")
elseif fs.is_windows then
error("Trash is not implemented yet on Windows")
else
return require("oil.adapters.trash.freedesktop")
end
Loading

0 comments on commit 6175bd6

Please sign in to comment.