diff --git a/README.md b/README.md index 3ade09969..f425ecb1d 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ should you! plenty of room to display the whole tree. - Neo-tree does not need to be manually refreshed (set `use_libuv_file_watcher=true`) - Neo-tree can intelligently follow the current file (set `follow_current_file.enabled=true`) +- Neo-tree can copy and cut files over many neovim instances (set `filesystem.shared_clipboard=true`) - Neo-tree is thoughtful about maintaining or setting focus on the right node - Neo-tree windows in different tabs are completely separate - `respect_gitignore` actually works! diff --git a/lua/neo-tree/defaults.lua b/lua/neo-tree/defaults.lua index cbd30c77d..b5c8d4a29 100644 --- a/lua/neo-tree/defaults.lua +++ b/lua/neo-tree/defaults.lua @@ -453,6 +453,7 @@ local config = { async_directory_scan = "auto", -- "auto" means refreshes are async, but it's synchronous when called from the Neotree commands. -- "always" means directory scans are always async. -- "never" means directory scans are never async. + shared_clipboard = false, -- Enabling this feature allows you to copy and cut files over different instances of neovim scan_mode = "shallow", -- "shallow": Don't scan into directories to detect possible empty directory a priori -- "deep": Scan into directories to detect empty or grouped empty directories a priori. bind_to_cwd = true, -- true creates a 2-way binding between vim's cwd and neo-tree's root diff --git a/lua/neo-tree/sources/filesystem/commands.lua b/lua/neo-tree/sources/filesystem/commands.lua index 3198d9f13..7bc2abc06 100644 --- a/lua/neo-tree/sources/filesystem/commands.lua +++ b/lua/neo-tree/sources/filesystem/commands.lua @@ -6,6 +6,7 @@ local utils = require("neo-tree.utils") local filter = require("neo-tree.sources.filesystem.lib.filter") local renderer = require("neo-tree.ui.renderer") local log = require("neo-tree.log") +local shared_clipboard = require("neo-tree.sources.filesystem.lib.shared_clipboard") local M = {} local refresh = function(state) @@ -16,6 +17,12 @@ local redraw = function(state) renderer.redraw(state) end +local sync_with_shared_clipboard = function(state) + if state.shared_clipboard then + shared_clipboard.save_clipboard(state.clipboard) + end +end + M.add = function(state) cc.add(state, utils.wrap(fs.show_new_children, state)) end @@ -35,19 +42,23 @@ end ---Marks node as copied, so that it can be pasted somewhere else. M.copy_to_clipboard = function(state) cc.copy_to_clipboard(state, utils.wrap(redraw, state)) + sync_with_shared_clipboard(state) end M.copy_to_clipboard_visual = function(state, selected_nodes) cc.copy_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state)) + sync_with_shared_clipboard(state) end ---Marks node as cut, so that it can be pasted (moved) somewhere else. M.cut_to_clipboard = function(state) cc.cut_to_clipboard(state, utils.wrap(redraw, state)) + sync_with_shared_clipboard(state) end M.cut_to_clipboard_visual = function(state, selected_nodes) cc.cut_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state)) + sync_with_shared_clipboard(state) end M.move = function(state) @@ -57,6 +68,7 @@ end ---Pastes all items from the clipboard to the current directory. M.paste_from_clipboard = function(state) cc.paste_from_clipboard(state, utils.wrap(fs.show_new_children, state)) + sync_with_shared_clipboard(state) end M.delete = function(state) diff --git a/lua/neo-tree/sources/filesystem/init.lua b/lua/neo-tree/sources/filesystem/init.lua index 7101864c1..7e94875a8 100644 --- a/lua/neo-tree/sources/filesystem/init.lua +++ b/lua/neo-tree/sources/filesystem/init.lua @@ -383,6 +383,10 @@ M.setup = function(config, global_config) end, }) end + + if config.shared_clipboard then + require("neo-tree.sources.filesystem.lib.shared_clipboard").init() + end end ---Expands or collapses the current node. diff --git a/lua/neo-tree/sources/filesystem/lib/shared_clipboard.lua b/lua/neo-tree/sources/filesystem/lib/shared_clipboard.lua new file mode 100644 index 000000000..2cc8114c7 --- /dev/null +++ b/lua/neo-tree/sources/filesystem/lib/shared_clipboard.lua @@ -0,0 +1,79 @@ +local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch") +local manager = require("neo-tree.sources.manager") +local events = require("neo-tree.events") +local renderer = require("neo-tree.ui.renderer") +local log = require("neo-tree.log") + +local M = {} + +local clipboard_state_dir_path = vim.fn.stdpath("state") .. "/neo-tree/" +local clipboard_file_path = clipboard_state_dir_path .. "filesystem-clipboard.json" +local clipboard_file_change_triggered_by_cur_neovim_instance = false + +M.save_clipboard = function(clipboard) + local file = io.open(clipboard_file_path, "w+") + -- We want to erase data in the file if clipboard is nil instead writing null + if not clipboard or not file then + return + end + + local is_success, data = pcall(vim.json.encode, clipboard) + if not is_success then + log.error("Failed to save clipboard. JSON serialization error") + return + end + file:write(data) + file:flush() + M._update_all_cilpboards(clipboard) + clipboard_file_change_triggered_by_cur_neovim_instance = true +end + +M._load_clipboard = function() + local file = io.open(clipboard_file_path, "r") + if not file then + return nil + end + local content = file:read("*a") + local is_success, clipboard = pcall(vim.json.decode, content) + if not is_success then + return nil + end + return clipboard +end + +M._update_all_cilpboards = function(clipboard) + manager._for_each_state("filesystem", function(state) + state.clipboard = clipboard + vim.schedule(function() + renderer.redraw(state) + end) + end) +end + +M.init = function() + if vim.fn.isdirectory(clipboard_state_dir_path) == 0 then + vim.fn.mkdir(clipboard_state_dir_path) + end + + events.subscribe({ + event = events.STATE_CREATED, + handler = function(state) + if state.name ~= "filesystem" then + return + end + vim.schedule(function() + M._update_all_cilpboards(M._load_clipboard()) + end) + end, + }) + + -- Using watch_folder because we haven't "watch_file" :) + fs_watch.watch_folder(clipboard_state_dir_path, function() + if not clipboard_file_change_triggered_by_cur_neovim_instance then + M._update_all_cilpboards(M._load_clipboard()) + end + clipboard_file_change_triggered_by_cur_neovim_instance = false + end, true) +end + +return M