From da8de99d0bdc6684213de54905c56ca827a8836f Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 7 Feb 2026 17:02:54 +0000 Subject: [PATCH] fix(window): use real last buffer of closed window Problem: close_buffer autocmds may switch buffers at the last moment when closing a window, causing terminal_check_size to prefer the size of a closed window, or TabClosed to set an old . Solution: use the actual last buffer, similar to what TabClosed did before. NOTE: If buffer was unloaded/deleted (not wiped), then TabClosed's may not use it. (w_buffer = NULL) Maybe odd, but it's how it worked before anyhow. Relies on close_buffer reliably setting w_buffer to NULL if freed, otherwise buf_valid is better. Only concern I see is if the window wasn't in the window list after closing the buffer (close_buffer won't set it to NULL then), but then win_close{_othertab} should've returned earlier. --- src/nvim/window.c | 8 ++++ test/functional/autocmd/tabclose_spec.lua | 14 +++++++ test/functional/terminal/window_spec.lua | 49 +++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/src/nvim/window.c b/src/nvim/window.c index e1f29a62f3..f3b88f7a0b 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2976,6 +2976,10 @@ int win_close(win_T *win, bool free_buf, bool force) } } + // About to free the window. Remember its final buffer for terminal_check_size, + // which may have changed since the last set_bufref. (e.g: close_buffer autocmds) + set_bufref(&bufref, win->w_buffer); + // Free the memory used for the window and get the window that received // the screen space. int dir; @@ -3262,6 +3266,10 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force) } } + // About to free the window. Remember its final buffer for terminal_check_size/TabClosed, + // which may have changed since the last set_bufref. (e.g: close_buffer autocmds) + set_bufref(&bufref, win->w_buffer); + // Free the memory used for the window. int dir; win_free_mem(win, &dir, tp); diff --git a/test/functional/autocmd/tabclose_spec.lua b/test/functional/autocmd/tabclose_spec.lua index b6f05ffcaa..38bdcfa441 100644 --- a/test/functional/autocmd/tabclose_spec.lua +++ b/test/functional/autocmd/tabclose_spec.lua @@ -80,6 +80,20 @@ describe('TabClosed', function() ]]) eq(true, eval('nvim_buf_is_valid(g:buf)')) eq(eval('g:buf'), tonumber(eval('g:abuf'))) + + exec([[ + tabnew + let s:win = win_getid() + + tabfirst + let g:buf = nvim_create_buf(1, 1) + au BufHidden * ++once call nvim_win_set_buf(s:win, g:buf) + au TabClosed * ++once let g:abuf = expand('') + + call nvim_win_close(s:win, 1) + ]]) + -- BufHidden switched buffers at the last moment; TabClosed's should show that. + eq(eval('g:buf'), tonumber(eval('g:abuf'))) end) end) diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index d948d32cc8..bb6b9658cf 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -430,6 +430,8 @@ describe(':terminal window', function() [1] = { reverse = true }, [2] = { background = 225, foreground = Screen.colors.Gray0 }, [3] = { bold = true }, + [4] = { foreground = 12 }, + [5] = { reverse = true, bold = true }, [17] = { background = 2, foreground = Screen.colors.Grey0 }, [18] = { background = 2, foreground = 8 }, [19] = { underline = true, foreground = Screen.colors.Grey0, background = 7 }, @@ -517,6 +519,53 @@ describe(':terminal window', function() {17:foo [-] }{18:foo [-] }| {3:-- TERMINAL --} | ]]) + + -- Sizing logic should only consider the final buffer shown in a window, even if autocommands + -- changed it at the last moment. + exec_lua(function() + vim.g.fired = 0 + vim.api.nvim_create_autocmd('BufHidden', { + callback = function(ev) + vim.api.nvim_win_set_buf(vim.fn.win_findbuf(ev.buf)[1], vim.fn.bufnr('foo')) + vim.g.fired = vim.g.fired + 1 + return vim.g.fired == 2 + end, + }) + end) + command('botright new') + screen:expect([[ + rows: 2, cols: 25 │rows: 5, cols: 50 | + │rows: 2, cols: 50 | + {18:foo [-] foo [-] }| + ^ | + {4:~ }| + {5:[No Name] }| + | + ]]) + command('quit') + eq(1, eval('g:fired')) + screen:expect([[ + rows: 5, cols: 50 │rows: 5, cols: 25 | + rows: 5, cols: 25 │rows: 5, cols: 50 | + rows: 2, cols: 25 │rows: 2, cols: 50 | + rows: 5, cols: 25 │rows: 5, cols: 25 | + ^ │rows: 5, cols: 40 | + {17:foo [-] }{18:foo [-] }| + | + ]]) + -- Check it doesn't use the size of the closed window in the other tab page; size should only + -- change via the :wincmd below. Hide tabline so it doesn't affect sizes. + command('set showtabline=0 | tabnew | tabprevious | wincmd > | tabonly') + eq(2, eval('g:fired')) + screen:expect([[ + rows: 5, cols: 25 │rows: 5, cols: 25 | + rows: 2, cols: 25 │rows: 5, cols: 50 | + rows: 5, cols: 25 │rows: 2, cols: 50 | + rows: 5, cols: 26 │rows: 5, cols: 25 | + ^ │rows: 5, cols: 40 | + {17:foo [-] }{18:foo [-] }| + | + ]]) end) it('restores window options when switching terminals', function()