Skip to content

Commit 7743d90

Browse files
authored
Merge pull request #52 from kristijanhusak/feature/cut-copy-paste
Add cut,copy and paste functionality.
2 parents 5c809cd + 89df407 commit 7743d90

File tree

7 files changed

+159
-1
lines changed

7 files changed

+159
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ highlight LuaTreeFolderIcon guibg=blue
9393
- type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path.
9494
> 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
9595
- type `r` to rename a file
96+
- type `x` to add/remove file/directory to cut clipboard
97+
- type `c` to add/remove file/directory to copy clipboard
98+
- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation)
9699
- type `d` to delete a file (will prompt for confirmation)
97100
- if the file is a directory, `<CR>` will open the directory otherwise it will open the file in the buffer near the tree
98101
- if the file is a symlink, `<CR>` will follow the symlink (if the target is a file)

doc/nvim-tree-lua.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ It will also open the leafs of the tree leading to the file in the buffer
4545
(if you opened a file with something else than the LuaTree, like `fzf` or
4646
`:split`)
4747

48+
|:LuaTreeClipboard| *:LuaTreeClipboard*
49+
50+
Print clipboard content for both cut and copy
51+
4852
==============================================================================
4953
OPTIONS *nvim-tree-options*
5054

@@ -136,6 +140,10 @@ INFORMATIONS *nvim-tree-info*
136140

137141
- type 'a' to add a file
138142
- type 'r' to rename a file
143+
- type 'x' to add/remove file/directory to cut clipboard
144+
- type 'c' to add/remove file/directory to copy clipboard
145+
- type 'p' to paste from clipboard. Cut clipboard has precedence over copy
146+
(will prompt for confirmation)
139147
- type 'd' to delete a file (will prompt for confirmation)
140148

141149
- if the file is a directory, '<CR>' will open the directory
@@ -166,7 +174,10 @@ default keybindings will be applied to undefined keys.
166174
\ preview: '<Tab>',
167175
\ create: 'a',
168176
\ remove: 'd',
169-
\ rename: 'r'
177+
\ rename: 'r',
178+
\ cut: 'x',
179+
\ copy: 'c',
180+
\ paste: 'p',
170181
\ }
171182
172183
|Features| *nvim-tree-features*

lua/lib/config.lua

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

lua/lib/fs.lua

Lines changed: 127 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,95 @@ 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+
125+
if type(handle) == 'string' then
126+
return false, handle
127+
end
128+
129+
luv.fs_mkdir(destination, source_stats.mode)
130+
131+
while true do
132+
local name, t = luv.fs_scandir_next(handle)
133+
if not name then break end
134+
135+
local new_name = source..'/'..name
136+
local new_destination = destination..'/'..name
137+
local success, msg = do_copy(new_name, new_destination)
138+
if not success then return success, msg end
139+
end
140+
141+
return true
142+
end
143+
144+
local function do_paste(node, action_type, action_fn)
145+
if node.name == '..' then return end
146+
local clip = clipboard[action_type]
147+
if #clip == 0 then return end
148+
149+
local destination = node.absolute_path
150+
local stats = luv.fs_stat(destination)
151+
local is_dir = stats and stats.type == 'directory'
152+
153+
if not is_dir then
154+
destination = vim.fn.fnamemodify(destination, ':p:h')
155+
elseif not node.open then
156+
destination = vim.fn.fnamemodify(destination, ':p:h:h')
157+
end
158+
159+
local msg = #clip..' entries'
160+
161+
if #clip == 1 then
162+
msg = clip[1].absolute_path
163+
end
164+
165+
local ans = vim.fn.input(action_type..' '..msg..' to '..destination..'? y/n: ')
166+
clear_prompt()
167+
if not ans:match('^y') then
168+
return api.nvim_out_write('Canceled.\n')
169+
end
170+
171+
for _, entry in ipairs(clip) do
172+
local dest = destination..'/'..entry.name
173+
local dest_stats = luv.fs_stat(dest)
174+
local should_process = true
175+
if dest_stats then
176+
local ans = vim.fn.input(dest..' already exists, overwrite ? y/n: ')
177+
clear_prompt()
178+
should_process = ans:match('^y')
179+
end
180+
181+
if should_process then
182+
local success, msg = action_fn(entry.absolute_path, dest)
183+
if not success then
184+
api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path..' - '..msg)
185+
end
186+
end
187+
end
188+
clipboard[action_type] = {}
189+
return refresh_tree()
190+
end
191+
192+
local function add_to_clipboard(node, clip)
193+
if node.name == '..' then return end
194+
195+
for idx, entry in ipairs(clip) do
196+
if entry.absolute_path == node.absolute_path then
197+
table.remove(clip, idx)
198+
return api.nvim_out_write(node.absolute_path..' removed to clipboard.\n')
199+
end
200+
end
201+
table.insert(clip, node)
202+
api.nvim_out_write(node.absolute_path..' added to clipboard.\n')
203+
end
204+
112205
function M.remove(node)
113206
if node.name == '..' then return end
114207

@@ -153,4 +246,38 @@ function M.rename(node)
153246
refresh_tree()
154247
end
155248

249+
function M.copy(node)
250+
add_to_clipboard(node, clipboard.copy)
251+
end
252+
253+
function M.cut(node)
254+
add_to_clipboard(node, clipboard.move)
255+
end
256+
257+
function M.paste(node)
258+
if clipboard.move[1] ~= nil then
259+
return do_paste(node, 'move', luv.fs_rename)
260+
end
261+
262+
return do_paste(node, 'copy', do_copy)
263+
end
264+
265+
function M.print_clipboard()
266+
local content = {}
267+
if #clipboard.move > 0 then
268+
table.insert(content, 'Cut')
269+
for _, item in pairs(clipboard.move) do
270+
table.insert(content, ' * '..item.absolute_path)
271+
end
272+
end
273+
if #clipboard.copy > 0 then
274+
table.insert(content, 'Copy')
275+
for _, item in pairs(clipboard.copy) do
276+
table.insert(content, ' * '..item.absolute_path)
277+
end
278+
end
279+
280+
return api.nvim_out_write(table.concat(content, '\n')..'\n')
281+
end
282+
156283
return M

lua/lib/lib.lua

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

lua/tree.lua

Lines changed: 10 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
@@ -73,6 +79,10 @@ function M.refresh()
7379
lib.refresh_tree()
7480
end
7581

82+
function M.print_clipboard()
83+
fs.print_clipboard()
84+
end
85+
7686
function M.on_enter()
7787
local bufnr = api.nvim_get_current_buf()
7888
local bufname = api.nvim_buf_get_name(bufnr)

plugin/tree.vim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ command! LuaTreeOpen lua require'tree'.open()
2222
command! LuaTreeClose lua require'tree'.close()
2323
command! LuaTreeToggle lua require'tree'.toggle()
2424
command! LuaTreeRefresh lua require'tree'.refresh()
25+
command! LuaTreeClipboard lua require'tree'.print_clipboard()
2526
command! LuaTreeFindFile lua require'tree'.find_file()
2627

2728
let &cpo = s:save_cpo

0 commit comments

Comments
 (0)