From d5bbe203d4586509039937ea0b3a1c1f9c37e526 Mon Sep 17 00:00:00 2001 From: Igor Date: Fri, 12 Jun 2026 00:22:19 -0300 Subject: [PATCH 1/4] feat: restore buffers after loading a session --- doc/nvim-tree-lua.txt | 8 ++++ lua/nvim-tree/_meta/config/experimental.lua | 4 ++ lua/nvim-tree/autocmd.lua | 17 ++++++++ lua/nvim-tree/config.lua | 1 + lua/nvim-tree/session.lua | 46 +++++++++++++++++++++ 5 files changed, 76 insertions(+) create mode 100644 lua/nvim-tree/session.lua diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index c63b85beb51..d906734f89d 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -1911,6 +1911,13 @@ Config: experimental *nvim-tree-config-experimental* In the event of a problem please disable the experiment and raise an issue. + Fields: ~ + • {session_restore_nvim}? (`boolean`, default: `false`) Restore + nvim-tree buffers when restoring vim sessions + (requires 0.13+). To restore the working + directory, ensure that |'sessionoptions'| + includes "globals" + ============================================================================== Config: log *nvim-tree-config-log* @@ -2216,6 +2223,7 @@ Following is the default configuration, see |nvim_tree.config| for details. >lua persist = false, }, experimental = { + session_restore_nvim = false }, log = { enable = false, diff --git a/lua/nvim-tree/_meta/config/experimental.lua b/lua/nvim-tree/_meta/config/experimental.lua index da8467cf0a3..3cdda90647d 100644 --- a/lua/nvim-tree/_meta/config/experimental.lua +++ b/lua/nvim-tree/_meta/config/experimental.lua @@ -9,6 +9,10 @@ error("Cannot require a meta file") --- ---@class nvim_tree.config.experimental --- +---Restore nvim-tree buffers when restoring vim sessions (requires 0.13+). +---To restore the working directory, ensure that |'sessionoptions'| includes "globals" +---(default: `false`) +---@field session_restore_nvim? boolean --Example below for future reference: -- --Buffers opened by nvim-tree will use with relative paths instead of absolute. diff --git a/lua/nvim-tree/autocmd.lua b/lua/nvim-tree/autocmd.lua index 2056927ffc0..e0c9a608cdc 100644 --- a/lua/nvim-tree/autocmd.lua +++ b/lua/nvim-tree/autocmd.lua @@ -17,6 +17,23 @@ function M.global() 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() + require("nvim-tree.session").save() + end, + }) + + vim.api.nvim_create_autocmd("SessionLoadPost", { + group = augroup_id, + callback = function() + require("nvim-tree.session").restore() + end, + }) + end + + if config.g.tab.sync.open then vim.api.nvim_create_autocmd("TabEnter", { group = augroup_id, diff --git a/lua/nvim-tree/config.lua b/lua/nvim-tree/config.lua index 24d87c389a2..ae7d2fbfdd8 100644 --- a/lua/nvim-tree/config.lua +++ b/lua/nvim-tree/config.lua @@ -297,6 +297,7 @@ M.d = { -- config-default-start persist = false, }, experimental = { + session_restore_nvim = false }, log = { enable = false, diff --git a/lua/nvim-tree/session.lua b/lua/nvim-tree/session.lua new file mode 100644 index 00000000000..3dfa5e7c048 --- /dev/null +++ b/lua/nvim-tree/session.lua @@ -0,0 +1,46 @@ +local M = {} + +function M.save() + local cwd = require("nvim-tree.core").get_cwd() + vim.g.NvimTreeState = vim.json.encode({ cwd = cwd }) +end + +function M.restore() + local api = require("nvim-tree.api").tree + + ---@type table + local tabs = {} + + local session_state = vim.json.decode(vim.g.NvimTreeState or "{}") or {} + local path = session_state and session_state.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 From 3cddd189a8171c8aeaf4731ae85839fc659de234 Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 7 Jul 2026 23:57:06 -0300 Subject: [PATCH 2/4] feat(sessions): invalid json handling --- lua/nvim-tree/autocmd.lua | 3 +++ lua/nvim-tree/session.lua | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lua/nvim-tree/autocmd.lua b/lua/nvim-tree/autocmd.lua index e0c9a608cdc..d1de38b42ea 100644 --- a/lua/nvim-tree/autocmd.lua +++ b/lua/nvim-tree/autocmd.lua @@ -27,6 +27,9 @@ function M.global() 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, diff --git a/lua/nvim-tree/session.lua b/lua/nvim-tree/session.lua index 3dfa5e7c048..80cdd6f8785 100644 --- a/lua/nvim-tree/session.lua +++ b/lua/nvim-tree/session.lua @@ -1,5 +1,9 @@ local M = {} +local function load_state() + return vim.json.decode(vim.g.NvimTreeState or "{}") or {} +end + function M.save() local cwd = require("nvim-tree.core").get_cwd() vim.g.NvimTreeState = vim.json.encode({ cwd = cwd }) @@ -11,7 +15,10 @@ function M.restore() ---@type table local tabs = {} - local session_state = vim.json.decode(vim.g.NvimTreeState or "{}") or {} + local ok, session_state = pcall(load_state) + if not ok then + require("nvim-tree.notify").warn(string.format("Failed to restore cwd: %s", session_state)) + end local path = session_state and session_state.cwd or nil -- Save tabs with leftover nvim-tree buffers From afad15129139ffd61432f54909b274533af0f8bc Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 8 Jul 2026 20:51:48 -0300 Subject: [PATCH 3/4] feat(sessions): switch to "persist in a file" strategy --- lua/nvim-tree/autocmd.lua | 4 ++- lua/nvim-tree/session.lua | 64 ++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/lua/nvim-tree/autocmd.lua b/lua/nvim-tree/autocmd.lua index d1de38b42ea..e530cc52e9d 100644 --- a/lua/nvim-tree/autocmd.lua +++ b/lua/nvim-tree/autocmd.lua @@ -21,7 +21,9 @@ function M.global() vim.api.nvim_create_autocmd("SessionWritePre", { ---@diagnostic disable-line: param-type-mismatch group = augroup_id, callback = function() - require("nvim-tree.session").save() + -- 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, }) diff --git a/lua/nvim-tree/session.lua b/lua/nvim-tree/session.lua index 80cdd6f8785..65baac312e4 100644 --- a/lua/nvim-tree/session.lua +++ b/lua/nvim-tree/session.lua @@ -1,12 +1,59 @@ +local notify = require("nvim-tree.notify") + local M = {} -local function load_state() - return vim.json.decode(vim.g.NvimTreeState or "{}") or {} +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() - local cwd = require("nvim-tree.core").get_cwd() - vim.g.NvimTreeState = vim.json.encode({ cwd = cwd }) + -- 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() @@ -15,11 +62,14 @@ function M.restore() ---@type table local tabs = {} - local ok, session_state = pcall(load_state) + local ok, session_file_content = pcall(decode_session_file) + + -- Failing to restore cwd is not fatal, we can continue if not ok then - require("nvim-tree.notify").warn(string.format("Failed to restore cwd: %s", session_state)) + notify.warn(string.format("Failed to decode session file (%s): %s", storepath, session_file_content)) end - local path = session_state and session_state.cwd or nil + + 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 From 018cf9ae045b7d57b98f3bc78fbe58d3a71f40f0 Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 8 Jul 2026 21:09:28 -0300 Subject: [PATCH 4/4] docs: no longer needs to toggle sessionoptions flag --- doc/nvim-tree-lua.txt | 4 +--- lua/nvim-tree/_meta/config/experimental.lua | 1 - 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/doc/nvim-tree-lua.txt b/doc/nvim-tree-lua.txt index d906734f89d..cf32ebf45fe 100644 --- a/doc/nvim-tree-lua.txt +++ b/doc/nvim-tree-lua.txt @@ -1914,9 +1914,7 @@ Config: experimental *nvim-tree-config-experimental* Fields: ~ • {session_restore_nvim}? (`boolean`, default: `false`) Restore nvim-tree buffers when restoring vim sessions - (requires 0.13+). To restore the working - directory, ensure that |'sessionoptions'| - includes "globals" + (requires 0.13+). ============================================================================== Config: log *nvim-tree-config-log* diff --git a/lua/nvim-tree/_meta/config/experimental.lua b/lua/nvim-tree/_meta/config/experimental.lua index 3cdda90647d..2d48842ee2c 100644 --- a/lua/nvim-tree/_meta/config/experimental.lua +++ b/lua/nvim-tree/_meta/config/experimental.lua @@ -10,7 +10,6 @@ error("Cannot require a meta file") ---@class nvim_tree.config.experimental --- ---Restore nvim-tree buffers when restoring vim sessions (requires 0.13+). ----To restore the working directory, ensure that |'sessionoptions'| includes "globals" ---(default: `false`) ---@field session_restore_nvim? boolean --Example below for future reference: