From 98294e5d5dc55f04741bcee60b218ac5337c067c Mon Sep 17 00:00:00 2001 From: Steven Arcangeli Date: Tue, 17 Oct 2023 07:24:03 +0000 Subject: [PATCH] refactor: overhaul the UI --- lua/oil/adapters/trash/freedesktop.lua | 90 ++++++++++++++++++-------- lua/oil/init.lua | 1 + lua/oil/mutator/init.lua | 23 +++++-- lua/oil/view.lua | 10 +-- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/lua/oil/adapters/trash/freedesktop.lua b/lua/oil/adapters/trash/freedesktop.lua index 00bfd214..d59cdafd 100644 --- a/lua/oil/adapters/trash/freedesktop.lua +++ b/lua/oil/adapters/trash/freedesktop.lua @@ -1,6 +1,5 @@ -- Based on the FreeDesktop.org trash specification -- https://specifications.freedesktop.org/trash-spec/trashspec-1.0.html --- TODO make sure that the subdirs for trash use the same entry as the root local cache = require("oil.cache") local config = require("oil.config") local constants = require("oil.constants") @@ -103,21 +102,6 @@ local function get_write_trash_dir(path) return top_trash end ----@param path string ----@return boolean -local function is_dev_root(path) - if path == "/" then - return true - end - local stat = uv.fs_stat(path) - if not stat then - return false - end - local dev = stat.dev - local parent = vim.fs.dirname(path) - return uv.fs_stat(parent).dev ~= dev -end - ---@param path string ---@return string[] local function get_read_trash_dirs(path) @@ -141,20 +125,24 @@ M.normalize_url = function(url, callback) ) end ----@param _url string +---@param url string ---@param entry oil.Entry ---@param cb fun(path: string) -M.get_entry_path = function(_url, entry, cb) +M.get_entry_path = function(url, entry, cb) local internal_entry = assert(cache.get_entry_by_id(entry.id)) local meta = internal_entry[FIELD_META] ---@type oil.TrashInfo local trash_info = meta.trash_info + if not trash_info then + -- This is a subpath in the trash + M.normalize_url(url, cb) + return + end local path = fs.os_to_posix_path(trash_info.trash_file) if meta.stat.type == "directory" then path = util.addslash(path) end - local url = "oil://" .. path - cb(url) + cb("oil://" .. path) end ---@class oil.TrashInfo @@ -237,7 +225,6 @@ M.list = function(url, column_defs, cb) cb = vim.schedule_wrap(cb) local _, path = util.parse_url(url) assert(path) - local is_in_dev_root = is_dev_root(path) local trash_dirs = get_read_trash_dirs(path) local trash_idx = 0 @@ -248,6 +235,16 @@ M.list = function(url, column_defs, cb) if not trash_dir then return cb() end + + -- Show all files from the trash directory if we are in the root of the device, which we can + -- tell if the trash dir is a subpath of our current path + local show_all_files = fs.is_subpath(path, trash_dir) + -- The first trash dir is a special case; it is in the home directory and we should only show + -- all entries if we are in the top root path "/" + if trash_idx == 1 then + show_all_files = path == "/" + end + local info_dir = fs.join(trash_dir, "info") ---@diagnostic disable-next-line: param-type-mismatch uv.fs_opendir(info_dir, function(open_err, fd) @@ -288,7 +285,7 @@ M.list = function(url, column_defs, cb) poll() else local parent = util.addslash(vim.fn.fnamemodify(info.original_path, ":h")) - if path == parent or is_in_dev_root then + if path == parent or show_all_files then local name = vim.fn.fnamemodify(info.trash_file, ":t") ---@diagnostic disable-next-line: undefined-field local cache_entry = cache.create_entry(url, name, info.stat.type) @@ -300,6 +297,21 @@ M.list = function(url, column_defs, cb) } table.insert(internal_entries, cache_entry) end + if path ~= parent and (show_all_files or fs.is_subpath(path, parent)) then + local name = parent:sub(path:len() + 1) + local next_par = vim.fs.dirname(name) + while next_par ~= "." do + name = next_par + next_par = vim.fs.dirname(name) + end + ---@diagnostic disable-next-line: undefined-field + local cache_entry = cache.create_entry(url, name, "directory") + + cache_entry[FIELD_META] = { + stat = info.stat, + } + table.insert(internal_entries, cache_entry) + end poll() end end) @@ -338,16 +350,20 @@ file_columns.mtime = { local meta = entry[FIELD_META] ---@type oil.TrashInfo local trash_info = meta.trash_info + local time = trash_info and trash_info.deletion_date or meta.stat and meta.stat.mtime.sec + if not time then + return nil + end local fmt = conf and conf.format local ret if fmt then - ret = vim.fn.strftime(fmt, trash_info.deletion_date) + ret = vim.fn.strftime(fmt, time) else - local year = vim.fn.strftime("%Y", trash_info.deletion_date) + local year = vim.fn.strftime("%Y", time) if year ~= current_year then - ret = vim.fn.strftime("%b %d %Y", trash_info.deletion_date) + ret = vim.fn.strftime("%b %d %Y", time) else - ret = vim.fn.strftime("%b %d %H:%M", trash_info.deletion_date) + ret = vim.fn.strftime("%b %d %H:%M", time) end end return ret @@ -384,6 +400,28 @@ end M.supported_cross_adapter_actions = { files = "move" } +---@param action oil.Action +---@return boolean +M.filter_action = function(action) + if action.type == "create" then + return false + elseif action.type == "delete" then + local entry = assert(cache.get_entry_by_url(action.url)) + local meta = entry[FIELD_META] + return meta.trash_info ~= nil + 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)) + return src_adapter.name == "files" or dest_adapter.name == "files" + elseif 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)) + return src_adapter.name == "files" or dest_adapter.name == "files" + else + error(string.format("Bad action type '%s'", action.type)) + end +end + ---@param action oil.Action ---@return string M.render_action = function(action) diff --git a/lua/oil/init.lua b/lua/oil/init.lua index 723457c7..767bbb71 100644 --- a/lua/oil/init.lua +++ b/lua/oil/init.lua @@ -23,6 +23,7 @@ local M = {} ---@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_cross_adapter_actions? table Mapping of adapter name to enum for all other adapters that can be used as a src or dest for move/copy actions. ---@field disable_changes? boolean When true, adapter will not support creating new entries or changing (e.g. renaming) existing entries +---@field filter_action? fun(action: oil.Action): boolean When present, filter out actions as they are created -- TODO remove after https://github.com/folke/neodev.nvim/pull/163 lands ---@diagnostic disable: undefined-field diff --git a/lua/oil/mutator/init.lua b/lua/oil/mutator/init.lua index 138faa45..1ccd0876 100644 --- a/lua/oil/mutator/init.lua +++ b/lua/oil/mutator/init.lua @@ -65,6 +65,11 @@ M.create_actions_from_diffs = function(all_diffs) if not adapter then error("Missing adapter") end + local function add_action(action) + if not adapter.filter_action or adapter.filter_action(action) then + table.insert(actions, action) + end + end local parent_url = vim.api.nvim_buf_get_name(bufnr) for _, diff in ipairs(diffs) do if diff.type == "new" then @@ -86,7 +91,7 @@ M.create_actions_from_diffs = function(all_diffs) -- Parse alternations like foo.{js,test.js} for _, alt in ipairs(vim.split(alternation, ",")) do local alt_url = url .. "/" .. v:gsub("{[^}]+}", alt) - table.insert(actions, { + add_action({ type = "create", url = alt_url, entry_type = entry_type, @@ -95,7 +100,7 @@ M.create_actions_from_diffs = function(all_diffs) end else url = url .. "/" .. v - table.insert(actions, { + add_action({ type = "create", url = url, entry_type = entry_type, @@ -105,7 +110,7 @@ M.create_actions_from_diffs = function(all_diffs) end end elseif diff.type == "change" then - table.insert(actions, { + add_action({ type = "change", url = parent_url .. diff.name, entry_type = diff.entry_type, @@ -121,6 +126,12 @@ M.create_actions_from_diffs = function(all_diffs) end end + local function add_action(action) + local adapter = assert(config.get_adapter_by_scheme(action.dest_url or action.url)) + if not adapter.filter_action or adapter.filter_action(action) then + table.insert(actions, action) + end + end for id, diffs in pairs(diff_by_id) do local entry = cache.get_entry_by_id(id) if not entry then @@ -131,7 +142,7 @@ M.create_actions_from_diffs = function(all_diffs) if has_create then -- MOVE (+ optional copies) when has both creates and delete for i, diff in ipairs(diffs) do - table.insert(actions, { + add_action({ type = i == #diffs and "move" or "copy", entry_type = entry[FIELD_TYPE], dest_url = diff.dest, @@ -140,7 +151,7 @@ M.create_actions_from_diffs = function(all_diffs) end else -- DELETE when no create - table.insert(actions, { + add_action({ type = "delete", entry_type = entry[FIELD_TYPE], url = cache.get_parent_url(id) .. entry[FIELD_NAME], @@ -149,7 +160,7 @@ M.create_actions_from_diffs = function(all_diffs) else -- COPY when create but no delete for _, diff in ipairs(diffs) do - table.insert(actions, { + add_action({ type = "copy", entry_type = entry[FIELD_TYPE], src_url = cache.get_parent_url(id) .. entry[FIELD_NAME], diff --git a/lua/oil/view.lua b/lua/oil/view.lua index d5e83afe..ce9d622a 100644 --- a/lua/oil/view.lua +++ b/lua/oil/view.lua @@ -2,6 +2,7 @@ local cache = require("oil.cache") local columns = require("oil.columns") local config = require("oil.config") local constants = require("oil.constants") +local fs = require("oil.fs") local keymap_util = require("oil.keymap_util") local loading = require("oil.loading") local util = require("oil.util") @@ -427,7 +428,7 @@ local function render_buffer(bufnr, opts) jump = false, jump_first = false, }) - local scheme = util.parse_url(bufname) + local scheme, buf_path = util.parse_url(bufname) local adapter = util.get_adapter(bufnr) if not scheme or not adapter then return false @@ -474,17 +475,18 @@ local function render_buffer(bufnr, opts) util.set_highlights(bufnr, highlights) -- Show original location of trash file as virtual text - if adapter.name == "trash" and bufname == "oil-trash:///" then + if adapter.name == "trash" then local ns = vim.api.nvim_create_namespace("OilVtext") vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + local os_path = fs.posix_to_os_path(assert(buf_path)) for i, entry in ipairs(entry_list) do local meta = entry[FIELD_META] ---@type nil|oil.TrashInfo local trash_info = meta and meta.trash_info if trash_info then - vim.api.nvim_buf_set_extmark(0, ns, i - 1, 0, { + vim.api.nvim_buf_set_extmark(bufnr, ns, i - 1, 0, { virt_text = { - { "➜ " .. trash_info.original_path, "OilTrashSourcePath" }, + { "➜ " .. fs.shorten_path(trash_info.original_path, os_path), "OilTrashSourcePath" }, }, }) end