Skip to content

refactor(#2831): multi instance clipboard #2869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,38 @@ 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 = {
copy = "copy",
cut = "cut",
}

local clipboard = {
cut = {},
copy = {},
}
---@class Clipboard to handle all actions.fs clipboard API
---@field config table hydrated user opts.filters
---@field private explorer Explorer
---@field private data table<ACTION, Node[]>
local Clipboard = {}

---@param opts table user options
---@param explorer Explorer
---@return Clipboard
function Clipboard:new(opts, explorer)
local o = {
explorer = explorer,
data = {
[ACTION.copy] = {},
[ACTION.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
Expand Down Expand Up @@ -85,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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resisted the urge to make these members or create cut/copy subclasses

local dest_stats
local success, errmsg, errcode
local notify_source = notify.render_path(source)
Expand All @@ -98,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

Expand All @@ -123,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
Expand All @@ -137,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
Expand Down Expand Up @@ -165,37 +189,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[ACTION.copy] = {}
self.data[ACTION.cut] = {}
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[ACTION.cut], node)
toggle(node, self.data[ACTION.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[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)
local function 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 = clipboard[action_type]
local clip = self.data[action]
if #clip == 0 then
return
end
Expand All @@ -204,7 +233,7 @@ local function 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"
Expand All @@ -214,11 +243,11 @@ local function 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

clipboard[action_type] = {}
if not M.config.filesystem_watchers.enable then
self.data[action] = {}
if not self.config.filesystem_watchers.enable then
reloaders.reload_explorer()
end
end
Expand Down Expand Up @@ -246,26 +275,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[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 M.print_clipboard()
function Clipboard:print_clipboard()
local content = {}
if #clipboard.cut > 0 then
if #self.data[ACTION.cut] > 0 then
table.insert(content, "Cut")
for _, node in pairs(clipboard.cut) do
for _, node in pairs(self.data[ACTION.cut]) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
if #clipboard.copy > 0 then
if #self.data[ACTION.copy] > 0 then
table.insert(content, "Copy")
for _, node in pairs(clipboard.copy) do
for _, node in pairs(self.data[ACTION.copy]) do
table.insert(content, " * " .. (notify.render_path(node.absolute_path)))
end
end
Expand All @@ -274,10 +304,10 @@ function M.print_clipboard()
end

---@param content string
local function copy_to_clipboard(content)
function Clipboard:copy_to_reg(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
Expand All @@ -298,18 +328,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_reg(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_reg(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
Expand All @@ -318,33 +348,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_reg(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_reg(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[ACTION.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[ACTION.copy], node)
end

return M
return Clipboard
2 changes: 0 additions & 2 deletions lua/nvim-tree/actions/fs/init.lua
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
18 changes: 9 additions & 9 deletions lua/nvim-tree/api.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lua/nvim-tree/explorer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions lua/nvim-tree/renderer/decorator/copied.lua
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -20,17 +20,14 @@ function DecoratorCopied:new(opts)
})
---@cast o DecoratorCopied

-- cyclic
copy_paste = copy_paste or require "nvim-tree.actions.fs.copy-paste"

return o
end

---Copied highlight: renderer.highlight_clipboard and node is copied
---@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
Expand Down
Loading
Loading