From 498641a14510932b0a4ba95d5d0df5e303c2b74a Mon Sep 17 00:00:00 2001 From: Alexander Courtis Date: Mon, 4 Aug 2025 15:58:15 +1000 Subject: [PATCH] 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 0708de29..41512607 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) - 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 = "" }) - ) + -- ignore other buffers + if data.buf ~= bufnr then + return + end - self.winid_by_tabid[tabid] = vim.fn.bufwinid(data.buf) -- first on current tabpage + -- 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 + + 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() end, }) end