Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ should you!

- Neo-tree won't let other buffers take over its window.
- Neo-tree won't leave its window scrolled to the last line when there is plenty
of room to display the whole tree.
- Neo-tree does not need to be manually refreshed (set
`use_libuv_file_watcher=true`)
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`)
`follow_current_file.enabled = true`)
- Neo-tree can sync its clipboard across multiple instances, either globally
(within the same Neovim instance) or universally (across multiple Neovim
instances). Set `clipboard.sync = "global" | "universal"`
- 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!
Expand All @@ -72,15 +75,19 @@ utilities, such as scanning the filesystem.

There are also some optional plugins that work with Neo-tree:

- [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) for file icons.
- [antosha417/nvim-lsp-file-operations](https://github.com/antosha417/nvim-lsp-file-operations) for LSP-enhanced renames/etc.
- [folke/snacks.nvim](https://github.com/folke/snacks.nvim) for image previews, see Preview Mode section.
- [snacks.rename](https://github.com/folke/snacks.nvim/blob/main/docs/rename.md#neo-treenvim) can also work with
Neo-tree
- [nvim-tree/nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
for file icons.
- [antosha417/nvim-lsp-file-operations](https://github.com/antosha417/nvim-lsp-file-operations)
for LSP-enhanced renames/etc.
- [folke/snacks.nvim](https://github.com/folke/snacks.nvim) for image previews,
see Preview Mode section.
- [snacks.rename](https://github.com/folke/snacks.nvim/blob/main/docs/rename.md#neo-treenvim)
can also work with Neo-tree
- [3rd/image.nvim](https://github.com/3rd/image.nvim) for image previews.
- If both snacks.nvim and image.nvim are installed. Neo-tree currently will
try to preview with snacks.nvim first, then try image.nvim.
- [s1n7ax/nvim-window-picker](https://github.com/s1n7ax/nvim-window-picker) for `_with_window_picker` keymaps.
- If both snacks.nvim and image.nvim are installed. Neo-tree currently will try
to preview with snacks.nvim first, then try image.nvim.
- [s1n7ax/nvim-window-picker](https://github.com/s1n7ax/nvim-window-picker) for
`_with_window_picker` keymaps.


### mini.deps example:
Expand Down
12 changes: 12 additions & 0 deletions doc/neo-tree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Configuration ............... |neo-tree-configuration|
Components and Renderers .. |neo-tree-renderers|
Buffer Variables .......... |neo-tree-buffer-variables|
Popups .................... |neo-tree-popups|
Clipboard ................. |neo-tree-clipboard|
Other Sources ............... |neo-tree-sources|
Buffers ................... |neo-tree-buffers|
Git Status ................ |neo-tree-git-status-source|
Expand Down Expand Up @@ -1912,6 +1913,17 @@ state to a string. The colors of the popup border are controlled by the
highlight group.


CLIPBOARD *neo-tree-clipboard*

Neo-tree's clipboard can be synced globally (within the same Neovim instance) or
universally (across multiple Neovim instances). The default is to not sync at
all. To change this option, change the `clipboard.sync` option (options are
`"none"|"global"|"universal"`). The universal sync option relies on a file
located under `stdpath("state") .. "/neo-tree.nvim/clipboards"` You can also
implement your own backend and pass it to that option as well - reading the
source code of `require('neo-tree.clipboard')` is a good way to do it.


================================================================================
OTHER SOURCES ~
================================================================================
Expand Down
91 changes: 91 additions & 0 deletions lua/neo-tree/clipboard/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
local events = require("neo-tree.events")
local manager = require("neo-tree.sources.manager")
local log = require("neo-tree.log")

local M = {}

---@enum (key) neotree.clipboard.BackendNames.Builtin
local builtins = {
none = require("neo-tree.clipboard.sync.base"),
global = require("neo-tree.clipboard.sync.global"),
universal = require("neo-tree.clipboard.sync.universal"),
}

---@type table<string, neotree.clipboard.Backend?>
M.backends = builtins

---@alias neotree.Config.Clipboard.Sync neotree.clipboard.BackendNames.Builtin|neotree.clipboard.Backend

---@class (exact) neotree.Config.Clipboard
---@field sync neotree.Config.Clipboard.Sync?

---@param opts neotree.Config.Clipboard
M.setup = function(opts)
opts = opts or {}
opts.sync = opts.sync or "none"

---@type neotree.clipboard.Backend?
local selected_backend
if type(opts.sync) == "string" then
selected_backend = M.backends[opts.sync]
elseif type(opts.sync) == "table" then
local sync = opts.sync
---@cast sync -neotree.clipboard.BackendNames.Builtin
selected_backend = sync
end

if not selected_backend then
log.error("invalid clipboard sync method, disabling sync")
selected_backend = builtins.none
end
M.current_backend = assert(selected_backend:new())

events.subscribe({
event = events.STATE_CREATED,
---@param new_state neotree.State
handler = function(new_state)
local clipboard, err = M.current_backend:load(new_state)
if not clipboard then
if err then
log.error(err)
end
return
end
new_state.clipboard = clipboard
end,
})

events.subscribe({
event = events.NEO_TREE_CLIPBOARD_CHANGED,
---@param state neotree.State
handler = function(state)
local ok, err = M.current_backend:save(state)
if ok == false then
log.error(err)
end
M.sync_to_clipboards(state)
end,
})
end

---@param exclude_state neotree.State?
function M.sync_to_clipboards(exclude_state)
-- try loading the changed clipboard into all other states
vim.schedule(function()
manager._for_each_state(nil, function(state)
if exclude_state == state then
return
end
local modified_clipboard, err = M.current_backend:load(state)
if not modified_clipboard then
if err then
log.error(err)
end
return
end
state.clipboard = modified_clipboard
end)
end)
end

return M
33 changes: 33 additions & 0 deletions lua/neo-tree/clipboard/sync/base.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---@class neotree.clipboard.Backend
local Backend = {}

---@class neotree.clipboard.Node
---@field action string
---@field node NuiTree.Node

---@alias neotree.clipboard.Contents table<string, neotree.clipboard.Node?>

---@return neotree.clipboard.Backend?
function Backend:new()
local o = {}
setmetatable(o, self)
self.__index = self
return o
end

---Loads the clipboard from the backend
---Return a nil clipboard to not make any changes.
---@param state neotree.State
---@return neotree.clipboard.Contents|false? clipboard
---@return string? err
function Backend:load(state) end

---Writes the clipboard to the backend
---Returns nil when nothing was saved
---@param state neotree.State
---@return boolean? success_or_noop
function Backend:save(state)
return true
end

return Backend
17 changes: 17 additions & 0 deletions lua/neo-tree/clipboard/sync/global.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local Backend = require("neo-tree.clipboard.sync.base")
local g = vim.g
---@class neotree.clipboard.GlobalBackend : neotree.clipboard.Backend
local GlobalBackend = Backend:new()

---@type table<string, neotree.clipboard.Contents?>
local clipboards = {}

function GlobalBackend:load(state)
return clipboards[state.name]
end

function GlobalBackend:save(state)
clipboards[state.name] = state.clipboard
end

return GlobalBackend
Loading
Loading