local cache = require("oil.cache") local config = require("oil.config") local files = require("oil.adapters.files") local fs = require("oil.fs") local util = require("oil.util") local uv = vim.uv or vim.loop local M = {} local function touch_dir(path) uv.fs_mkdir(path, 448) -- 0700 end ---Gets the location of the home trash dir, creating it if necessary ---@return string local function get_trash_dir() local trash_dir = fs.join(assert(uv.os_homedir()), ".Trash") touch_dir(trash_dir) return trash_dir end ---@param url string ---@param callback fun(url: string) M.normalize_url = function(url, callback) local scheme, path = util.parse_url(url) assert(path) callback(scheme .. "/") end ---@param url string ---@param entry oil.Entry ---@param cb fun(path: string) M.get_entry_path = function(url, entry, cb) local trash_dir = get_trash_dir() local path = fs.join(trash_dir, entry.name) if entry.type == "directory" then path = "oil://" .. path end cb(path) end ---@param url string ---@param column_defs string[] ---@param cb fun(err?: string, entries?: oil.InternalEntry[], fetch_more?: fun()) M.list = function(url, column_defs, cb) cb = vim.schedule_wrap(cb) local _, path = util.parse_url(url) assert(path) local trash_dir = get_trash_dir() ---@diagnostic disable-next-line: param-type-mismatch, discard-returns uv.fs_opendir(trash_dir, function(open_err, fd) if open_err then if open_err:match("^ENOENT: no such file or directory") then -- If the directory doesn't exist, treat the list as a success. We will be able to traverse -- and edit a not-yet-existing directory. return cb() else return cb(open_err) end end local read_next read_next = function() uv.fs_readdir(fd, function(err, entries) if err then uv.fs_closedir(fd, function() cb(err) end) return elseif entries then local internal_entries = {} local poll = util.cb_collect(#entries, function(inner_err) if inner_err then cb(inner_err) else cb(nil, internal_entries, read_next) end end) for _, entry in ipairs(entries) do -- TODO: read .DS_Store and filter by original dir local cache_entry = cache.create_entry(url, entry.name, entry.type) table.insert(internal_entries, cache_entry) poll() end else uv.fs_closedir(fd, function(close_err) if close_err then cb(close_err) else cb() end end) end end) end read_next() ---@diagnostic disable-next-line: param-type-mismatch end, 10000) end ---@param bufnr integer ---@return boolean M.is_modifiable = function(bufnr) return true end ---@param name string ---@return nil|oil.ColumnDefinition M.get_column = function(name) return nil end M.supported_cross_adapter_actions = { files = "move" } ---@param action oil.Action ---@return string M.render_action = function(action) if action.type == "create" then return string.format("CREATE %s", action.url) elseif action.type == "delete" then return string.format(" PURGE %s", action.url) elseif action.type == "move" then local src_adapter = assert(config.get_adapter_by_scheme(action.src_url)) local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url)) if src_adapter.name == "files" then local _, path = util.parse_url(action.src_url) assert(path) local short_path = files.to_short_os_path(path, action.entry_type) return string.format(" TRASH %s", short_path) elseif dest_adapter.name == "files" then local _, path = util.parse_url(action.dest_url) assert(path) local short_path = files.to_short_os_path(path, action.entry_type) return string.format("RESTORE %s", short_path) else return string.format(" %s %s -> %s", action.type:upper(), action.src_url, action.dest_url) end elseif action.type == "copy" then return string.format(" %s %s -> %s", action.type:upper(), action.src_url, action.dest_url) else error("Bad action type") end end ---@param action oil.Action ---@param cb fun(err: nil|string) M.perform_action = function(action, cb) local trash_dir = get_trash_dir() if action.type == "create" then local _, path = util.parse_url(action.url) assert(path) path = trash_dir .. path if action.entry_type == "directory" then uv.fs_mkdir(path, 493, function(err) -- Ignore if the directory already exists if not err or err:match("^EEXIST:") then cb() else cb(err) end end) -- 0755 elseif action.entry_type == "link" and action.link then local flags = nil local target = fs.posix_to_os_path(action.link) ---@diagnostic disable-next-line: param-type-mismatch uv.fs_symlink(target, path, flags, cb) else fs.touch(path, config.new_file_mode, cb) end elseif action.type == "delete" then local _, path = util.parse_url(action.url) assert(path) local fullpath = trash_dir .. path fs.recursive_delete(action.entry_type, fullpath, cb) elseif action.type == "move" or action.type == "copy" then local src_adapter = assert(config.get_adapter_by_scheme(action.src_url)) local dest_adapter = assert(config.get_adapter_by_scheme(action.dest_url)) local _, src_path = util.parse_url(action.src_url) local _, dest_path = util.parse_url(action.dest_url) assert(src_path and dest_path) if src_adapter.name == "files" then dest_path = trash_dir .. dest_path elseif dest_adapter.name == "files" then src_path = trash_dir .. src_path else dest_path = trash_dir .. dest_path src_path = trash_dir .. src_path end if action.type == "move" then fs.recursive_move(action.entry_type, src_path, dest_path, cb) else fs.recursive_copy(action.entry_type, src_path, dest_path, cb) end else cb(string.format("Bad action type: %s", action.type)) end end ---@param path string ---@param cb fun(err?: string) M.delete_to_trash = function(path, cb) local basename = vim.fs.basename(path) local trash_dir = get_trash_dir() local dest = fs.join(trash_dir, basename) uv.fs_lstat( path, vim.schedule_wrap(function(stat_err, src_stat) if stat_err then return cb(stat_err) end assert(src_stat) if uv.fs_lstat(dest) then local date_str = vim.fn.strftime(" %Y-%m-%dT%H:%M:%S") local name_pieces = vim.split(basename, ".", { plain = true }) if #name_pieces > 1 then table.insert(name_pieces, #name_pieces - 1, date_str) basename = table.concat(name_pieces) else basename = basename .. date_str end dest = fs.join(trash_dir, basename) end local stat_type = src_stat.type fs.recursive_move(stat_type, path, dest, vim.schedule_wrap(cb)) end) ) end return M