diff --git a/README.md b/README.md index 4220f978..38c025b2 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 3becdce3..c0f2e8e9 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 bab24e44..d26b15a8 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 75dcc364..0f8883bd 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 724645ad..d4d6d3d6 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 c4f8565e..e1c12bd7 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