Skip to content

Commit 21eb9a9

Browse files
committed
feat(filesystem): Implement shared clipboard
1 parent a77af2e commit 21eb9a9

File tree

5 files changed

+110
-0
lines changed

5 files changed

+110
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ should you!
4545
plenty of room to display the whole tree.
4646
- Neo-tree does not need to be manually refreshed (set `use_libuv_file_watcher=true`)
4747
- Neo-tree can intelligently follow the current file (set `follow_current_file.enabled=true`)
48+
- Neo-tree can copy and cut files over many neovim instances (est `filesystem.shared_clipboard=true`)
4849
- Neo-tree is thoughtful about maintaining or setting focus on the right node
4950
- Neo-tree windows in different tabs are completely separate
5051
- `respect_gitignore` actually works!

lua/neo-tree/defaults.lua

+1
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ local config = {
453453
async_directory_scan = "auto", -- "auto" means refreshes are async, but it's synchronous when called from the Neotree commands.
454454
-- "always" means directory scans are always async.
455455
-- "never" means directory scans are never async.
456+
shared_clipboard = false, -- Enabling this feature allows you to copy and cut files over different instances of neovim
456457
scan_mode = "shallow", -- "shallow": Don't scan into directories to detect possible empty directory a priori
457458
-- "deep": Scan into directories to detect empty or grouped empty directories a priori.
458459
bind_to_cwd = true, -- true creates a 2-way binding between vim's cwd and neo-tree's root

lua/neo-tree/sources/filesystem/commands.lua

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ local utils = require("neo-tree.utils")
66
local filter = require("neo-tree.sources.filesystem.lib.filter")
77
local renderer = require("neo-tree.ui.renderer")
88
local log = require("neo-tree.log")
9+
local shared_clipboard = require("neo-tree.sources.filesystem.lib.shared_clipboard")
910

1011
local M = {}
1112
local refresh = function(state)
@@ -16,6 +17,12 @@ local redraw = function(state)
1617
renderer.redraw(state)
1718
end
1819

20+
local sync_with_shared_clipboard = function(state)
21+
if state.shared_clipboard then
22+
shared_clipboard.save_clipboard(state.clipboard)
23+
end
24+
end
25+
1926
M.add = function(state)
2027
cc.add(state, utils.wrap(fs.show_new_children, state))
2128
end
@@ -35,19 +42,23 @@ end
3542
---Marks node as copied, so that it can be pasted somewhere else.
3643
M.copy_to_clipboard = function(state)
3744
cc.copy_to_clipboard(state, utils.wrap(redraw, state))
45+
sync_with_shared_clipboard(state)
3846
end
3947

4048
M.copy_to_clipboard_visual = function(state, selected_nodes)
4149
cc.copy_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state))
50+
sync_with_shared_clipboard(state)
4251
end
4352

4453
---Marks node as cut, so that it can be pasted (moved) somewhere else.
4554
M.cut_to_clipboard = function(state)
4655
cc.cut_to_clipboard(state, utils.wrap(redraw, state))
56+
sync_with_shared_clipboard(state)
4757
end
4858

4959
M.cut_to_clipboard_visual = function(state, selected_nodes)
5060
cc.cut_to_clipboard_visual(state, selected_nodes, utils.wrap(redraw, state))
61+
sync_with_shared_clipboard(state)
5162
end
5263

5364
M.move = function(state)
@@ -57,6 +68,7 @@ end
5768
---Pastes all items from the clipboard to the current directory.
5869
M.paste_from_clipboard = function(state)
5970
cc.paste_from_clipboard(state, utils.wrap(fs.show_new_children, state))
71+
sync_with_shared_clipboard(state)
6072
end
6173

6274
M.delete = function(state)

lua/neo-tree/sources/filesystem/init.lua

+4
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,10 @@ M.setup = function(config, global_config)
383383
end,
384384
})
385385
end
386+
387+
if config.shared_clipboard then
388+
require("neo-tree.sources.filesystem.lib.shared_clipboard").init()
389+
end
386390
end
387391

388392
---Expands or collapses the current node.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
local fs_watch = require("neo-tree.sources.filesystem.lib.fs_watch")
2+
local manager = require("neo-tree.sources.manager")
3+
local events = require("neo-tree.events")
4+
local renderer = require("neo-tree.ui.renderer")
5+
6+
local M = {}
7+
8+
local clipboard_state_dir_path = vim.fn.stdpath("state") .. "/neo-tree/"
9+
local clipboard_file_path = clipboard_state_dir_path .. "filesystem-clipboard.json"
10+
local clipboard_file_last_mtime = nil
11+
12+
13+
M.save_clipboard = function(clipboard)
14+
local file = io.open(clipboard_file_path, "w+")
15+
-- We want to erase data in the file if clipboard is nil instead writing null
16+
if not clipboard or not file then
17+
return
18+
end
19+
20+
file:write(vim.json.encode(clipboard))
21+
file:flush()
22+
clipboard_file_last_mtime = vim.uv.fs_stat(clipboard_file_path).mtime
23+
end
24+
25+
M._load_clipboard = function()
26+
local file = io.open(clipboard_file_path, "r")
27+
if not file then
28+
return nil
29+
end
30+
local content = file:read("*a")
31+
local is_success, clipboard = pcall(vim.json.decode, content)
32+
if not is_success then
33+
return nil
34+
end
35+
return clipboard
36+
end
37+
38+
M._update_cilpboard = function()
39+
if not vim.fn.filereadable(clipboard_file_path) then
40+
return
41+
end
42+
local cur_mtime = vim.uv.fs_stat(clipboard_file_path).mtime
43+
if
44+
not clipboard_file_last_mtime
45+
-- We need exactly >= because it allows us to synchronize the clipboard
46+
-- even with trees openned in another window (in the current Neovim instance)
47+
or M._is_left_mtime_greater_or_equal(cur_mtime, clipboard_file_last_mtime)
48+
then
49+
local clipboard = M._load_clipboard()
50+
manager._for_each_state("filesystem", function(state)
51+
state.clipboard = clipboard
52+
vim.schedule(function()
53+
renderer.redraw(state)
54+
end)
55+
end)
56+
clipboard_file_last_mtime = cur_mtime
57+
end
58+
end
59+
60+
M._is_left_mtime_greater_or_equal = function(a, b)
61+
if a.sec > b.sec then
62+
return true
63+
elseif a.sec == b.sec and a.nsec >= b.nsec then
64+
return true
65+
end
66+
return false
67+
end
68+
69+
M.init = function()
70+
if vim.fn.isdirectory(clipboard_state_dir_path) == 0 then
71+
vim.fn.mkdir(clipboard_state_dir_path)
72+
end
73+
74+
events.subscribe({
75+
event = events.STATE_CREATED,
76+
handler = function(state)
77+
if state.name ~= "filesystem" then
78+
return
79+
end
80+
vim.schedule(function()
81+
M._update_cilpboard()
82+
end)
83+
end,
84+
})
85+
86+
-- Using watch_folder because we haven't "watch_file" :)
87+
fs_watch.watch_folder(clipboard_state_dir_path, function()
88+
M._update_cilpboard()
89+
end, true)
90+
end
91+
92+
return M

0 commit comments

Comments
 (0)