diff --git a/README.md b/README.md index ec0eef764f1..1a19f1357d8 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,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 41342eb5ebb..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* @@ -136,6 +140,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 @@ -166,7 +174,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 70a39c625d1..54d83e5dcde 100644 --- a/lua/lib/config.lua +++ b/lua/lib/config.lua @@ -55,6 +55,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..5df4f83045a 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,95 @@ 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 false, 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 + local success, msg = do_copy(new_name, new_destination) + if not success then return success, msg end + end + + return true +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 + + 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 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() + should_process = ans:match('^y') + end + + 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 + 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 +246,38 @@ 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 + +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/lib/lib.lua b/lua/lib/lib.lua index fe8a2f57114..7c981054e29 100644 --- a/lua/lib/lib.lua +++ b/lua/lib/lib.lua @@ -210,6 +210,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 e700f343e94..5ca4e8aaf71 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 @@ -73,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