Skip to content

Commit 9b20fcb

Browse files
Add cut,copy and paste functionality.
1 parent 20f39a9 commit 9b20fcb

File tree

6 files changed

+125
-1
lines changed

6 files changed

+125
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ highlight LuaTreeFolderIcon guibg=blue
9292
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
9393
> you can add multiple directories by doing foo/bar/baz/f and it will add foo bar and baz directories and f as a file
9494
- type `r` to rename a file
95+
- type `x` to add/remove file/directory to cut clipboard
96+
- type `c` to add/remove file/directory to copy clipboard
97+
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
9598
- type `d` to delete a file (will prompt for confirmation)
9699
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
97100
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)

doc/nvim-tree-lua.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@ INFORMATIONS *nvim-tree-info*
136136

137137
- type 'a' to add a file
138138
- type 'r' to rename a file
139+
- type 'x' to add/remove file/directory to cut clipboard
140+
- type 'c' to add/remove file/directory to copy clipboard
141+
- type 'p' to paste from clipboard. Cut clipboard has precedence over copy
142+
(will prompt for confirmation)
139143
- type 'd' to delete a file (will prompt for confirmation)
140144

141145
- if the file is a directory, '<CR>' will open the directory
@@ -165,7 +169,10 @@ default keybindings will be applied to undefined keys.
165169
\ preview: '<Tab>',
166170
\ create: 'a',
167171
\ remove: 'd',
168-
\ rename: 'r'
172+
\ rename: 'r',
173+
\ cut: 'x',
174+
\ copy: 'c',
175+
\ paste: 'p',
169176
\ }
170177
171178
|Features| *nvim-tree-features*

lua/lib/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ function M.get_bindings()
5454
create = keybindings.create or 'a',
5555
remove = keybindings.remove or 'd',
5656
rename = keybindings.rename or 'r',
57+
cut = keybindings.cut or 'x',
58+
copy = keybindings.copy or 'c',
59+
paste = keybindings.paste or 'p',
5760
}
5861
end
5962

lua/lib/fs.lua

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ local luv = vim.loop
33
local open_mode = luv.constants.O_CREAT + luv.constants.O_WRONLY + luv.constants.O_TRUNC
44

55
local M = {}
6+
local clipboard = {
7+
move = {},
8+
copy = {}
9+
}
610

711
local function clear_prompt()
812
vim.api.nvim_command('normal :esc<CR>')
@@ -109,6 +113,88 @@ local function remove_dir(cwd)
109113
return luv.fs_rmdir(cwd)
110114
end
111115

116+
local function do_copy(source, destination)
117+
local source_stats = luv.fs_stat(source)
118+
119+
if source_stats and source_stats.type == 'file' then
120+
return luv.fs_copyfile(source, destination)
121+
end
122+
123+
local handle = luv.fs_scandir(source)
124+
if type(handle) == 'string' then
125+
return api.nvim_err_writeln(handle)
126+
end
127+
128+
luv.fs_mkdir(destination, source_stats.mode)
129+
130+
while true do
131+
local name, t = luv.fs_scandir_next(handle)
132+
if not name then break end
133+
134+
local new_name = source..'/'..name
135+
local new_destination = destination..'/'..name
136+
if t == 'directory' then
137+
local success = do_copy(new_name, new_destination)
138+
if not success then return false end
139+
else
140+
local success = luv.fs_copyfile(new_name, new_destination)
141+
if not success then return false end
142+
end
143+
end
144+
145+
return true
146+
end
147+
148+
local function do_paste(node, action_type, action_fn)
149+
local clip = clipboard[action_type]
150+
if #clip == 0 then return end
151+
152+
local destination = node.absolute_path
153+
local stats = luv.fs_stat(destination)
154+
local is_dir = stats and stats.type == 'directory'
155+
156+
if not is_dir then
157+
destination = vim.fn.fnamemodify(destination, ':p:h')
158+
elseif not node.open then
159+
destination = vim.fn.fnamemodify(destination, ':p:h:h')
160+
end
161+
162+
local msg = #clip..' entries'
163+
164+
if #clip == 1 then
165+
msg = clip[1].absolute_path
166+
end
167+
168+
local ans = vim.fn.input(action_type..' '..msg..' to '..destination..'? y/n: ')
169+
clear_prompt()
170+
if not ans:match('^y') then
171+
return api.nvim_out_write('Canceled.\n')
172+
end
173+
174+
for _, entry in ipairs(clip) do
175+
local dest = destination..'/'..entry.name
176+
local success = action_fn(entry.absolute_path, dest)
177+
if not success then
178+
api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path)
179+
end
180+
end
181+
clipboard[action_type] = {}
182+
return refresh_tree()
183+
end
184+
185+
local function add_to_clipboard(node, clip)
186+
if node.name == '..' then return end
187+
188+
for idx, entry in ipairs(clip) do
189+
if entry.absolute_path == node.absolute_path then
190+
table.remove(clip, idx)
191+
return api.nvim_out_write(node.absolute_path..' removed to clipboard.\n')
192+
end
193+
end
194+
table.insert(clip, node)
195+
api.nvim_out_write(node.absolute_path..' added to clipboard.\n')
196+
end
197+
112198
function M.remove(node)
113199
if node.name == '..' then return end
114200

@@ -153,4 +239,20 @@ function M.rename(node)
153239
refresh_tree()
154240
end
155241

242+
function M.copy(node)
243+
add_to_clipboard(node, clipboard.copy)
244+
end
245+
246+
function M.cut(node)
247+
add_to_clipboard(node, clipboard.move)
248+
end
249+
250+
function M.paste(node)
251+
if clipboard.move[1] ~= nil then
252+
return do_paste(node, 'move', luv.fs_rename)
253+
end
254+
255+
return do_paste(node, 'copy', do_copy)
256+
end
257+
156258
return M

lua/lib/lib.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,9 @@ local function set_mappings()
209209
[bindings.remove] = 'on_keypress("remove")';
210210
[bindings.rename] = 'on_keypress("rename")';
211211
[bindings.preview] = 'on_keypress("preview")';
212+
[bindings.cut] = 'on_keypress("cut")';
213+
[bindings.copy] = 'on_keypress("copy")';
214+
[bindings.paste] = 'on_keypress("paste")';
212215
gx = "xdg_open()";
213216
}
214217

lua/tree.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ function M.on_keypress(mode)
3838
return fs.remove(node)
3939
elseif mode == 'rename' then
4040
return fs.rename(node)
41+
elseif mode == 'copy' then
42+
return fs.copy(node)
43+
elseif mode == 'cut' then
44+
return fs.cut(node)
45+
elseif mode == 'paste' then
46+
return fs.paste(node)
4147
end
4248

4349
if mode == 'preview' then

0 commit comments

Comments
 (0)