mirror of
https://github.com/nvim-tree/nvim-tree.lua.git
synced 2026-07-28 07:49:46 +05:30
* feat: add copy absolute path with protocol into public api * feat: add helper get_basename into node * feat: add gby keymap, and allow visual mode for copy.basename|absolute_path * feat(clipboard): add opts for adding protocol in reg operations * feat(api): allow visual operation in absolute_path* * feat: add paste with protocol to keymap+api * feat: add paste with protocol to clipboard * feat: add configurable clipboard protocol * fix: filter descendant nodes on copy_absolute_path_with_protocol * refactor: change clip protocol keymaps to bgy and bgp * wip * fix(clipboad): correctly show the number of copied nodes on bgy * chore: remove bgy, bgp and add gp + gx * chore: update api/fs.lua and api impl.lua to match new keymaps * fix: correctly pass the function to new keymaps * chore: remove use_protocol and use use_register + cut options * chore: update docs * chore: move node:get_basename to cliboard_ * chore(clipboard): remove vim.cmd workaround in favor of vim.fn.setreg * chore: use newline as separator instead of comma * refactor(clipboard): simplify copying node attribute and allow all to work in visual mode * refactor(keymap): remove gx, use gp to move * feat(clipboard): always copy/cut to register and make `p` pick local data or register data * refactor(docs): update api docs and rename paste_while_cutting to move * fix: correctly format keymaps * chore: regenerate docs * fix: remove unused apis * chore: copy clipboard.data to register on copy/cut * chore: revert copy_node_attribute * refactor: remove visual mode from copy.basename|filename|path * chore: update docs * chore: add fs.move api and update docs * chore: destroy dummy nodes and prevent empty paths nodes parsing * fix: _meta/api/fs.lua and docs * fix(clipboard): destroy dummy nodes from register on paste * fix(clipboard): correctly check for local paste operation
This commit is contained in:
@@ -20,7 +20,8 @@ Features
|
||||
- Git integration
|
||||
- Diagnostics integration: LSP and COC
|
||||
- (Live) filtering
|
||||
- Cut, copy, paste, rename, delete, create
|
||||
- Rename, delete, create
|
||||
- Cut, copy, paste locally and between instances
|
||||
- Highly customisable
|
||||
|
||||
File Icons
|
||||
@@ -142,7 +143,7 @@ Show the mappings: `g?`
|
||||
`F` n Live Filter: Clear |nvim_tree.api.filter.live.clear()|
|
||||
`f` n Live Filter: Start |nvim_tree.api.filter.live.start()|
|
||||
`g?` n Help |nvim_tree.api.tree.toggle_help()|
|
||||
`gy` n Copy Absolute Path |nvim_tree.api.fs.copy.absolute_path()|
|
||||
`gy` nx Copy Absolute Path |nvim_tree.api.fs.copy.absolute_path()|
|
||||
`ge` n Copy Basename |nvim_tree.api.fs.copy.basename()|
|
||||
`H` n Toggle Filter: Dotfiles |nvim_tree.api.filter.dotfiles.toggle()|
|
||||
`I` n Toggle Filter: Git Ignored |nvim_tree.api.filter.git.ignored.toggle()|
|
||||
@@ -154,6 +155,7 @@ Show the mappings: `g?`
|
||||
`o` n Open |nvim_tree.api.node.open.edit()|
|
||||
`O` n Open: No Window Picker |nvim_tree.api.node.open.no_window_picker()|
|
||||
`p` n Paste |nvim_tree.api.fs.paste()|
|
||||
`gp` n Move |nvim_tree.api.fs.move()|
|
||||
`P` n Parent Directory |nvim_tree.api.node.navigate.parent()|
|
||||
`q` n Close |nvim_tree.api.tree.close()|
|
||||
`r` n Rename |nvim_tree.api.fs.rename()|
|
||||
@@ -435,7 +437,7 @@ You are encouraged to copy these to your {on_attach} function. >lua
|
||||
vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear"))
|
||||
vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start"))
|
||||
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
|
||||
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
|
||||
vim.keymap.set({ "n", "x" }, "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
|
||||
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
|
||||
vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles"))
|
||||
vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored"))
|
||||
@@ -447,6 +449,7 @@ You are encouraged to copy these to your {on_attach} function. >lua
|
||||
vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
|
||||
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
|
||||
vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
|
||||
vim.keymap.set("n", "gp", api.fs.move, opts("Move"))
|
||||
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
|
||||
vim.keymap.set("n", "q", api.tree.close, opts("Close"))
|
||||
vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))
|
||||
@@ -2459,8 +2462,20 @@ cut({node}) *nvim_tree.api.fs.cut()*
|
||||
Parameters: ~
|
||||
• {node} (`nvim_tree.api.Node?`)
|
||||
|
||||
move({node}) *nvim_tree.api.fs.move()*
|
||||
Paste while cutting files from:
|
||||
• current nvim-tree clipboard (if present)
|
||||
• another nvim-tree instance via system clipboard
|
||||
|
||||
If {node} is a file it will pasted in the parent directory.
|
||||
|
||||
Parameters: ~
|
||||
• {node} (`nvim_tree.api.Node?`)
|
||||
|
||||
paste({node}) *nvim_tree.api.fs.paste()*
|
||||
Paste from the nvim-tree clipboard.
|
||||
Paste files from:
|
||||
• current nvim-tree clipboard (if present)
|
||||
• another nvim-tree instance via system clipboard
|
||||
|
||||
If {node} is a file it will pasted in the parent directory.
|
||||
|
||||
|
||||
@@ -59,13 +59,25 @@ function nvim_tree.api.fs.create(node) end
|
||||
function nvim_tree.api.fs.cut(node) end
|
||||
|
||||
---
|
||||
---Paste from the nvim-tree clipboard.
|
||||
---Paste files from:
|
||||
---- current nvim-tree clipboard (if present)
|
||||
---- another nvim-tree instance via system clipboard
|
||||
---
|
||||
---If {node} is a file it will pasted in the parent directory.
|
||||
---
|
||||
---@param node? nvim_tree.api.Node
|
||||
function nvim_tree.api.fs.paste(node) end
|
||||
|
||||
---
|
||||
---Paste while cutting files from:
|
||||
---- current nvim-tree clipboard (if present)
|
||||
---- another nvim-tree instance via system clipboard
|
||||
---
|
||||
---If {node} is a file it will pasted in the parent directory.
|
||||
---
|
||||
---@param node? nvim_tree.api.Node
|
||||
function nvim_tree.api.fs.move(node) end
|
||||
|
||||
---
|
||||
---Print the contents of the nvim-tree clipboard.
|
||||
---
|
||||
|
||||
@@ -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"
|
||||
@@ -176,28 +177,40 @@ 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()
|
||||
else
|
||||
self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.cut, self.data.copy, "added to")
|
||||
end
|
||||
self:copy_absolute_path(self.data.copy, { notify = false })
|
||||
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()
|
||||
else
|
||||
self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.copy, self.data.cut, "cut to")
|
||||
end
|
||||
self:copy_absolute_path(self.data.cut, { notify = false })
|
||||
end
|
||||
|
||||
---Clear clipboard for action and reload to reflect filesystem changes from paste.
|
||||
@@ -304,6 +317,40 @@ function Clipboard:resolve_conflicts(conflict, destination, action, action_fn)
|
||||
end)
|
||||
end
|
||||
|
||||
--- Transforms the copied absolute paths on register to node
|
||||
---@private
|
||||
function Clipboard:get_nodes_from_reg()
|
||||
local content = vim.fn.getreg(self.reg)
|
||||
|
||||
if #content == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
local nodes = {}
|
||||
local absolute_paths = vim.split(content:sub(1, #content), "\n")
|
||||
|
||||
for _, absolute_path in ipairs(absolute_paths) do
|
||||
if absolute_path:match("^%s*(.-)%s*s") then
|
||||
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
|
||||
end
|
||||
return nodes
|
||||
end
|
||||
|
||||
---@param nodes Node[]
|
||||
---@private
|
||||
function Clipboard:destroy_nodes(nodes)
|
||||
for _, n in ipairs(nodes) do
|
||||
n:destroy()
|
||||
end
|
||||
end
|
||||
|
||||
---Paste cut or copy with batch conflict resolution.
|
||||
---@private
|
||||
---@param node Node
|
||||
@@ -318,7 +365,9 @@ function Clipboard:do_paste(node, action, action_fn)
|
||||
node = dir:last_group_node()
|
||||
end
|
||||
end
|
||||
local clip = self.data[action]
|
||||
local is_local = #self.data[action] > 0
|
||||
local clip = is_local and self.data[action] or self:get_nodes_from_reg()
|
||||
|
||||
if #clip == 0 then
|
||||
return
|
||||
end
|
||||
@@ -328,6 +377,9 @@ function Clipboard:do_paste(node, action, action_fn)
|
||||
if not stats and err_name ~= "ENOENT" then
|
||||
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err)
|
||||
notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???"))
|
||||
if is_local == false then
|
||||
self:destroy_nodes(clip)
|
||||
end
|
||||
return
|
||||
end
|
||||
local is_dir = stats and stats.type == "directory"
|
||||
@@ -359,6 +411,10 @@ function Clipboard:do_paste(node, action, action_fn)
|
||||
else
|
||||
self:finish_paste(action)
|
||||
end
|
||||
|
||||
if is_local == false then
|
||||
self:destroy_nodes(clip)
|
||||
end
|
||||
end
|
||||
|
||||
---@param source string
|
||||
@@ -386,10 +442,12 @@ end
|
||||
|
||||
---Paste cut (if present) or copy (if present)
|
||||
---@param node Node
|
||||
function Clipboard:paste(node)
|
||||
if self.data.cut[1] ~= nil then
|
||||
---@param opts? { cut?: boolean }
|
||||
function Clipboard:paste(node, opts)
|
||||
opts = opts and opts or {}
|
||||
if self.data.cut[1] ~= nil or opts.cut == true then
|
||||
self:do_paste(node, "cut", do_cut)
|
||||
elseif self.data.copy[1] ~= nil then
|
||||
else
|
||||
self:do_paste(node, "copy", do_copy)
|
||||
end
|
||||
end
|
||||
@@ -413,17 +471,15 @@ function Clipboard:print_clipboard()
|
||||
end
|
||||
|
||||
---@param content string
|
||||
function Clipboard:copy_to_reg(content)
|
||||
-- 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)
|
||||
vim.api.nvim_buf_set_text(temp_buf, 0, 0, 0, 0, { content })
|
||||
vim.api.nvim_buf_call(temp_buf, function()
|
||||
vim.cmd(string.format('normal! "%sy$', self.reg))
|
||||
end)
|
||||
vim.api.nvim_buf_delete(temp_buf, {})
|
||||
---@param message? string
|
||||
---@param opts? { notify?: boolean }
|
||||
function Clipboard:copy_to_reg(content, message, opts)
|
||||
opts = opts and opts or {}
|
||||
vim.fn.setreg(self.reg, type(content) == "table" and content or { content }, "v")
|
||||
|
||||
notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
|
||||
if opts.notify ~= false then
|
||||
notify.info(message or string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
|
||||
end
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
@@ -437,18 +493,20 @@ function Clipboard:copy_filename(node)
|
||||
end
|
||||
end
|
||||
|
||||
---@private
|
||||
---@param node Node
|
||||
function Clipboard:copy_basename(node)
|
||||
---@return string
|
||||
function Clipboard:get_absolute_path(node)
|
||||
if node.name == ".." then
|
||||
-- root
|
||||
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r"))
|
||||
else
|
||||
-- node
|
||||
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
|
||||
node = self.explorer
|
||||
end
|
||||
|
||||
local absolute_path = node.absolute_path
|
||||
return node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
---@return string|nil
|
||||
function Clipboard:copy_path(node)
|
||||
if node.name == ".." then
|
||||
-- root
|
||||
@@ -470,15 +528,44 @@ function Clipboard:copy_path(node)
|
||||
end
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
function Clipboard:copy_absolute_path(node)
|
||||
if node.name == ".." then
|
||||
node = self.explorer
|
||||
---@param node_or_nodes Node|Node[]
|
||||
---@param opts? { notify?: boolean }
|
||||
function Clipboard:copy_absolute_path(node_or_nodes, opts)
|
||||
opts = opts and opts or {}
|
||||
local content
|
||||
|
||||
local is_single = self:is_nodes_array(node_or_nodes) == false
|
||||
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)
|
||||
content = {}
|
||||
for _, node in ipairs(node_or_nodes) do
|
||||
table.insert(content, self:get_absolute_path(node))
|
||||
end
|
||||
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)
|
||||
|
||||
if content ~= nil then
|
||||
local message = nil
|
||||
|
||||
if not is_single then
|
||||
message = string.format("%s %s copied to register", #content, #content > 1 and "absolute paths" or "absolute path")
|
||||
end
|
||||
self:copy_to_reg(content, message, opts)
|
||||
end
|
||||
end
|
||||
|
||||
---@param node Node
|
||||
function Clipboard:copy_basename(node)
|
||||
if node.name == ".." then
|
||||
-- root
|
||||
self:copy_to_reg(vim.fn.fnamemodify(node.explorer.absolute_path, ":t:r"))
|
||||
else
|
||||
-- node
|
||||
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
|
||||
end
|
||||
end
|
||||
|
||||
---Node is cut. Will not be copied.
|
||||
|
||||
@@ -155,14 +155,15 @@ 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.absolute_path = ev(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.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.copy.node = ev(function(e, n) e.clipboard:copy(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.move = en(function(e, n) e.clipboard:paste(n, { cut = 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)
|
||||
|
||||
@@ -90,7 +90,7 @@ function M.on_attach_default(bufnr)
|
||||
vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear"))
|
||||
vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start"))
|
||||
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
|
||||
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
|
||||
vim.keymap.set({ "n", "x" }, "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
|
||||
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
|
||||
vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles"))
|
||||
vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored"))
|
||||
@@ -102,6 +102,7 @@ function M.on_attach_default(bufnr)
|
||||
vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
|
||||
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
|
||||
vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
|
||||
vim.keymap.set("n", "gp", api.fs.move, opts("Move"))
|
||||
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
|
||||
vim.keymap.set("n", "q", api.tree.close, opts("Close"))
|
||||
vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))
|
||||
|
||||
Reference in New Issue
Block a user