Compare commits

...

7 Commits

Author SHA1 Message Date
Uanela Como
27aeba9b03 Merge branch 'master' into feat/rename-with-substitute 2026-07-14 12:15:43 +02:00
Igor Lacerda
cf18a662cd feat: experiment: restore nvim-tree on session load, see https://github.com/nvim-tree/nvim-tree.lua/issues/3343 (#3335)
* feat: restore buffers after loading a session

* feat(sessions): invalid json handling

* feat(sessions): switch to "persist in a file" strategy

* docs: no longer needs to toggle sessionoptions flag

---------

Co-authored-by: Alexander Courtis <alex@courtis.org>
2026-07-10 11:27:15 +10:00
Alexander Courtis
a797d5e201 fix(#3346): api.tree.resize: 'configure_width' (a nil value) (#3347) 2026-07-09 10:21:38 +10:00
Alexander Courtis
35d3511e7a refactor(#3344): temporarily allow deprecated nvim_win_set_width until a replacement is provided (#3345) 2026-07-07 11:09:35 +10:00
Uanela Como
7ff7040bf6 docs: update README with cut, copy, and paste features between instances (#3342)
Update README with cut, copy, and paste features between instances
2026-07-02 11:05:47 +10:00
github-actions[bot]
531b807b8f chore(master): release nvim-tree 1.18.0 (#3318)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-01 12:53:44 +10:00
Uanela Como
c760c92667 feat(#1549): p will paste files from another nvim-tree instance, gp will cut/paste (#3336)
* feat: add copy absolute path with protocol into public api

* feat: add helper get_basename into node

* feat: add gby keymap, and allow visual mode for copy.basename|absolute_path

* feat(clipboard): add opts for adding protocol in reg operations

* feat(api): allow visual operation in absolute_path*

* feat: add paste with protocol to keymap+api

* feat: add paste with protocol to clipboard

* feat: add configurable clipboard protocol

* fix: filter descendant nodes on copy_absolute_path_with_protocol

* refactor: change clip protocol keymaps to bgy and bgp

* wip

* fix(clipboad): correctly show the number of copied nodes on bgy

* chore: remove bgy, bgp and add gp + gx

* chore: update api/fs.lua and api impl.lua to match new keymaps

* fix: correctly pass the function to new keymaps

* chore: remove use_protocol and use use_register + cut options

* chore: update docs

* chore: move node:get_basename to cliboard_

* chore(clipboard): remove vim.cmd workaround in favor of vim.fn.setreg

* chore: use newline as separator instead of comma

* refactor(clipboard): simplify copying node attribute and allow all to work in visual mode

* refactor(keymap): remove gx, use gp to move

* feat(clipboard): always copy/cut to register and make `p` pick local data or register data

* refactor(docs): update api docs and rename paste_while_cutting to move

* fix: correctly format keymaps

* chore: regenerate docs

* fix: remove unused apis

* chore: copy clipboard.data to register on copy/cut

* chore: revert copy_node_attribute

* refactor: remove visual mode from copy.basename|filename|path

* chore: update docs

* chore: add fs.move api and update docs

* chore: destroy dummy nodes and prevent empty paths nodes parsing

* fix: _meta/api/fs.lua and docs

* fix(clipboard): destroy dummy nodes from register on paste

* fix(clipboard): correctly check for local paste operation
2026-07-01 10:11:03 +10:00
15 changed files with 313 additions and 44 deletions

View File

@@ -1,3 +1,3 @@
{ {
".": "1.17.0" ".": "1.18.0"
} }

View File

@@ -1,5 +1,20 @@
# Changelog # Changelog
## [1.18.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.17.0...nvim-tree-v1.18.0) (2026-07-01)
### Features
* **#1549:** p will paste files from another nvim-tree instance, gp will cut/paste ([#3336](https://github.com/nvim-tree/nvim-tree.lua/issues/3336)) ([c760c92](https://github.com/nvim-tree/nvim-tree.lua/commit/c760c92667fe5febf99e2a5bb8a543b0208c743b))
### Bug Fixes
* **#3312:** correct cursor when full name shown, requires nvim 0.12.2+ ([#3316](https://github.com/nvim-tree/nvim-tree.lua/issues/3316)) ([d277467](https://github.com/nvim-tree/nvim-tree.lua/commit/d277467fc0d1d0e2bca88165a1de6b526f9f6fe8))
* **#3324:** deprecated BufModifiedSet event replaced by OptionSet event for Nvim 0.13+ ([#3325](https://github.com/nvim-tree/nvim-tree.lua/issues/3325)) ([f9bfc00](https://github.com/nvim-tree/nvim-tree.lua/commit/f9bfc0059eca24546b69a7006110463da4808f8f))
* **#3327:** pass nvim_tree.api.node.open.Opts to all applicable nvim_tree.api.node.open function ([#3329](https://github.com/nvim-tree/nvim-tree.lua/issues/3329)) ([07f541f](https://github.com/nvim-tree/nvim-tree.lua/commit/07f541fcaa4a5ae019598240362449ab7e9812b3))
* **full-name:** lower `zindex` to get consistent cursor shape ([d277467](https://github.com/nvim-tree/nvim-tree.lua/commit/d277467fc0d1d0e2bca88165a1de6b526f9f6fe8))
## [1.17.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.16.0...nvim-tree-v1.17.0) (2026-04-07) ## [1.17.0](https://github.com/nvim-tree/nvim-tree.lua/compare/nvim-tree-v1.16.0...nvim-tree-v1.17.0) (2026-04-07)

View File

@@ -15,7 +15,9 @@
(Live) filtering (Live) filtering
Cut, copy, paste, rename, delete, create Rename, delete, create
Cut, copy, paste locally and between instances
Highly customisable Highly customisable

View File

@@ -20,7 +20,8 @@ Features
- Git integration - Git integration
- Diagnostics integration: LSP and COC - Diagnostics integration: LSP and COC
- (Live) filtering - (Live) filtering
- Cut, copy, paste, rename, delete, create - Rename, delete, create
- Cut, copy, paste locally and between instances
- Highly customisable - Highly customisable
File Icons File Icons
@@ -142,7 +143,7 @@ Show the mappings: `g?`
`F` n Live Filter: Clear |nvim_tree.api.filter.live.clear()| `F` n Live Filter: Clear |nvim_tree.api.filter.live.clear()|
`f` n Live Filter: Start |nvim_tree.api.filter.live.start()| `f` n Live Filter: Start |nvim_tree.api.filter.live.start()|
`g?` n Help |nvim_tree.api.tree.toggle_help()| `g?` n Help |nvim_tree.api.tree.toggle_help()|
`gy` n Copy Absolute Path |nvim_tree.api.fs.copy.absolute_path()| `gy` nx Copy Absolute Path |nvim_tree.api.fs.copy.absolute_path()|
`ge` n Copy Basename |nvim_tree.api.fs.copy.basename()| `ge` n Copy Basename |nvim_tree.api.fs.copy.basename()|
`H` n Toggle Filter: Dotfiles |nvim_tree.api.filter.dotfiles.toggle()| `H` n Toggle Filter: Dotfiles |nvim_tree.api.filter.dotfiles.toggle()|
`I` n Toggle Filter: Git Ignored |nvim_tree.api.filter.git.ignored.toggle()| `I` n Toggle Filter: Git Ignored |nvim_tree.api.filter.git.ignored.toggle()|
@@ -154,6 +155,7 @@ Show the mappings: `g?`
`o` n Open |nvim_tree.api.node.open.edit()| `o` n Open |nvim_tree.api.node.open.edit()|
`O` n Open: No Window Picker |nvim_tree.api.node.open.no_window_picker()| `O` n Open: No Window Picker |nvim_tree.api.node.open.no_window_picker()|
`p` n Paste |nvim_tree.api.fs.paste()| `p` n Paste |nvim_tree.api.fs.paste()|
`gp` n Move |nvim_tree.api.fs.move()|
`P` n Parent Directory |nvim_tree.api.node.navigate.parent()| `P` n Parent Directory |nvim_tree.api.node.navigate.parent()|
`q` n Close |nvim_tree.api.tree.close()| `q` n Close |nvim_tree.api.tree.close()|
`r` n Rename |nvim_tree.api.fs.rename()| `r` n Rename |nvim_tree.api.fs.rename()|
@@ -435,7 +437,7 @@ You are encouraged to copy these to your {on_attach} function. >lua
vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear")) vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear"))
vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start")) vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start"))
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help")) vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path")) vim.keymap.set({ "n", "x" }, "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename")) vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles")) vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles"))
vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored")) vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored"))
@@ -447,6 +449,7 @@ You are encouraged to copy these to your {on_attach} function. >lua
vim.keymap.set("n", "o", api.node.open.edit, opts("Open")) vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker")) vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
vim.keymap.set("n", "p", api.fs.paste, opts("Paste")) vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
vim.keymap.set("n", "gp", api.fs.move, opts("Move"))
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory")) vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
vim.keymap.set("n", "q", api.tree.close, opts("Close")) vim.keymap.set("n", "q", api.tree.close, opts("Close"))
vim.keymap.set("n", "r", api.fs.rename, opts("Rename")) vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))
@@ -1908,6 +1911,11 @@ Config: experimental *nvim-tree-config-experimental*
In the event of a problem please disable the experiment and raise an In the event of a problem please disable the experiment and raise an
issue. issue.
Fields: ~
• {session_restore_nvim}? (`boolean`, default: `false`) Restore
nvim-tree buffers when restoring vim sessions
(requires 0.13+).
============================================================================== ==============================================================================
Config: log *nvim-tree-config-log* Config: log *nvim-tree-config-log*
@@ -2213,6 +2221,7 @@ Following is the default configuration, see |nvim_tree.config| for details. >lua
persist = false, persist = false,
}, },
experimental = { experimental = {
session_restore_nvim = false
}, },
log = { log = {
enable = false, enable = false,
@@ -2459,8 +2468,20 @@ cut({node}) *nvim_tree.api.fs.cut()*
Parameters: ~ Parameters: ~
• {node} (`nvim_tree.api.Node?`) • {node} (`nvim_tree.api.Node?`)
move({node}) *nvim_tree.api.fs.move()*
Paste while cutting files from:
• current nvim-tree clipboard (if present)
• another nvim-tree instance via system clipboard
If {node} is a file it will pasted in the parent directory.
Parameters: ~
• {node} (`nvim_tree.api.Node?`)
paste({node}) *nvim_tree.api.fs.paste()* paste({node}) *nvim_tree.api.fs.paste()*
Paste from the nvim-tree clipboard. Paste files from:
• current nvim-tree clipboard (if present)
• another nvim-tree instance via system clipboard
If {node} is a file it will pasted in the parent directory. If {node} is a file it will pasted in the parent directory.

View File

@@ -59,13 +59,25 @@ function nvim_tree.api.fs.create(node) end
function nvim_tree.api.fs.cut(node) end function nvim_tree.api.fs.cut(node) end
--- ---
---Paste from the nvim-tree clipboard. ---Paste files from:
---- current nvim-tree clipboard (if present)
---- another nvim-tree instance via system clipboard
--- ---
---If {node} is a file it will pasted in the parent directory. ---If {node} is a file it will pasted in the parent directory.
--- ---
---@param node? nvim_tree.api.Node ---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.paste(node) end function nvim_tree.api.fs.paste(node) end
---
---Paste while cutting files from:
---- current nvim-tree clipboard (if present)
---- another nvim-tree instance via system clipboard
---
---If {node} is a file it will pasted in the parent directory.
---
---@param node? nvim_tree.api.Node
function nvim_tree.api.fs.move(node) end
--- ---
---Print the contents of the nvim-tree clipboard. ---Print the contents of the nvim-tree clipboard.
--- ---

View File

@@ -9,6 +9,9 @@ error("Cannot require a meta file")
--- ---
---@class nvim_tree.config.experimental ---@class nvim_tree.config.experimental
--- ---
---Restore nvim-tree buffers when restoring vim sessions (requires 0.13+).
---(default: `false`)
---@field session_restore_nvim? boolean
--Example below for future reference: --Example below for future reference:
-- --
--Buffers opened by nvim-tree will use with relative paths instead of absolute. --Buffers opened by nvim-tree will use with relative paths instead of absolute.

View File

@@ -9,6 +9,7 @@ local find_file = require("nvim-tree.actions.finders.find-file").fn
local Class = require("nvim-tree.classic") local Class = require("nvim-tree.classic")
local DirectoryNode = require("nvim-tree.node.directory") local DirectoryNode = require("nvim-tree.node.directory")
local FileNode = require("nvim-tree.node.file")
local Node = require("nvim-tree.node") local Node = require("nvim-tree.node")
---@alias ClipboardAction "copy" | "cut" ---@alias ClipboardAction "copy" | "cut"
@@ -176,28 +177,40 @@ function Clipboard:bulk_clipboard(nodes, from, to, verb)
self.explorer.renderer:draw() self.explorer.renderer:draw()
end end
---@private
---@param node_or_nodes Node|Node[]
---@return boolean
function Clipboard:is_nodes_array(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then
return false
end
return true
end
---Copy one or more nodes ---Copy one or more nodes
---@param node_or_nodes Node|Node[] ---@param node_or_nodes Node|Node[]
function Clipboard:copy(node_or_nodes) function Clipboard:copy(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then if self:is_nodes_array(node_or_nodes) == false then
utils.array_remove(self.data.cut, node_or_nodes) utils.array_remove(self.data.cut, node_or_nodes)
toggle(node_or_nodes, self.data.copy) toggle(node_or_nodes, self.data.copy)
self.explorer.renderer:draw() self.explorer.renderer:draw()
else else
self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.cut, self.data.copy, "added to") self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.cut, self.data.copy, "added to")
end end
self:copy_absolute_path(self.data.copy, { notify = false })
end end
---Cut one or more nodes ---Cut one or more nodes
---@param node_or_nodes Node|Node[] ---@param node_or_nodes Node|Node[]
function Clipboard:cut(node_or_nodes) function Clipboard:cut(node_or_nodes)
if type(node_or_nodes) == "table" and node_or_nodes.is and node_or_nodes:is(Node) then if self:is_nodes_array(node_or_nodes) == false then
utils.array_remove(self.data.copy, node_or_nodes) utils.array_remove(self.data.copy, node_or_nodes)
toggle(node_or_nodes, self.data.cut) toggle(node_or_nodes, self.data.cut)
self.explorer.renderer:draw() self.explorer.renderer:draw()
else else
self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.copy, self.data.cut, "cut to") self:bulk_clipboard(utils.filter_descendant_nodes(node_or_nodes), self.data.copy, self.data.cut, "cut to")
end end
self:copy_absolute_path(self.data.cut, { notify = false })
end end
---Clear clipboard for action and reload to reflect filesystem changes from paste. ---Clear clipboard for action and reload to reflect filesystem changes from paste.
@@ -304,6 +317,40 @@ function Clipboard:resolve_conflicts(conflict, destination, action, action_fn)
end) end)
end end
--- Transforms the copied absolute paths on register to node
---@private
function Clipboard:get_nodes_from_reg()
local content = vim.fn.getreg(self.reg)
if #content == 0 then
return {}
end
local nodes = {}
local absolute_paths = vim.split(content:sub(1, #content), "\n")
for _, absolute_path in ipairs(absolute_paths) do
if absolute_path:match("^%s*(.-)%s*s") then
local node_args = { absolute_path = absolute_path, name = vim.fn.fnamemodify(absolute_path, ":t"), explorer = self.explorer }
if absolute_path:sub(-1) == "/" then
node_args.name = vim.fn.fnamemodify(absolute_path:sub(1, -2), ":t")
table.insert(nodes, DirectoryNode(node_args))
else
table.insert(nodes, FileNode(node_args))
end
end
end
return nodes
end
---@param nodes Node[]
---@private
function Clipboard:destroy_nodes(nodes)
for _, n in ipairs(nodes) do
n:destroy()
end
end
---Paste cut or copy with batch conflict resolution. ---Paste cut or copy with batch conflict resolution.
---@private ---@private
---@param node Node ---@param node Node
@@ -318,7 +365,9 @@ function Clipboard:do_paste(node, action, action_fn)
node = dir:last_group_node() node = dir:last_group_node()
end end
end end
local clip = self.data[action] local is_local = #self.data[action] > 0
local clip = is_local and self.data[action] or self:get_nodes_from_reg()
if #clip == 0 then if #clip == 0 then
return return
end end
@@ -328,6 +377,9 @@ function Clipboard:do_paste(node, action, action_fn)
if not stats and err_name ~= "ENOENT" then if not stats and err_name ~= "ENOENT" then
log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err) log.line("copy_paste", "do_paste fs_stat '%s' failed '%s'", destination, err)
notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???")) notify.error("Could not " .. action .. " " .. notify.render_path(destination) .. " - " .. (err or "???"))
if is_local == false then
self:destroy_nodes(clip)
end
return return
end end
local is_dir = stats and stats.type == "directory" local is_dir = stats and stats.type == "directory"
@@ -359,6 +411,10 @@ function Clipboard:do_paste(node, action, action_fn)
else else
self:finish_paste(action) self:finish_paste(action)
end end
if is_local == false then
self:destroy_nodes(clip)
end
end end
---@param source string ---@param source string
@@ -386,10 +442,12 @@ end
---Paste cut (if present) or copy (if present) ---Paste cut (if present) or copy (if present)
---@param node Node ---@param node Node
function Clipboard:paste(node) ---@param opts? { cut?: boolean }
if self.data.cut[1] ~= nil then function Clipboard:paste(node, opts)
opts = opts and opts or {}
if self.data.cut[1] ~= nil or opts.cut == true then
self:do_paste(node, "cut", do_cut) self:do_paste(node, "cut", do_cut)
elseif self.data.copy[1] ~= nil then else
self:do_paste(node, "copy", do_copy) self:do_paste(node, "copy", do_copy)
end end
end end
@@ -413,17 +471,15 @@ function Clipboard:print_clipboard()
end end
---@param content string ---@param content string
function Clipboard:copy_to_reg(content) ---@param message? string
-- manually firing TextYankPost does not set vim.v.event ---@param opts? { notify?: boolean }
-- workaround: create a scratch buffer with the clipboard contents and send a yank command function Clipboard:copy_to_reg(content, message, opts)
local temp_buf = vim.api.nvim_create_buf(false, true) opts = opts and opts or {}
vim.api.nvim_buf_set_text(temp_buf, 0, 0, 0, 0, { content }) vim.fn.setreg(self.reg, type(content) == "table" and content or { content }, "v")
vim.api.nvim_buf_call(temp_buf, function()
vim.cmd(string.format('normal! "%sy$', self.reg))
end)
vim.api.nvim_buf_delete(temp_buf, {})
notify.info(string.format("Copied %s to %s clipboard!", content, self.clipboard_name)) if opts.notify ~= false then
notify.info(message or string.format("Copied %s to %s clipboard!", content, self.clipboard_name))
end
end end
---@param node Node ---@param node Node
@@ -437,18 +493,20 @@ function Clipboard:copy_filename(node)
end end
end end
---@private
---@param node Node ---@param node Node
function Clipboard:copy_basename(node) ---@return string
function Clipboard:get_absolute_path(node)
if node.name == ".." then if node.name == ".." then
-- root node = self.explorer
self:copy_to_reg(vim.fn.fnamemodify(self.explorer.absolute_path, ":t:r"))
else
-- node
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
end end
local absolute_path = node.absolute_path
return node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path
end end
---@param node Node ---@param node Node
---@return string|nil
function Clipboard:copy_path(node) function Clipboard:copy_path(node)
if node.name == ".." then if node.name == ".." then
-- root -- root
@@ -470,15 +528,44 @@ function Clipboard:copy_path(node)
end end
end end
---@param node Node ---@param node_or_nodes Node|Node[]
function Clipboard:copy_absolute_path(node) ---@param opts? { notify?: boolean }
if node.name == ".." then function Clipboard:copy_absolute_path(node_or_nodes, opts)
node = self.explorer opts = opts and opts or {}
local content
local is_single = self:is_nodes_array(node_or_nodes) == false
if is_single then
local node = #node_or_nodes == 1 and node_or_nodes[0] or node_or_nodes
content = self:get_absolute_path(node)
else
node_or_nodes = utils.filter_descendant_nodes(node_or_nodes)
content = {}
for _, node in ipairs(node_or_nodes) do
table.insert(content, self:get_absolute_path(node))
end
end end
local absolute_path = node.absolute_path
local content = node.nodes ~= nil and utils.path_add_trailing(absolute_path) or absolute_path if content ~= nil then
self:copy_to_reg(content) local message = nil
if not is_single then
message = string.format("%s %s copied to register", #content, #content > 1 and "absolute paths" or "absolute path")
end
self:copy_to_reg(content, message, opts)
end
end
---@param node Node
function Clipboard:copy_basename(node)
if node.name == ".." then
-- root
self:copy_to_reg(vim.fn.fnamemodify(node.explorer.absolute_path, ":t:r"))
else
-- node
self:copy_to_reg(vim.fn.fnamemodify(node.name, ":r"))
end
end end
---Node is cut. Will not be copied. ---Node is cut. Will not be copied.

View File

@@ -1,4 +1,5 @@
local view = require("nvim-tree.view") local view = require("nvim-tree.view")
local view_state = require("nvim-tree.view-state")
local M = {} local M = {}
@@ -7,7 +8,7 @@ local M = {}
function M.fn(opts) function M.fn(opts)
if opts == nil then if opts == nil then
-- reset to config values -- reset to config values
view.configure_width() view_state.configure_width()
view.resize() view.resize()
return return
end end
@@ -16,7 +17,7 @@ function M.fn(opts)
local width_cfg = options.width local width_cfg = options.width
if width_cfg ~= nil then if width_cfg ~= nil then
view.configure_width(width_cfg) view_state.configure_width(width_cfg)
view.resize() view.resize()
return return
end end

View File

@@ -155,14 +155,15 @@ function M.hydrate_post_setup(api)
api.filter.toggle = e_(function(e) e.filters:toggle() end) api.filter.toggle = e_(function(e) e.filters:toggle() end)
api.fs.clear_clipboard = e_(function(e) e.clipboard:clear_clipboard() end) api.fs.clear_clipboard = e_(function(e) e.clipboard:clear_clipboard() end)
api.fs.copy.absolute_path = en(function(e, n) e.clipboard:copy_absolute_path(n) end) api.fs.copy.absolute_path = ev(function(e, n) e.clipboard:copy_absolute_path(n) end)
api.fs.copy.basename = en(function(e, n) e.clipboard:copy_basename(n) end) api.fs.copy.basename = en(function(e, n) e.clipboard:copy_basename(n) end)
api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end) api.fs.copy.filename = en(function(e, n) e.clipboard:copy_filename(n) end)
api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end)
api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end) api.fs.copy.relative_path = en(function(e, n) e.clipboard:copy_path(n) end)
api.fs.copy.node = ev(function(e, n) e.clipboard:copy(n) end)
api.fs.create = _n(function(n) require("nvim-tree.actions.fs.create-file").fn(n) end) api.fs.create = _n(function(n) require("nvim-tree.actions.fs.create-file").fn(n) end)
api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end) api.fs.cut = ev(function(e, n) e.clipboard:cut(n) end)
api.fs.paste = en(function(e, n) e.clipboard:paste(n) end) api.fs.paste = en(function(e, n) e.clipboard:paste(n) end)
api.fs.move = en(function(e, n) e.clipboard:paste(n, { cut = true }) end)
api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end) api.fs.print_clipboard = e_(function(e) e.clipboard:print_clipboard() end)
api.fs.remove = _v(function(n) require("nvim-tree.actions.fs.remove-file").fn(n) end) api.fs.remove = _v(function(n) require("nvim-tree.actions.fs.remove-file").fn(n) end)
api.fs.rename = _n(function(n) require("nvim-tree.actions.fs.rename-file").rename_node(n) end) api.fs.rename = _n(function(n) require("nvim-tree.actions.fs.rename-file").rename_node(n) end)

View File

@@ -17,6 +17,28 @@ function M.global()
end, end,
}) })
if vim.fn.has("nvim-0.13") == 1 and config.g.experimental.session_restore_nvim then
vim.api.nvim_create_autocmd("SessionWritePre", { ---@diagnostic disable-line: param-type-mismatch
group = augroup_id,
callback = function()
-- We must schedule to wait until `vim.v.this_session` is properly updated
-- This prevents overwriting the data if a new session is created with a new `:mksession`
vim.schedule(require("nvim-tree.session").save)
end,
})
vim.api.nvim_create_autocmd("SessionLoadPost", {
group = augroup_id,
-- Workaround: this event fires multiple times (report upstream),
-- failure notifications could show up many times.
once = true,
callback = function()
require("nvim-tree.session").restore()
end,
})
end
if config.g.tab.sync.open then if config.g.tab.sync.open then
vim.api.nvim_create_autocmd("TabEnter", { vim.api.nvim_create_autocmd("TabEnter", {
group = augroup_id, group = augroup_id,

View File

@@ -297,6 +297,7 @@ M.d = { -- config-default-start
persist = false, persist = false,
}, },
experimental = { experimental = {
session_restore_nvim = false
}, },
log = { log = {
enable = false, enable = false,

View File

@@ -90,7 +90,7 @@ function M.on_attach_default(bufnr)
vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear")) vim.keymap.set("n", "F", api.filter.live.clear, opts("Live Filter: Clear"))
vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start")) vim.keymap.set("n", "f", api.filter.live.start, opts("Live Filter: Start"))
vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help")) vim.keymap.set("n", "g?", api.tree.toggle_help, opts("Help"))
vim.keymap.set("n", "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path")) vim.keymap.set({ "n", "x" }, "gy", api.fs.copy.absolute_path, opts("Copy Absolute Path"))
vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename")) vim.keymap.set("n", "ge", api.fs.copy.basename, opts("Copy Basename"))
vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles")) vim.keymap.set("n", "H", api.filter.dotfiles.toggle, opts("Toggle Filter: Dotfiles"))
vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored")) vim.keymap.set("n", "I", api.filter.git.ignored.toggle, opts("Toggle Filter: Git Ignored"))
@@ -102,6 +102,7 @@ function M.on_attach_default(bufnr)
vim.keymap.set("n", "o", api.node.open.edit, opts("Open")) vim.keymap.set("n", "o", api.node.open.edit, opts("Open"))
vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker")) vim.keymap.set("n", "O", api.node.open.no_window_picker, opts("Open: No Window Picker"))
vim.keymap.set("n", "p", api.fs.paste, opts("Paste")) vim.keymap.set("n", "p", api.fs.paste, opts("Paste"))
vim.keymap.set("n", "gp", api.fs.move, opts("Move"))
vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory")) vim.keymap.set("n", "P", api.node.navigate.parent, opts("Parent Directory"))
vim.keymap.set("n", "q", api.tree.close, opts("Close")) vim.keymap.set("n", "q", api.tree.close, opts("Close"))
vim.keymap.set("n", "r", api.fs.rename, opts("Rename")) vim.keymap.set("n", "r", api.fs.rename, opts("Rename"))

103
lua/nvim-tree/session.lua Normal file
View File

@@ -0,0 +1,103 @@
local notify = require("nvim-tree.notify")
local M = {}
local storepath = vim.fn.stdpath("data") .. "/nvim-tree-session.json"
local function decode_session_file()
local file = io.open(storepath, "r")
if file then
local content = file:read("*all")
file:close()
if content and content ~= "" then
return vim.json.decode(content)
end
end
-- Notifying on failure could be noisy, as it'd happen if the file simply did not exist
end
local function write_session_file(session_file_content)
local file, errmsg = io.open(storepath, "w")
if file then
file:write(vim.json.encode(session_file_content))
file:close()
else
notify.warn(string.format("Invalid session file (%s): %s", storepath, errmsg))
end
end
function M.save()
-- We either always save the session data, which ends up populating the storepath with empty data
-- Or we gate by the tree's visibility, which could create "out of sync" scenario:
-- if the tree is initially open and the session is saved, it won't be overridden later,
-- if the tree is no longer visible. However, when restoring, this is harmless.
if require("nvim-tree.api").tree.is_visible({ any_tabpage = true }) then
local ok, session_file_content = pcall(decode_session_file)
if not ok then
notify.warn(string.format("Failed to decode session file (%s): %s", storepath, session_file_content))
return
end
-- might be nil at this point
session_file_content = session_file_content or {}
local cwd = require("nvim-tree.core").get_cwd()
session_file_content[vim.v.this_session] = { cwd = cwd }
local ok2, err = pcall(write_session_file, session_file_content)
if not ok2 then
notify.warn(string.format("Failed to write session file (%s): %s", storepath, err))
end
end
end
function M.restore()
local api = require("nvim-tree.api").tree
---@type table<integer,boolean>
local tabs = {}
local ok, session_file_content = pcall(decode_session_file)
-- Failing to restore cwd is not fatal, we can continue
if not ok then
notify.warn(string.format("Failed to decode session file (%s): %s", storepath, session_file_content))
end
local path = session_file_content and session_file_content[vim.v.this_session] and session_file_content[vim.v.this_session].cwd or nil
-- Save tabs with leftover nvim-tree buffers
for _, tab in ipairs(vim.api.nvim_list_tabpages()) do
for _, win in ipairs(vim.api.nvim_tabpage_list_wins(tab)) do
local buf = vim.api.nvim_win_get_buf(win)
if api.is_tree_buf(buf) then
tabs[tab] = true
end
end
end
-- The new window may inherit options from the current one if we don't schedule
vim.schedule(function()
api.close_in_all_tabs()
for tab, _ in pairs(tabs) do
if vim.api.nvim_tabpage_is_valid(tab) then
local win = vim.api.nvim_tabpage_get_win(tab)
-- Invoke `open` from each tab's current window to prevent having to switch tabs
if vim.api.nvim_win_is_valid(win) then
vim.api.nvim_win_call(win, function()
api.open({ path = path })
end)
end
end
end
end)
end
return M

View File

@@ -71,7 +71,7 @@ end
---Configure width-related config ---Configure width-related config
---@param width string|function|number|table|nil ---@param width string|function|number|table|nil
local function configure_width(width) function M.configure_width(width)
if type(width) == "table" then if type(width) == "table" then
M.Active.adaptive_size = true M.Active.adaptive_size = true
M.Active.width = width.min or DEFAULT_MIN_WIDTH M.Active.width = width.min or DEFAULT_MIN_WIDTH
@@ -101,7 +101,7 @@ function M.initialize()
M.Active.winopts.relativenumber = config.g.view.relativenumber M.Active.winopts.relativenumber = config.g.view.relativenumber
M.Active.winopts.signcolumn = config.g.view.signcolumn M.Active.winopts.signcolumn = config.g.view.signcolumn
configure_width(config.g.view.width) M.configure_width(config.g.view.width)
M.Active.initial_width = M.get_width() M.Active.initial_width = M.get_width()
end end

View File

@@ -298,7 +298,7 @@ function M.resize(size)
local new_size = view_state.get_width() local new_size = view_state.get_width()
if new_size ~= vim.api.nvim_win_get_width(winnr) then if new_size ~= vim.api.nvim_win_get_width(winnr) then
vim.api.nvim_win_set_width(winnr, new_size) vim.api.nvim_win_set_width(winnr, new_size) ---@diagnostic disable-line: deprecated see #3344
if not config.g.view.preserve_window_proportions then if not config.g.view.preserve_window_proportions then
vim.cmd(":wincmd =") vim.cmd(":wincmd =")
end end