feat: trash support for linux and mac (#165)

* 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
This commit is contained in:
Steven Arcangeli 2023-11-05 12:40:58 -08:00 committed by GitHub
parent d8f0d91b10
commit 6175bd6462
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1580 additions and 229 deletions

View file

@ -8,6 +8,7 @@ local M = {}
---@alias oil.EntryType "file"|"directory"|"socket"|"link"|"fifo"
---@alias oil.TextChunk string|string[]
---@alias oil.CrossAdapterAction "copy"|"move"
---@class (exact) oil.Adapter
---@field name string The unique name of the adapter (this will be set automatically)
@ -20,7 +21,9 @@ local M = {}
---@field perform_action? fun(action: oil.Action, cb: fun(err: nil|string)) Perform a mutation action. Only needed if adapter is modifiable.
---@field read_file? fun(bufnr: integer) Used for adapters that deal with remote/virtual files. Read the contents of the file into a buffer.
---@field write_file? fun(bufnr: integer) Used for adapters that deal with remote/virtual files. Write the contents of a buffer to the destination.
---@field supported_adapters_for_copy? table<string, boolean> Mapping of adapter name to true for all other adapters that can be used as a src or dest for move/copy actions.
---@field supported_cross_adapter_actions? table<string, oil.CrossAdapterAction> Mapping of adapter name to enum for all other adapters that can be used as a src or dest for move/copy actions.
---@field filter_action? fun(action: oil.Action): boolean When present, filter out actions as they are created
---@field filter_error? fun(action: oil.ParseError): boolean When present, filter out errors from parsing a buffer
-- TODO remove after https://github.com/folke/neodev.nvim/pull/163 lands
---@diagnostic disable: undefined-field
@ -110,34 +113,6 @@ M.discard_all_changes = function()
end
end
---Delete all files in the trash directory
---@private
---@note
--- Trash functionality is incomplete and experimental.
M.empty_trash = function()
local config = require("oil.config")
local fs = require("oil.fs")
local util = require("oil.util")
local trash_url = config.get_trash_url()
if not trash_url then
vim.notify("No trash directory configured", vim.log.levels.WARN)
return
end
local _, path = util.parse_url(trash_url)
assert(path)
local dir = fs.posix_to_os_path(path)
if vim.fn.isdirectory(dir) == 1 then
fs.recursive_delete("directory", dir, function(err)
if err then
vim.notify(string.format("Error emptying trash: %s", err), vim.log.levels.ERROR)
else
vim.notify("Trash emptied")
fs.mkdirp(dir)
end
end)
end
end
---Change the display columns for oil
---@param cols oil.ColumnSpec[]
M.set_columns = function(cols)
@ -177,9 +152,13 @@ end
---Get the oil url for a given directory
---@private
---@param dir nil|string When nil, use the cwd
---@return nil|string The parent url
---@param use_oil_parent nil|boolean If in an oil buffer, return the parent (default true)
---@return string The parent url
---@return nil|string The basename (if present) of the file/dir we were just in
M.get_url_for_path = function(dir)
M.get_url_for_path = function(dir, use_oil_parent)
if use_oil_parent == nil then
use_oil_parent = true
end
local config = require("oil.config")
local fs = require("oil.fs")
local util = require("oil.util")
@ -196,15 +175,16 @@ M.get_url_for_path = function(dir)
return config.adapter_to_scheme.files .. path
else
local bufname = vim.api.nvim_buf_get_name(0)
return M.get_buffer_parent_url(bufname)
return M.get_buffer_parent_url(bufname, use_oil_parent)
end
end
---@private
---@param bufname string
---@param use_oil_parent boolean If in an oil buffer, return the parent
---@return string
---@return nil|string
M.get_buffer_parent_url = function(bufname)
M.get_buffer_parent_url = function(bufname, use_oil_parent)
local config = require("oil.config")
local fs = require("oil.fs")
local pathutil = require("oil.pathutil")
@ -223,13 +203,15 @@ M.get_buffer_parent_url = function(bufname)
return parent_url, basename
else
assert(path)
-- TODO maybe we should remove this special case and turn it into a config
if scheme == "term://" then
---@type string
path = vim.fn.expand(path:match("^(.*)//")) ---@diagnostic disable-line: assign-type-mismatch
return config.adapter_to_scheme.files .. util.addslash(path)
end
if not use_oil_parent then
return bufname
end
local adapter = config.get_adapter_by_scheme(scheme)
local parent_url
if adapter and adapter.get_parent then
@ -672,7 +654,7 @@ M._get_highlights = function()
{
name = "OilDir",
link = "Directory",
desc = "Directories in an oil buffer",
desc = "Directory names in an oil buffer",
},
{
name = "OilDirIcon",
@ -689,6 +671,11 @@ M._get_highlights = function()
link = nil,
desc = "Soft links in an oil buffer",
},
{
name = "OilLinkTarget",
link = "Comment",
desc = "The target of a soft link",
},
{
name = "OilFile",
link = nil,
@ -719,6 +706,26 @@ M._get_highlights = function()
link = "Special",
desc = "Change action in the oil preview window",
},
{
name = "OilRestore",
link = "OilCreate",
desc = "Restore (from the trash) action in the oil preview window",
},
{
name = "OilPurge",
link = "OilDelete",
desc = "Purge (Permanently delete a file from trash) action in the oil preview window",
},
{
name = "OilTrash",
link = "OilDelete",
desc = "Trash (delete a file to trash) action in the oil preview window",
},
{
name = "OilTrashSourcePath",
link = "Comment",
desc = "Virtual text that shows the original path of file in the trash",
},
}
end
@ -855,14 +862,23 @@ M.setup = function(opts)
config.setup(opts)
set_colors()
vim.api.nvim_create_user_command("Oil", function(args)
local util = require("oil.util")
if args.smods.tab == 1 then
vim.cmd.tabnew()
end
local float = false
for i, v in ipairs(args.fargs) do
local trash = false
local i = 1
while i <= #args.fargs do
local v = args.fargs[i]
if v == "--float" then
float = true
table.remove(args.fargs, i)
elseif v == "--trash" then
trash = true
table.remove(args.fargs, i)
else
i = i + 1
end
end
@ -875,7 +891,13 @@ M.setup = function(opts)
end
local method = float and "open_float" or "open"
M[method](unpack(args.fargs))
local path = args.fargs[1]
if trash then
local url = M.get_url_for_path(path, false)
local _, new_path = util.parse_url(url)
path = "oil-trash://" .. new_path
end
M[method](path)
end, { desc = "Open oil file browser on a directory", nargs = "*", complete = "dir" })
local aug = vim.api.nvim_create_augroup("Oil", {})