From 85c83cf0353445b012dec5d8aa4f5c606f320ef4 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sat, 17 Aug 2024 16:11:43 +1000 Subject: [PATCH 1/3] refactor(#2831): multi instance clipboard --- lua/nvim-tree/actions/fs/copy-paste.lua | 128 ++++++++++++-------- lua/nvim-tree/actions/fs/init.lua | 2 - lua/nvim-tree/api.lua | 18 +-- lua/nvim-tree/explorer/init.lua | 4 + lua/nvim-tree/renderer/decorator/copied.lua | 7 +- lua/nvim-tree/renderer/decorator/cut.lua | 7 +- 6 files changed, 96 insertions(+), 70 deletions(-) diff --git a/lua/nvim-tree/actions/fs/copy-paste.lua b/lua/nvim-tree/actions/fs/copy-paste.lua index b87d28f1b5e..5a71db3ea8c 100644 --- a/lua/nvim-tree/actions/fs/copy-paste.lua +++ b/lua/nvim-tree/actions/fs/copy-paste.lua @@ -9,14 +9,43 @@ local reloaders = require "nvim-tree.actions.reloaders" local find_file = require("nvim-tree.actions.finders.find-file").fn -local M = { - config = {}, +---@enum ACTION +local ACTION = { + none = 0, + copy = 1, + cut = 2, } -local clipboard = { - cut = {}, - copy = {}, -} +---@class ClipboardData absolute paths +---@field copy string[] copied +---@field cut string[] cut + +---@class Clipboard to handle all actions.fs clipboard API +---@field config table hydrated user opts.filters +---@field private explorer Explorer +---@field private data ClipboardData +local Clipboard = {} + +---@param opts table user options +---@param explorer Explorer +---@return Clipboard +function Clipboard:new(opts, explorer) + local o = { + explorer = explorer, + data = { + copy = {}, + cut = {}, + }, + config = { + filesystem_watchers = opts.filesystem_watchers, + actions = opts.actions, + }, + } + + setmetatable(o, self) + self.__index = self + return o +end ---@param source string ---@param destination string @@ -165,37 +194,42 @@ local function toggle(node, clip) notify.info(notify_node .. " added to clipboard.") end -function M.clear_clipboard() - clipboard.cut = {} - clipboard.copy = {} +---Clear copied and cut +function Clipboard:clear_clipboard() + self.data.cut = {} + self.data.copy = {} notify.info "Clipboard has been emptied." renderer.draw() end +---Copy one node ---@param node Node -function M.copy(node) - utils.array_remove(clipboard.cut, node) - toggle(node, clipboard.copy) +function Clipboard:copy(node) + utils.array_remove(self.data.cut, node) + toggle(node, self.data.copy) renderer.draw() end +---Cut one node ---@param node Node -function M.cut(node) - utils.array_remove(clipboard.copy, node) - toggle(node, clipboard.cut) +function Clipboard:cut(node) + utils.array_remove(self.data.copy, node) + toggle(node, self.data.cut) renderer.draw() end +---Paste cut or cop +---@private ---@param node Node ---@param action_type string ---@param action_fn fun(source: string, dest: string) -local function do_paste(node, action_type, action_fn) +function Clipboard:do_paste(node, action_type, action_fn) node = lib.get_last_group_node(node) local explorer = core.get_explorer() if node.name == ".." and explorer then node = explorer end - local clip = clipboard[action_type] + local clip = self.data[action_type] if #clip == 0 then return end @@ -217,8 +251,8 @@ local function do_paste(node, action_type, action_fn) do_single_paste(_node.absolute_path, dest, action_type, action_fn) end - clipboard[action_type] = {} - if not M.config.filesystem_watchers.enable then + self.data[action_type] = {} + if not self.config.filesystem_watchers.enable then reloaders.reload_explorer() end end @@ -246,26 +280,27 @@ local function do_cut(source, destination) return true end +---Paste cut (if present) or copy (if present) ---@param node Node -function M.paste(node) - if clipboard.cut[1] ~= nil then - do_paste(node, "cut", do_cut) - else - do_paste(node, "copy", do_copy) +function Clipboard:paste(node) + if self.data.cut[1] ~= nil then + self:do_paste(node, "cut", do_cut) + elseif self.data.copy[1] ~= nil then + self:do_paste(node, "cop", do_copy) end end -function M.print_clipboard() +function Clipboard:print_clipboard() local content = {} - if #clipboard.cut > 0 then + if #self.data.cut > 0 then table.insert(content, "Cut") - for _, node in pairs(clipboard.cut) do + for _, node in pairs(self.data.cut) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end - if #clipboard.copy > 0 then + if #self.data.copy > 0 then table.insert(content, "Copy") - for _, node in pairs(clipboard.copy) do + for _, node in pairs(self.data.copy) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end @@ -274,10 +309,10 @@ function M.print_clipboard() end ---@param content string -local function copy_to_clipboard(content) +function Clipboard:copy_to_clipboard(content) local clipboard_name local reg - if M.config.actions.use_system_clipboard == true then + if self.config.actions.use_system_clipboard == true then clipboard_name = "system" reg = "+" else @@ -298,18 +333,18 @@ local function copy_to_clipboard(content) end ---@param node Node -function M.copy_filename(node) - copy_to_clipboard(node.name) +function Clipboard:copy_filename(node) + self:copy_to_clipboard(node.name) end ---@param node Node -function M.copy_basename(node) +function Clipboard:copy_basename(node) local basename = vim.fn.fnamemodify(node.name, ":r") - copy_to_clipboard(basename) + self:copy_to_clipboard(basename) end ---@param node Node -function M.copy_path(node) +function Clipboard:copy_path(node) local absolute_path = node.absolute_path local cwd = core.get_cwd() if cwd == nil then @@ -318,33 +353,28 @@ function M.copy_path(node) local relative_path = utils.path_relative(absolute_path, cwd) local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path - copy_to_clipboard(content) + self:copy_to_clipboard(content) end ---@param node Node -function M.copy_absolute_path(node) +function Clipboard:copy_absolute_path(node) local absolute_path = node.absolute_path local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path - copy_to_clipboard(content) + self:copy_to_clipboard(content) end ---Node is cut. Will not be copied. ---@param node Node ---@return boolean -function M.is_cut(node) - return vim.tbl_contains(clipboard.cut, node) +function Clipboard:is_cut(node) + return vim.tbl_contains(self.data.cut, node) end ---Node is copied. Will not be cut. ---@param node Node ---@return boolean -function M.is_copied(node) - return vim.tbl_contains(clipboard.copy, node) -end - -function M.setup(opts) - M.config.filesystem_watchers = opts.filesystem_watchers - M.config.actions = opts.actions +function Clipboard:is_copied(node) + return vim.tbl_contains(self.data.copy, node) end -return M +return Clipboard diff --git a/lua/nvim-tree/actions/fs/init.lua b/lua/nvim-tree/actions/fs/init.lua index 0fefa09e1da..5cdf78cd4ea 100644 --- a/lua/nvim-tree/actions/fs/init.lua +++ b/lua/nvim-tree/actions/fs/init.lua @@ -1,13 +1,11 @@ local M = {} -M.copy_paste = require "nvim-tree.actions.fs.copy-paste" M.create_file = require "nvim-tree.actions.fs.create-file" M.remove_file = require "nvim-tree.actions.fs.remove-file" M.rename_file = require "nvim-tree.actions.fs.rename-file" M.trash = require "nvim-tree.actions.fs.trash" function M.setup(opts) - M.copy_paste.setup(opts) M.remove_file.setup(opts) M.rename_file.setup(opts) M.trash.setup(opts) diff --git a/lua/nvim-tree/api.lua b/lua/nvim-tree/api.lua index eea7b0acbdc..815c1dd7794 100644 --- a/lua/nvim-tree/api.lua +++ b/lua/nvim-tree/api.lua @@ -174,15 +174,15 @@ Api.fs.rename = wrap_node(actions.fs.rename_file.fn ":t") Api.fs.rename_sub = wrap_node(actions.fs.rename_file.fn ":p:h") Api.fs.rename_basename = wrap_node(actions.fs.rename_file.fn ":t:r") Api.fs.rename_full = wrap_node(actions.fs.rename_file.fn ":p") -Api.fs.cut = wrap_node(actions.fs.copy_paste.cut) -Api.fs.paste = wrap_node(actions.fs.copy_paste.paste) -Api.fs.clear_clipboard = wrap(actions.fs.copy_paste.clear_clipboard) -Api.fs.print_clipboard = wrap(actions.fs.copy_paste.print_clipboard) -Api.fs.copy.node = wrap_node(actions.fs.copy_paste.copy) -Api.fs.copy.absolute_path = wrap_node(actions.fs.copy_paste.copy_absolute_path) -Api.fs.copy.filename = wrap_node(actions.fs.copy_paste.copy_filename) -Api.fs.copy.basename = wrap_node(actions.fs.copy_paste.copy_basename) -Api.fs.copy.relative_path = wrap_node(actions.fs.copy_paste.copy_path) +Api.fs.cut = wrap_node(wrap_explorer_member("clipboard", "cut")) +Api.fs.paste = wrap_node(wrap_explorer_member("clipboard", "paste")) +Api.fs.clear_clipboard = wrap_explorer_member("clipboard", "clear_clipboard") +Api.fs.print_clipboard = wrap_explorer_member("clipboard", "print_clipboard") +Api.fs.copy.node = wrap_node(wrap_explorer_member("clipboard", "copy")) +Api.fs.copy.absolute_path = wrap_node(wrap_explorer_member("clipboard", "copy_absolute_path")) +Api.fs.copy.filename = wrap_node(wrap_explorer_member("clipboard", "copy_filename")) +Api.fs.copy.basename = wrap_node(wrap_explorer_member("clipboard", "copy_basename")) +Api.fs.copy.relative_path = wrap_node(wrap_explorer_member("clipboard", "copy_path")) ---@param mode string ---@param node table diff --git a/lua/nvim-tree/explorer/init.lua b/lua/nvim-tree/explorer/init.lua index 95cbb1de2be..89b02e96e68 100644 --- a/lua/nvim-tree/explorer/init.lua +++ b/lua/nvim-tree/explorer/init.lua @@ -6,6 +6,7 @@ local Filters = require "nvim-tree.explorer.filters" local Marks = {} -- circular dependencies local LiveFilter = require "nvim-tree.explorer.live-filter" local Sorters = require "nvim-tree.explorer.sorters" +local Clipboard = {} -- circular dependencies local M = {} @@ -20,6 +21,7 @@ M.reload = require("nvim-tree.explorer.reload").reload ---@field live_filter LiveFilter ---@field sorters Sorter ---@field marks Marks +---@field clipboard Clipboard local Explorer = {} Explorer.__index = Explorer @@ -50,6 +52,7 @@ function Explorer.new(path) explorer.filters = Filters:new(M.config, explorer) explorer.live_filter = LiveFilter:new(M.config, explorer) explorer.marks = Marks:new(M.config, explorer) + explorer.clipboard = Clipboard:new(M.config, explorer) explorer:_load(explorer) return explorer end @@ -87,6 +90,7 @@ function M.setup(opts) require("nvim-tree.explorer.watch").setup(opts) Marks = require "nvim-tree.marks" + Clipboard = require "nvim-tree.actions.fs.clipboard" end M.Explorer = Explorer diff --git a/lua/nvim-tree/renderer/decorator/copied.lua b/lua/nvim-tree/renderer/decorator/copied.lua index f7ceba0a293..ba187e7d5be 100644 --- a/lua/nvim-tree/renderer/decorator/copied.lua +++ b/lua/nvim-tree/renderer/decorator/copied.lua @@ -1,4 +1,4 @@ -local copy_paste +local core = require "nvim-tree.core" local HL_POSITION = require("nvim-tree.enum").HL_POSITION local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT @@ -20,9 +20,6 @@ function DecoratorCopied:new(opts) }) ---@cast o DecoratorCopied - -- cyclic - copy_paste = copy_paste or require "nvim-tree.actions.fs.copy-paste" - return o end @@ -30,7 +27,7 @@ end ---@param node Node ---@return string|nil group function DecoratorCopied:calculate_highlight(node) - if self.hl_pos ~= HL_POSITION.none and copy_paste.is_copied(node) then + if self.hl_pos ~= HL_POSITION.none and core.get_explorer() and core.get_explorer().clipboard:is_copied(node) then return "NvimTreeCopiedHL" end end diff --git a/lua/nvim-tree/renderer/decorator/cut.lua b/lua/nvim-tree/renderer/decorator/cut.lua index f02872ff6f5..615168f8907 100644 --- a/lua/nvim-tree/renderer/decorator/cut.lua +++ b/lua/nvim-tree/renderer/decorator/cut.lua @@ -1,4 +1,4 @@ -local copy_paste +local core = require "nvim-tree.core" local HL_POSITION = require("nvim-tree.enum").HL_POSITION local ICON_PLACEMENT = require("nvim-tree.enum").ICON_PLACEMENT @@ -20,9 +20,6 @@ function DecoratorCut:new(opts) }) ---@cast o DecoratorCut - -- cyclic - copy_paste = copy_paste or require "nvim-tree.actions.fs.copy-paste" - return o end @@ -30,7 +27,7 @@ end ---@param node Node ---@return string|nil group function DecoratorCut:calculate_highlight(node) - if self.hl_pos ~= HL_POSITION.none and copy_paste.is_cut(node) then + if self.hl_pos ~= HL_POSITION.none and core.get_explorer() and core.get_explorer().clipboard:is_cut(node) then return "NvimTreeCutHL" end end From 632ad2c100e17c92d73de1bb3b1e9e2b2663b753 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sat, 17 Aug 2024 16:25:32 +1000 Subject: [PATCH 2/3] refactor(#2831): multi instance clipboard --- lua/nvim-tree/actions/fs/copy-paste.lua | 69 ++++++++++++------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/lua/nvim-tree/actions/fs/copy-paste.lua b/lua/nvim-tree/actions/fs/copy-paste.lua index 5a71db3ea8c..858dd34e45b 100644 --- a/lua/nvim-tree/actions/fs/copy-paste.lua +++ b/lua/nvim-tree/actions/fs/copy-paste.lua @@ -11,19 +11,14 @@ local find_file = require("nvim-tree.actions.finders.find-file").fn ---@enum ACTION local ACTION = { - none = 0, - copy = 1, - cut = 2, + copy = "copy", + cut = "cut", } ----@class ClipboardData absolute paths ----@field copy string[] copied ----@field cut string[] cut - ---@class Clipboard to handle all actions.fs clipboard API ---@field config table hydrated user opts.filters ---@field private explorer Explorer ----@field private data ClipboardData +---@field private data table local Clipboard = {} ---@param opts table user options @@ -33,8 +28,8 @@ function Clipboard:new(opts, explorer) local o = { explorer = explorer, data = { - copy = {}, - cut = {}, + [ACTION.copy] = {}, + [ACTION.cut] = {}, }, config = { filesystem_watchers = opts.filesystem_watchers, @@ -196,8 +191,8 @@ end ---Clear copied and cut function Clipboard:clear_clipboard() - self.data.cut = {} - self.data.copy = {} + self.data[ACTION.copy] = {} + self.data[ACTION.cut] = {} notify.info "Clipboard has been emptied." renderer.draw() end @@ -205,31 +200,31 @@ end ---Copy one node ---@param node Node function Clipboard:copy(node) - utils.array_remove(self.data.cut, node) - toggle(node, self.data.copy) + utils.array_remove(self.data[ACTION.cut], node) + toggle(node, self.data[ACTION.copy]) renderer.draw() end ---Cut one node ---@param node Node function Clipboard:cut(node) - utils.array_remove(self.data.copy, node) - toggle(node, self.data.cut) + utils.array_remove(self.data[ACTION.copy], node) + toggle(node, self.data[ACTION.cut]) renderer.draw() end ---Paste cut or cop ---@private ---@param node Node ----@param action_type string +---@param action ACTION ---@param action_fn fun(source: string, dest: string) -function Clipboard:do_paste(node, action_type, action_fn) +function Clipboard:do_paste(node, action, action_fn) node = lib.get_last_group_node(node) local explorer = core.get_explorer() if node.name == ".." and explorer then node = explorer end - local clip = self.data[action_type] + local clip = self.data[action] if #clip == 0 then return end @@ -238,7 +233,7 @@ function Clipboard:do_paste(node, action_type, action_fn) local stats, errmsg, errcode = vim.loop.fs_stat(destination) if not stats and errcode ~= "ENOENT" then log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, errmsg) - notify.error("Could not " .. action_type .. " " .. notify.render_path(destination) .. " - " .. (errmsg or "???")) + notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (errmsg or "???")) return end local is_dir = stats and stats.type == "directory" @@ -248,10 +243,10 @@ function Clipboard:do_paste(node, action_type, action_fn) for _, _node in ipairs(clip) do local dest = utils.path_join { destination, _node.name } - do_single_paste(_node.absolute_path, dest, action_type, action_fn) + do_single_paste(_node.absolute_path, dest, action, action_fn) end - self.data[action_type] = {} + self.data[action] = {} if not self.config.filesystem_watchers.enable then reloaders.reload_explorer() end @@ -283,24 +278,24 @@ end ---Paste cut (if present) or copy (if present) ---@param node Node function Clipboard:paste(node) - if self.data.cut[1] ~= nil then - self:do_paste(node, "cut", do_cut) - elseif self.data.copy[1] ~= nil then - self:do_paste(node, "cop", do_copy) + if self.data[ACTION.cut][1] ~= nil then + self:do_paste(node, ACTION.cut, do_cut) + elseif self.data[ACTION.copy][1] ~= nil then + self:do_paste(node, ACTION.copy, do_copy) end end function Clipboard:print_clipboard() local content = {} - if #self.data.cut > 0 then + if #self.data[ACTION.cut] > 0 then table.insert(content, "Cut") - for _, node in pairs(self.data.cut) do + for _, node in pairs(self.data[ACTION.cut]) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end - if #self.data.copy > 0 then + if #self.data[ACTION.copy] > 0 then table.insert(content, "Copy") - for _, node in pairs(self.data.copy) do + for _, node in pairs(self.data[ACTION.copy]) do table.insert(content, " * " .. (notify.render_path(node.absolute_path))) end end @@ -309,7 +304,7 @@ function Clipboard:print_clipboard() end ---@param content string -function Clipboard:copy_to_clipboard(content) +function Clipboard:copy_to_reg(content) local clipboard_name local reg if self.config.actions.use_system_clipboard == true then @@ -334,13 +329,13 @@ end ---@param node Node function Clipboard:copy_filename(node) - self:copy_to_clipboard(node.name) + self:copy_to_reg(node.name) end ---@param node Node function Clipboard:copy_basename(node) local basename = vim.fn.fnamemodify(node.name, ":r") - self:copy_to_clipboard(basename) + self:copy_to_reg(basename) end ---@param node Node @@ -353,28 +348,28 @@ function Clipboard:copy_path(node) local relative_path = utils.path_relative(absolute_path, cwd) local content = node.nodes ~= nil and utils.path_add_trailing(relative_path) or relative_path - self:copy_to_clipboard(content) + self:copy_to_reg(content) end ---@param node Node function Clipboard:copy_absolute_path(node) local absolute_path = node.absolute_path local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path - self:copy_to_clipboard(content) + self:copy_to_reg(content) end ---Node is cut. Will not be copied. ---@param node Node ---@return boolean function Clipboard:is_cut(node) - return vim.tbl_contains(self.data.cut, node) + return vim.tbl_contains(self.data[ACTION.cut], node) end ---Node is copied. Will not be cut. ---@param node Node ---@return boolean function Clipboard:is_copied(node) - return vim.tbl_contains(self.data.copy, node) + return vim.tbl_contains(self.data[ACTION.copy], node) end return Clipboard From d50207446cf1b8e9f17be6ee9a5d7fd4c09b276c Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Sat, 17 Aug 2024 16:35:09 +1000 Subject: [PATCH 3/3] refactor(#2831): multi instance clipboard --- .../actions/fs/{copy-paste.lua => clipboard.lua} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename lua/nvim-tree/actions/fs/{copy-paste.lua => clipboard.lua} (95%) diff --git a/lua/nvim-tree/actions/fs/copy-paste.lua b/lua/nvim-tree/actions/fs/clipboard.lua similarity index 95% rename from lua/nvim-tree/actions/fs/copy-paste.lua rename to lua/nvim-tree/actions/fs/clipboard.lua index 858dd34e45b..e3714d6437b 100644 --- a/lua/nvim-tree/actions/fs/copy-paste.lua +++ b/lua/nvim-tree/actions/fs/clipboard.lua @@ -109,11 +109,11 @@ end ---@param source string ---@param dest string ----@param action_type string +---@param action ACTION ---@param action_fn fun(source: string, dest: string) ---@return boolean|nil -- success ---@return string|nil -- error message -local function do_single_paste(source, dest, action_type, action_fn) +local function do_single_paste(source, dest, action, action_fn) local dest_stats local success, errmsg, errcode local notify_source = notify.render_path(source) @@ -122,14 +122,14 @@ local function do_single_paste(source, dest, action_type, action_fn) dest_stats, errmsg, errcode = vim.loop.fs_stat(dest) if not dest_stats and errcode ~= "ENOENT" then - notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???")) + notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (errmsg or "???")) return false, errmsg end local function on_process() success, errmsg = action_fn(source, dest) if not success then - notify.error("Could not " .. action_type .. " " .. notify_source .. " - " .. (errmsg or "???")) + notify.error("Could not " .. action .. " " .. notify_source .. " - " .. (errmsg or "???")) return false, errmsg end @@ -147,7 +147,7 @@ local function do_single_paste(source, dest, action_type, action_fn) vim.ui.input(input_opts, function(new_dest) utils.clear_prompt() if new_dest then - do_single_paste(source, new_dest, action_type, action_fn) + do_single_paste(source, new_dest, action, action_fn) end end) else @@ -161,7 +161,7 @@ local function do_single_paste(source, dest, action_type, action_fn) vim.ui.input(input_opts, function(new_dest) utils.clear_prompt() if new_dest then - do_single_paste(source, new_dest, action_type, action_fn) + do_single_paste(source, new_dest, action, action_fn) end end) end