Skip to content
Open
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
589 changes: 310 additions & 279 deletions doc/nvim-tree-lua.txt

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions lua/nvim-tree/_meta/api/fs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ nvim_tree.api.fs.copy = {}
---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.copy.absolute_path(node) end

---
---Copy the absolute path to the system clipboard with nvim-tree protocol.
---
---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.copy.absolute_path_with_protocol(node) end

---
---Copy the name with extension omitted to the system clipboard.
---
Expand Down Expand Up @@ -66,6 +72,14 @@ function nvim_tree.api.fs.cut(node) end
---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.paste(node) end

---
---Paste nodes from the nvim-tree clipboard using protocol.
---
---If {node} is a file it will pasted in the parent directory.
---
---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.paste_with_protocol(node) end

---
---Print the contents of the nvim-tree clipboard.
---
Expand Down
10 changes: 9 additions & 1 deletion lua/nvim-tree/_meta/config/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ error("Cannot require a meta file")
---(default: `true`)
---@field use_system_clipboard? boolean
---
---[nvim_tree.config.actions.clipboard]
---@field clipboard? nvim_tree.config.actions.clipboard
---
---[nvim_tree.config.actions.change_dir]
---@field change_dir? nvim_tree.config.actions.change_dir
---
Expand All @@ -24,7 +27,12 @@ error("Cannot require a meta file")
---[nvim_tree.config.actions.remove_file]
---@field remove_file? nvim_tree.config.actions.remove_file


--- Customizes nvim-tree clipboard behaviour
---@class nvim_tree.config.actions.clipboard
---
---Change the protocol prefix to be used on multiple nvim instances operations
---(default: `nvim-tree`)
---@field protocol? string

--- vim [current-directory] behaviour
---@class nvim_tree.config.actions.change_dir
Expand Down
126 changes: 106 additions & 20 deletions lua/nvim-tree/actions/fs/clipboard.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ local find_file = require("nvim-tree.actions.finders.find-file").fn

local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory")
local FileNode = require("nvim-tree.node.file")
local Node = require("nvim-tree.node")

---@alias ClipboardAction "copy" | "cut"
Expand All @@ -21,6 +22,7 @@ local Node = require("nvim-tree.node")
---@field private data ClipboardData
---@field private clipboard_name string
---@field private reg string
---@field private protocol string
local Clipboard = Class:extend()

---@class Clipboard
Expand All @@ -41,8 +43,12 @@ function Clipboard:new(args)

self.clipboard_name = self.explorer.opts.actions.use_system_clipboard and "system" or "neovim"
self.reg = self.explorer.opts.actions.use_system_clipboard and "+" or "1"
self.protocol = self.explorer.opts.actions.clipboard.protocol or "nvim-tree"
end

---@class RegOperationOptions
---@field use_protocol? boolean

---@param source string
---@param destination string
---@return boolean
Expand Down Expand Up @@ -176,10 +182,20 @@ function Clipboard:bulk_clipboard(nodes, from, to, verb)
self.explorer.renderer:draw()
end

---@private
---@param node_or_nodes Node|Node[]
---@return boolean
function Clipboard:is_nodes_array(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then
return false
end
return true
end

---Copy one or more nodes
---@param node_or_nodes Node|Node[]
function Clipboard:copy(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then
if self:is_nodes_array(node_or_nodes) == false then
utils.array_remove(self.data.cut, node_or_nodes)
toggle(node_or_nodes, self.data.copy)
self.explorer.renderer:draw()
Expand All @@ -191,7 +207,7 @@ end
---Cut one or more nodes
---@param node_or_nodes Node|Node[]
function Clipboard:cut(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then
if self:is_nodes_array(node_or_nodes) == false then
utils.array_remove(self.data.copy, node_or_nodes)
toggle(node_or_nodes, self.data.cut)
self.explorer.renderer:draw()
Expand Down Expand Up @@ -304,12 +320,42 @@ function Clipboard:resolve_conflicts(conflict, destination, action, action_fn)
end)
end

--- Transforms the copied absolute paths with protocols to node
---@private
function Clipboard:get_nodes_from_reg()
local content = vim.fn.getreg(self.reg)

if #content == 0 then
return
end

local prefix = self.protocol .. ": "
if content:sub(1, #prefix) ~= prefix then
return
end

local nodes = {}
local absolute_paths = vim.split(content:sub(#prefix + 1, #content), ", ")

for _, absolute_path in ipairs(absolute_paths) do
local node_args = { absolute_path = absolute_path, name = vim.fn.fnamemodify(absolute_path, ":t"), explorer = self.explorer }
if absolute_path:sub(-1) == "/" then
node_args.name = vim.fn.fnamemodify(absolute_path:sub(1, -2), ":t")
table.insert(nodes, DirectoryNode(node_args))
else
table.insert(nodes, FileNode(node_args))
end
end
return nodes
end

---Paste cut or copy with batch conflict resolution.
---@private
---@param node Node
---@param action ClipboardAction
---@param action_fn ClipboardActionFn
function Clipboard:do_paste(node, action, action_fn)
---@param opts? RegOperationOptions
function Clipboard:do_paste(node, action, action_fn, opts)
if node.name == ".." then
node = self.explorer
else
Expand All @@ -318,7 +364,7 @@ function Clipboard:do_paste(node, action, action_fn)
node = dir:last_group_node()
end
end
local clip = self.data[action]
local clip = opts and opts.use_protocol and self:get_nodes_from_reg() or self.data[action]
if #clip == 0 then
return
end
Expand Down Expand Up @@ -386,11 +432,12 @@ end

---Paste cut (if present) or copy (if present)
---@param node Node
function Clipboard:paste(node)
---@param opts? RegOperationOptions
function Clipboard:paste(node, opts)
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, "copy", do_copy)
self:do_paste(node, "cut", do_cut, opts)
elseif self.data.copy[1] ~= nil or opts and opts.use_protocol then
self:do_paste(node, "copy", do_copy, opts)
end
end

Expand All @@ -413,7 +460,15 @@ function Clipboard:print_clipboard()
end

---@param content string
function Clipboard:copy_to_reg(content)
---@param opts? RegOperationOptions
---@param msg? string
function Clipboard:copy_to_reg(content, opts, msg)
local use_protocol = opts and opts.use_protocol and true or false

if use_protocol then
content = self.protocol .. ": " .. content
end

-- manually firing TextYankPost does not set vim.v.event
-- workaround: create a scratch buffer with the clipboard contents and send a yank command
local temp_buf = vim.api.nvim_create_buf(false, true)
Expand All @@ -423,7 +478,7 @@ function Clipboard:copy_to_reg(content)
end)
vim.api.nvim_buf_delete(temp_buf, {})

notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
notify.info(msg or string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
end

---@param node Node
Expand All @@ -437,15 +492,23 @@ function Clipboard:copy_filename(node)
end
end

---@param node Node
function Clipboard:copy_basename(node)
if node.name == ".." then
-- root
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r"))
---@param node_or_nodes Node|Node[]
---@param opts? RegOperationOptions
function Clipboard:copy_basename(node_or_nodes, opts)
local content = ""
if self:is_nodes_array(node_or_nodes) == false or #node_or_nodes == 1 then
local node = #node_or_nodes == 1 and node_or_nodes[0] or node_or_nodes
content = node:get_basename()
else
-- node
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
for i, node in ipairs(node_or_nodes) do
if i == 1 then
content = node:get_basename()
else
content = content .. ", " .. node:get_basename()
end
end
end
self:copy_to_reg(content, opts)
end

---@param node Node
Expand All @@ -470,15 +533,38 @@ function Clipboard:copy_path(node)
end
end

---@private
---@param node Node
function Clipboard:copy_absolute_path(node)
---@return string
function Clipboard:get_absolute_path(node)
if node.name == ".." then
node = self.explorer
end

local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
self:copy_to_reg(content)
return node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
end

---@param node_or_nodes Node|Node[]
---@param opts? RegOperationOptions
function Clipboard:copy_absolute_path(node_or_nodes, opts)
local content = ""
local is_single = self:is_nodes_array(node_or_nodes) == false or #node_or_nodes == 1
if is_single then
local node = #node_or_nodes == 1 and node_or_nodes[0] or node_or_nodes
content = self:get_absolute_path(node)
else
node_or_nodes = utils.filter_descendant_nodes(node_or_nodes)
for i, node in ipairs(node_or_nodes) do
if i == 1 then
content = self:get_absolute_path(node)
else
content = content .. ", " .. self:get_absolute_path(node)
end
end
end

self:copy_to_reg(content, opts, string.format("%s nodes copied to register", is_single and 1 or #node_or_nodes))
end

---Node is cut. Will not be copied.
Expand Down
6 changes: 4 additions & 2 deletions lua/nvim-tree/api/impl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,16 @@ function M.hydrate_post_setup(api)
api.filter.toggle = e_(function(e) e.filters:toggle() end)

api.fs.clear_clipboard = e_(function(e) e.clipboard:clear_clipboard() end)
api.fs.copy.absolute_path = en(function(e, n) e.clipboard:copy_absolute_path(n) end)
api.fs.copy.basename = en(function(e, n) e.clipboard:copy_basename(n) end)
api.fs.copy.absolute_path = ev(function(e, n) e.clipboard:copy_absolute_path(n) end)
api.fs.copy.absolute_path_with_protocol = ev(function(e, n) e.clipboard:copy_absolute_path(n, { use_protocol = true }) end)
api.fs.copy.basename = ev(function(e, n) e.clipboard:copy_basename(n) end)
api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end)
api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end)
api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end)
api.fs.create = _n(function(n) require("nvim-tree.actions.fs.create-file").fn(n) end)
api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end)
api.fs.paste = en(function(e, n) e.clipboard:paste(n) end)
api.fs.paste_with_protocol = en(function(e, n) e.clipboard:paste(n, { use_protocol = true }) end)
api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end)
api.fs.remove = _v(function(n) require("nvim-tree.actions.fs.remove-file").fn(n) end)
api.fs.rename = _n(function(n) require("nvim-tree.actions.fs.rename-file").rename_node(n) end)
Expand Down
3 changes: 3 additions & 0 deletions lua/nvim-tree/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ M.d = { -- config-default-start
},
actions = {
use_system_clipboard = true,
clipboard = {
protocol = "nvim-tree"
},
change_dir = {
enable = true,
global = false,
Expand Down
Loading
Loading