From 498641a14510932b0a4ba95d5d0df5e303c2b74a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 4 Aug 2025 15:58:15 +1000 Subject: [PATCH 1/5] feat(#2826): allow only one window with nvim-tree buffer per tab --- lua/nvim-tree/explorer/view.lua | 88 +++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 25 deletions(-) diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua index 0708de29dd3..415126073e5 100644 --- a/lua/nvim-tree/explorer/view.lua +++ b/lua/nvim-tree/explorer/view.lua @@ -78,6 +78,33 @@ local BUFFER_OPTIONS = { { name = "swapfile", value = false }, } +---@private +---@param data table +---@param bufnr integer +function View:log_event(data, bufnr) + log.line("dev", "View %s\ + bufnr = %s\ + vim.api.nvim_get_current_tabpage() = %s\ + vim.api.nvim_get_current_win() = %s\ + self.bufnr_by_tabid = %s\ + globals.BUFNR_BY_TABID = %s\ + globals.WINID_BY_TABID = %s\ + vim.fn.win_findbuf(bufnr) = %s\ + data = %s\ + vim.v.event = %s", + data.event, + bufnr, + vim.api.nvim_get_current_tabpage(), + vim.api.nvim_get_current_win(), + vim.inspect(self.bufnr_by_tabid, { newline = "" }), + vim.inspect(globals.BUFNR_BY_TABID, { newline = "" }), + vim.inspect(globals.WINID_BY_TABID, { newline = "" }), + vim.inspect(vim.fn.win_findbuf(bufnr), { newline = "" }), + vim.inspect(data, { newline = "" }), + vim.inspect(vim.v.event, { newline = "" }) + ) +end + ---Buffer local autocommands to track state, deleted on buffer wipeout ---@private ---@param bufnr integer @@ -88,26 +115,13 @@ function View:create_autocmds(bufnr) group = self.explorer.augroup_id, buffer = bufnr, callback = function(data) - log.line("dev", - "View BufWipeout\n bufnr = %s\n data.buf = %s\n self.bufnr_by_tabid = %s\n self.winid_by_tabid = %s", - bufnr, - data.buf, - vim.inspect(self.bufnr_by_tabid, { newline = "" }), - vim.inspect(self.winid_by_tabid, { newline = "" }), - vim.inspect(data, { newline = "" }) - ) + self:log_event(data, bufnr) -- clear the tab's buffer self.bufnr_by_tabid = vim.tbl_map(function(b) return b ~= bufnr and b or nil end, self.bufnr_by_tabid) - -- clear the tab's window(s) - local winids = vim.fn.win_findbuf(bufnr) - self.winid_by_tabid = vim.tbl_map(function(winid) - return not vim.tbl_contains(winids, winid) and winid or nil - end, self.winid_by_tabid) - if self.explorer.opts.actions.open_file.eject then self:prevent_buffer_override() else @@ -116,22 +130,46 @@ function View:create_autocmds(bufnr) end, }) - -- set winid - vim.api.nvim_create_autocmd("BufWinEnter", { + -- close any other windows containing this buffer + -- not fired when entering the first window, only subsequent event such as following a split + -- does fire on :tabnew for _any_ buffer + vim.api.nvim_create_autocmd("WinEnter", { group = self.explorer.augroup_id, buffer = bufnr, callback = function(data) - local tabid = vim.api.nvim_get_current_tabpage() + self:log_event(data, bufnr) + + -- ignore other buffers + if data.buf ~= bufnr then + return + end + + -- ignore other tabs + -- this event is fired on a a :tabnew window, even though the buffer isn't actually present + local tabid_cur = vim.api.nvim_get_current_tabpage() + if self.bufnr_by_tabid[tabid_cur] ~= bufnr then + return + end + + -- are there any other windows containing bufnr? + local winids_buf = vim.fn.win_findbuf(bufnr) + if #winids_buf <= 1 then + return + end + + -- close all other windows + local winid_cur = vim.api.nvim_get_current_win() + for _, winid in ipairs(winids_buf) do + if winid ~= winid_cur then + pcall(vim.api.nvim_win_close, winid, false) + end + end - log.line("dev", - "View BufWinEnter\n bufnr = %s\n data.buf = %s\n self.bufnr_by_tabid = %s\n self.winid_by_tabid = %s", - bufnr, - data.buf, - vim.inspect(self.bufnr_by_tabid, { newline = "" }), - vim.inspect(self.winid_by_tabid, { newline = "" }) - ) + globals.WINID_BY_TABID[tabid_cur] = winid_cur - self.winid_by_tabid[tabid] = vim.fn.bufwinid(data.buf) -- first on current tabpage + -- setup this window, it may be new e.g. split + self:set_window_options_and_buffer() + self:resize() end, }) end From 2651f9b34a942109ed8fb9259ba1b3de0dceaf75 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 4 Aug 2025 16:13:31 +1000 Subject: [PATCH 2/5] feat(#2826): remove globals.BUFNR_BY_TABID --- lua/nvim-tree/explorer/view.lua | 14 +++++--------- lua/nvim-tree/globals.lua | 1 - 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua index 415126073e5..1512965858f 100644 --- a/lua/nvim-tree/explorer/view.lua +++ b/lua/nvim-tree/explorer/view.lua @@ -87,7 +87,6 @@ function View:log_event(data, bufnr) vim.api.nvim_get_current_tabpage() = %s\ vim.api.nvim_get_current_win() = %s\ self.bufnr_by_tabid = %s\ - globals.BUFNR_BY_TABID = %s\ globals.WINID_BY_TABID = %s\ vim.fn.win_findbuf(bufnr) = %s\ data = %s\ @@ -97,7 +96,6 @@ function View:log_event(data, bufnr) vim.api.nvim_get_current_tabpage(), vim.api.nvim_get_current_win(), vim.inspect(self.bufnr_by_tabid, { newline = "" }), - vim.inspect(globals.BUFNR_BY_TABID, { newline = "" }), vim.inspect(globals.WINID_BY_TABID, { newline = "" }), vim.inspect(vim.fn.win_findbuf(bufnr), { newline = "" }), vim.inspect(data, { newline = "" }), @@ -179,7 +177,7 @@ end ---@param bufnr integer ---@return boolean function View:matches_bufnr(bufnr) - for _, b in pairs(globals.BUFNR_BY_TABID) do + for _, b in pairs(self.bufnr_by_tabid) do if b == bufnr then return true end @@ -208,8 +206,6 @@ function View:create_buffer(bufnr) self.bufnr_by_tabid[tabid] = bufnr - globals.BUFNR_BY_TABID[tabid] = bufnr - vim.api.nvim_buf_set_name(bufnr, "NvimTree_" .. tabid) for _, option in ipairs(BUFFER_OPTIONS) do @@ -544,14 +540,14 @@ end function View:abandon_current_window() local tab = vim.api.nvim_get_current_tabpage() - globals.BUFNR_BY_TABID[tab] = nil + self.bufnr_by_tabid[tab] = nil globals.WINID_BY_TABID[tab] = nil end function View:abandon_all_windows() for tab, _ in pairs(vim.api.nvim_list_tabpages()) do - globals.BUFNR_BY_TABID[tab] = nil + self.bufnr_by_tabid[tab] = nil globals.WINID_BY_TABID[tab] = nil end end @@ -628,7 +624,7 @@ end ---@param tabid number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage. ---@return integer? winid function View:winid(tabid) - local bufnr = globals.BUFNR_BY_TABID[tabid] + local bufnr = self.bufnr_by_tabid[tabid] if bufnr then for _, winid in pairs(vim.api.nvim_tabpage_list_wins(tabid or 0)) do @@ -653,7 +649,7 @@ end function View:get_bufnr() local tab = vim.api.nvim_get_current_tabpage() - return globals.BUFNR_BY_TABID[tab] + return self.bufnr_by_tabid[tab] end function View:prevent_buffer_override() diff --git a/lua/nvim-tree/globals.lua b/lua/nvim-tree/globals.lua index 66211260bc6..765f3209223 100644 --- a/lua/nvim-tree/globals.lua +++ b/lua/nvim-tree/globals.lua @@ -3,7 +3,6 @@ local M = { -- from View WINID_BY_TABID = {}, - BUFNR_BY_TABID = {}, CURSORS = {}, } From 5dc93f3a8f926f1d2a3a8ef9a5b7c62dc2655b77 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 4 Aug 2025 16:38:51 +1000 Subject: [PATCH 3/5] Revert "feat(#2826): remove globals.BUFNR_BY_TABID" This reverts commit 2651f9b34a942109ed8fb9259ba1b3de0dceaf75. --- lua/nvim-tree/explorer/view.lua | 14 +++++++++----- lua/nvim-tree/globals.lua | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua index 1512965858f..415126073e5 100644 --- a/lua/nvim-tree/explorer/view.lua +++ b/lua/nvim-tree/explorer/view.lua @@ -87,6 +87,7 @@ function View:log_event(data, bufnr) vim.api.nvim_get_current_tabpage() = %s\ vim.api.nvim_get_current_win() = %s\ self.bufnr_by_tabid = %s\ + globals.BUFNR_BY_TABID = %s\ globals.WINID_BY_TABID = %s\ vim.fn.win_findbuf(bufnr) = %s\ data = %s\ @@ -96,6 +97,7 @@ function View:log_event(data, bufnr) vim.api.nvim_get_current_tabpage(), vim.api.nvim_get_current_win(), vim.inspect(self.bufnr_by_tabid, { newline = "" }), + vim.inspect(globals.BUFNR_BY_TABID, { newline = "" }), vim.inspect(globals.WINID_BY_TABID, { newline = "" }), vim.inspect(vim.fn.win_findbuf(bufnr), { newline = "" }), vim.inspect(data, { newline = "" }), @@ -177,7 +179,7 @@ end ---@param bufnr integer ---@return boolean function View:matches_bufnr(bufnr) - for _, b in pairs(self.bufnr_by_tabid) do + for _, b in pairs(globals.BUFNR_BY_TABID) do if b == bufnr then return true end @@ -206,6 +208,8 @@ function View:create_buffer(bufnr) self.bufnr_by_tabid[tabid] = bufnr + globals.BUFNR_BY_TABID[tabid] = bufnr + vim.api.nvim_buf_set_name(bufnr, "NvimTree_" .. tabid) for _, option in ipairs(BUFFER_OPTIONS) do @@ -540,14 +544,14 @@ end function View:abandon_current_window() local tab = vim.api.nvim_get_current_tabpage() - self.bufnr_by_tabid[tab] = nil + globals.BUFNR_BY_TABID[tab] = nil globals.WINID_BY_TABID[tab] = nil end function View:abandon_all_windows() for tab, _ in pairs(vim.api.nvim_list_tabpages()) do - self.bufnr_by_tabid[tab] = nil + globals.BUFNR_BY_TABID[tab] = nil globals.WINID_BY_TABID[tab] = nil end end @@ -624,7 +628,7 @@ end ---@param tabid number|nil (optional) the number of the chosen tabpage. Defaults to current tabpage. ---@return integer? winid function View:winid(tabid) - local bufnr = self.bufnr_by_tabid[tabid] + local bufnr = globals.BUFNR_BY_TABID[tabid] if bufnr then for _, winid in pairs(vim.api.nvim_tabpage_list_wins(tabid or 0)) do @@ -649,7 +653,7 @@ end function View:get_bufnr() local tab = vim.api.nvim_get_current_tabpage() - return self.bufnr_by_tabid[tab] + return globals.BUFNR_BY_TABID[tab] end function View:prevent_buffer_override() diff --git a/lua/nvim-tree/globals.lua b/lua/nvim-tree/globals.lua index 765f3209223..66211260bc6 100644 --- a/lua/nvim-tree/globals.lua +++ b/lua/nvim-tree/globals.lua @@ -3,6 +3,7 @@ local M = { -- from View WINID_BY_TABID = {}, + BUFNR_BY_TABID = {}, CURSORS = {}, } From f322a2092a7838ac46d1d50b3e9838456b60f734 Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Tue, 5 Aug 2025 15:14:03 +1000 Subject: [PATCH 4/5] feat(#2826): remove unused View.winid_by_tabid --- lua/nvim-tree/explorer/view.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua index 415126073e5..468706ccc64 100644 --- a/lua/nvim-tree/explorer/view.lua +++ b/lua/nvim-tree/explorer/view.lua @@ -20,7 +20,6 @@ local Class = require("nvim-tree.classic") ---@field private padding integer -- TODO multi-instance replace with single members ---@field private bufnr_by_tabid table ----@field private winid_by_tabid table local View = Class:extend() ---@class View @@ -39,7 +38,6 @@ function View:new(args) self.side = (self.explorer.opts.view.side == "right") and "right" or "left" self.live_filter = { prev_focused_node = nil, } self.bufnr_by_tabid = {} - self.winid_by_tabid = {} self.winopts = { relativenumber = self.explorer.opts.view.relativenumber, @@ -165,8 +163,6 @@ function View:create_autocmds(bufnr) end end - globals.WINID_BY_TABID[tabid_cur] = winid_cur - -- setup this window, it may be new e.g. split self:set_window_options_and_buffer() self:resize() From f7bd7319d2f0b21b10cd767dab5597344044cd7e Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Tue, 5 Aug 2025 15:21:56 +1000 Subject: [PATCH 5/5] feat(#2826): add feature gate experimental.close_other_windows_in_tab --- lua/nvim-tree/explorer/view.lua | 48 ++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/lua/nvim-tree/explorer/view.lua b/lua/nvim-tree/explorer/view.lua index 468706ccc64..dd591b92878 100644 --- a/lua/nvim-tree/explorer/view.lua +++ b/lua/nvim-tree/explorer/view.lua @@ -107,7 +107,6 @@ end ---@private ---@param bufnr integer function View:create_autocmds(bufnr) - -- clear bufnr and winid -- eject buffer opened in the nvim-tree window and create a new buffer vim.api.nvim_create_autocmd("BufWipeout", { group = self.explorer.augroup_id, @@ -128,7 +127,6 @@ function View:create_autocmds(bufnr) end, }) - -- close any other windows containing this buffer -- not fired when entering the first window, only subsequent event such as following a split -- does fire on :tabnew for _any_ buffer vim.api.nvim_create_autocmd("WinEnter", { @@ -149,27 +147,39 @@ function View:create_autocmds(bufnr) return end - -- are there any other windows containing bufnr? - local winids_buf = vim.fn.win_findbuf(bufnr) - if #winids_buf <= 1 then - return - end - - -- close all other windows - local winid_cur = vim.api.nvim_get_current_win() - for _, winid in ipairs(winids_buf) do - if winid ~= winid_cur then - pcall(vim.api.nvim_win_close, winid, false) - end - end - - -- setup this window, it may be new e.g. split - self:set_window_options_and_buffer() - self:resize() + -- close other windows in this tab + self:close_other_windows(bufnr) end, }) end +---Close any other windows containing this buffer and setup current window +---Feature gated behind experimental.close_other_windows_in_tab +---@param bufnr integer +function View:close_other_windows(bufnr) + if not self.explorer.opts.experimental.close_other_windows_in_tab then + return + end + + -- are there any other windows containing bufnr? + local winids_buf = vim.fn.win_findbuf(bufnr) + if #winids_buf <= 1 then + return + end + + -- close all other windows + local winid_cur = vim.api.nvim_get_current_win() + for _, winid in ipairs(winids_buf) do + if winid ~= winid_cur then + pcall(vim.api.nvim_win_close, winid, false) + end + end + + -- setup current window, it may be new e.g. split + self:set_window_options_and_buffer() + self:resize() +end + -- TODO multi-instance remove this; delete buffers rather than retaining them ---@private ---@param bufnr integer