From 9b20fcba72f0f8902f72e426100b17884b86a68b Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Thu, 16 Jul 2020 11:39:44 +0200 Subject: [PATCH 1/4] Add cut,copy and paste functionality. --- README.md | 3 ++ doc/nvim-tree-lua.txt | 9 +++- lua/lib/config.lua | 3 ++ lua/lib/fs.lua | 102 ++++++++++++++++++++++++++++++++++++++++++ lua/lib/lib.lua | 3 ++ lua/tree.lua | 6 +++ 6 files changed, 125 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4220f978bad..38c025b2e70 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ highlight LuaTreeFolderIcon guibg=blue - type `a` to add a file. Adding a directory requires leaving a leading `/` at the end of the path. > 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 - type `r` to rename a file +- type `x` to add/remove file/directory to cut clipboard +- type `c` to add/remove file/directory to copy clipboard +- type `p` to paste from clipboard. Cut clipboard has precedence over copy (will prompt for confirmation) - type `d` to delete a file (will prompt for confirmation) - if the file is a directory, `` will open the directory otherwise it will open the file in the buffer near the tree - if the file is a symlink, `` will follow the symlink (if the target is a file) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index 3becdce3480..c0f2e8e923c 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -136,6 +136,10 @@ INFORMATIONS *nvim-tree-info* - type 'a' to add a file - type 'r' to rename a file +- type 'x' to add/remove file/directory to cut clipboard +- type 'c' to add/remove file/directory to copy clipboard +- type 'p' to paste from clipboard. Cut clipboard has precedence over copy + (will prompt for confirmation) - type 'd' to delete a file (will prompt for confirmation) - if the file is a directory, '' will open the directory @@ -165,7 +169,10 @@ default keybindings will be applied to undefined keys. \ preview: '', \ create: 'a', \ remove: 'd', - \ rename: 'r' + \ rename: 'r', + \ cut: 'x', + \ copy: 'c', + \ paste: 'p', \ } |Features| *nvim-tree-features* diff --git a/lua/lib/config.lua b/lua/lib/config.lua index bab24e44a83..d26b15a8e7a 100644 --- a/lua/lib/config.lua +++ b/lua/lib/config.lua @@ -54,6 +54,9 @@ function M.get_bindings() create = keybindings.create or 'a', remove = keybindings.remove or 'd', rename = keybindings.rename or 'r', + cut = keybindings.cut or 'x', + copy = keybindings.copy or 'c', + paste = keybindings.paste or 'p', } end diff --git a/lua/lib/fs.lua b/lua/lib/fs.lua index 75dcc36477e..0f8883bd1f6 100644 --- a/lua/lib/fs.lua +++ b/lua/lib/fs.lua @@ -3,6 +3,10 @@ local luv = vim.loop local open_mode = luv.constants.O_CREAT + luv.constants.O_WRONLY + luv.constants.O_TRUNC local M = {} +local clipboard = { + move = {}, + copy = {} +} local function clear_prompt() vim.api.nvim_command('normal :esc') @@ -109,6 +113,88 @@ local function remove_dir(cwd) return luv.fs_rmdir(cwd) end +local function do_copy(source, destination) + local source_stats = luv.fs_stat(source) + + if source_stats and source_stats.type == 'file' then + return luv.fs_copyfile(source, destination) + end + + local handle = luv.fs_scandir(source) + if type(handle) == 'string' then + return api.nvim_err_writeln(handle) + end + + luv.fs_mkdir(destination, source_stats.mode) + + while true do + local name, t = luv.fs_scandir_next(handle) + if not name then break end + + local new_name = source..'/'..name + local new_destination = destination..'/'..name + if t == 'directory' then + local success = do_copy(new_name, new_destination) + if not success then return false end + else + local success = luv.fs_copyfile(new_name, new_destination) + if not success then return false end + end + end + + return true +end + +local function do_paste(node, action_type, action_fn) + local clip = clipboard[action_type] + if #clip == 0 then return end + + local destination = node.absolute_path + local stats = luv.fs_stat(destination) + local is_dir = stats and stats.type == 'directory' + + if not is_dir then + destination = vim.fn.fnamemodify(destination, ':p:h') + elseif not node.open then + destination = vim.fn.fnamemodify(destination, ':p:h:h') + end + + local msg = #clip..' entries' + + if #clip == 1 then + msg = clip[1].absolute_path + end + + local ans = vim.fn.input(action_type..' '..msg..' to '..destination..'? y/n: ') + clear_prompt() + if not ans:match('^y') then + return api.nvim_out_write('Canceled.\n') + end + + for _, entry in ipairs(clip) do + local dest = destination..'/'..entry.name + local success = action_fn(entry.absolute_path, dest) + if not success then + api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path) + end + end + clipboard[action_type] = {} + return refresh_tree() +end + +local function add_to_clipboard(node, clip) + if node.name == '..' then return end + + for idx, entry in ipairs(clip) do + if entry.absolute_path == node.absolute_path then + table.remove(clip, idx) + return api.nvim_out_write(node.absolute_path..' removed to clipboard.\n') + end + end + table.insert(clip, node) + api.nvim_out_write(node.absolute_path..' added to clipboard.\n') +end + function M.remove(node) if node.name == '..' then return end @@ -153,4 +239,20 @@ function M.rename(node) refresh_tree() end +function M.copy(node) + add_to_clipboard(node, clipboard.copy) +end + +function M.cut(node) + add_to_clipboard(node, clipboard.move) +end + +function M.paste(node) + if clipboard.move[1] ~= nil then + return do_paste(node, 'move', luv.fs_rename) + end + + return do_paste(node, 'copy', do_copy) +end + return M diff --git a/lua/lib/lib.lua b/lua/lib/lib.lua index 724645adbd2..d4d6d3d6bc9 100644 --- a/lua/lib/lib.lua +++ b/lua/lib/lib.lua @@ -209,6 +209,9 @@ local function set_mappings() [bindings.remove] = 'on_keypress("remove")'; [bindings.rename] = 'on_keypress("rename")'; [bindings.preview] = 'on_keypress("preview")'; + [bindings.cut] = 'on_keypress("cut")'; + [bindings.copy] = 'on_keypress("copy")'; + [bindings.paste] = 'on_keypress("paste")'; gx = "xdg_open()"; } diff --git a/lua/tree.lua b/lua/tree.lua index c4f8565ec0e..e1c12bd77c6 100644 --- a/lua/tree.lua +++ b/lua/tree.lua @@ -38,6 +38,12 @@ function M.on_keypress(mode) return fs.remove(node) elseif mode == 'rename' then return fs.rename(node) + elseif mode == 'copy' then + return fs.copy(node) + elseif mode == 'cut' then + return fs.cut(node) + elseif mode == 'paste' then + return fs.paste(node) end if mode == 'preview' then From 86ff3b725fcb7a721272faf8560aa59b049ceb6d Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 21 Jul 2020 08:56:13 +0200 Subject: [PATCH 2/4] Prompt for confirmation when overwriting on cut/copy actions. --- lua/lib/fs.lua | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lua/lib/fs.lua b/lua/lib/fs.lua index 0f8883bd1f6..e97de79757f 100644 --- a/lua/lib/fs.lua +++ b/lua/lib/fs.lua @@ -121,8 +121,9 @@ local function do_copy(source, destination) end local handle = luv.fs_scandir(source) + if type(handle) == 'string' then - return api.nvim_err_writeln(handle) + return false, handle end luv.fs_mkdir(destination, source_stats.mode) @@ -133,13 +134,8 @@ local function do_copy(source, destination) local new_name = source..'/'..name local new_destination = destination..'/'..name - if t == 'directory' then - local success = do_copy(new_name, new_destination) - if not success then return false end - else - local success = luv.fs_copyfile(new_name, new_destination) - if not success then return false end - end + local success, msg = do_copy(new_name, new_destination) + if not success then return success, msg end end return true @@ -173,10 +169,18 @@ local function do_paste(node, action_type, action_fn) for _, entry in ipairs(clip) do local dest = destination..'/'..entry.name - local success = action_fn(entry.absolute_path, dest) + local dest_stats = luv.fs_stat(dest) + if dest_stats then + local ans = vim.fn.input(dest..' already exists, overwrite ? y/n: ') + clear_prompt() + if not ans:match('^y') then goto continue end + end + + local success, msg = action_fn(entry.absolute_path, dest) if not success then - api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path) + api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path..' - '..msg) end + ::continue:: end clipboard[action_type] = {} return refresh_tree() From 7ddee0a79c23ffa0978fc044fd6583e8c7a8f372 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 21 Jul 2020 12:35:28 +0200 Subject: [PATCH 3/4] Return if pasting on root and remove continue. --- lua/lib/fs.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lua/lib/fs.lua b/lua/lib/fs.lua index e97de79757f..7bcf8aec2ae 100644 --- a/lua/lib/fs.lua +++ b/lua/lib/fs.lua @@ -142,6 +142,7 @@ local function do_copy(source, destination) end local function do_paste(node, action_type, action_fn) + if node.name == '..' then return end local clip = clipboard[action_type] if #clip == 0 then return end @@ -170,17 +171,19 @@ local function do_paste(node, action_type, action_fn) for _, entry in ipairs(clip) do local dest = destination..'/'..entry.name local dest_stats = luv.fs_stat(dest) + local should_process = true if dest_stats then local ans = vim.fn.input(dest..' already exists, overwrite ? y/n: ') clear_prompt() - if not ans:match('^y') then goto continue end + should_process = ans:match('^y') end - local success, msg = action_fn(entry.absolute_path, dest) - if not success then - api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path..' - '..msg) + if should_process then + local success, msg = action_fn(entry.absolute_path, dest) + if not success then + api.nvim_err_writeln('Could not '..action_type..' '..entry.absolute_path..' - '..msg) + end end - ::continue:: end clipboard[action_type] = {} return refresh_tree() From 89df407737d0eb457ba48b2f73e23e7a8c21de71 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 21 Jul 2020 15:14:05 +0200 Subject: [PATCH 4/4] Add command to print clipboard content. --- doc/nvim-tree-lua.txt | 4 ++++ lua/lib/fs.lua | 18 ++++++++++++++++++ lua/tree.lua | 4 ++++ plugin/tree.vim | 1 + 4 files changed, 27 insertions(+) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index bc4b3ff3ee8..3f5f05a6fd0 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -45,6 +45,10 @@ It will also open the leafs of the tree leading to the file in the buffer (if you opened a file with something else than the LuaTree, like `fzf` or `:split`) +|:LuaTreeClipboard| *:LuaTreeClipboard* + +Print clipboard content for both cut and copy + ============================================================================== OPTIONS *nvim-tree-options* diff --git a/lua/lib/fs.lua b/lua/lib/fs.lua index 7bcf8aec2ae..5df4f83045a 100644 --- a/lua/lib/fs.lua +++ b/lua/lib/fs.lua @@ -262,4 +262,22 @@ function M.paste(node) return do_paste(node, 'copy', do_copy) end +function M.print_clipboard() + local content = {} + if #clipboard.move > 0 then + table.insert(content, 'Cut') + for _, item in pairs(clipboard.move) do + table.insert(content, ' * '..item.absolute_path) + end + end + if #clipboard.copy > 0 then + table.insert(content, 'Copy') + for _, item in pairs(clipboard.copy) do + table.insert(content, ' * '..item.absolute_path) + end + end + + return api.nvim_out_write(table.concat(content, '\n')..'\n') +end + return M diff --git a/lua/tree.lua b/lua/tree.lua index 2b8b4e545c8..5ca4e8aaf71 100644 --- a/lua/tree.lua +++ b/lua/tree.lua @@ -79,6 +79,10 @@ function M.refresh() lib.refresh_tree() end +function M.print_clipboard() + fs.print_clipboard() +end + function M.on_enter() local bufnr = api.nvim_get_current_buf() local bufname = api.nvim_buf_get_name(bufnr) diff --git a/plugin/tree.vim b/plugin/tree.vim index 37b5bef7c58..55846582edb 100644 --- a/plugin/tree.vim +++ b/plugin/tree.vim @@ -22,6 +22,7 @@ command! LuaTreeOpen lua require'tree'.open() command! LuaTreeClose lua require'tree'.close() command! LuaTreeToggle lua require'tree'.toggle() command! LuaTreeRefresh lua require'tree'.refresh() +command! LuaTreeClipboard lua require'tree'.print_clipboard() command! LuaTreeFindFile lua require'tree'.find_file() let &cpo = s:save_cpo