From d42fa1753adc18c4b22577724a804d556a16d736 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 5 Jan 2026 13:58:47 +0800 Subject: [PATCH 1/3] test(terminal): fix incorrect TermClose test The test may wipe the wrong buffer if :bdelete switches to another one. Also remove the builtin TermClose autocommand. It doesn't affect the tests for now, but still it's better to avoid its interference. --- test/functional/autocmd/termxx_spec.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index c5bc5d928e..96e4237d2a 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -20,13 +20,15 @@ describe('autocmd TermClose', function() clear() api.nvim_set_option_value('shell', testprg('shell-test'), {}) command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=') + command('autocmd! nvim.terminal TermClose') end) local function test_termclose_delete_own_buf() -- The terminal process needs to keep running so that TermClose isn't triggered immediately. api.nvim_set_option_value('shell', string.format('"%s" INTERACT', testprg('shell-test')), {}) - command('autocmd TermClose * bdelete!') command('terminal') + local termbuf = api.nvim_get_current_buf() + command(('autocmd TermClose * bdelete! %d'):format(termbuf)) matches( '^TermClose Autocommands for "%*": Vim%(bdelete%):E937: Attempt to delete a buffer that is in use: term://', pcall_err(command, 'bdelete!') @@ -34,8 +36,7 @@ describe('autocmd TermClose', function() assert_alive() end - -- TODO: fixed after merging patches for `can_unload_buffer`? - pending('TermClose deleting its own buffer, altbuf = buffer 1 #10386', function() + it('TermClose deleting its own buffer, altbuf = buffer 1 #10386', function() test_termclose_delete_own_buf() end) From 7297e9d339c64f2f2c57cb2cc57455c161ef78e5 Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 5 Jan 2026 14:11:39 +0800 Subject: [PATCH 2/3] fix(terminal): crash when TermClose deletes other buffers Problem: Crash when deleting terminal buffer and TermClose deletes other buffers. Solution: Close the terminal after restoring b_nwindows. --- src/nvim/buffer.c | 13 +++++++++---- test/functional/autocmd/termxx_spec.lua | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 3dd39fda42..666f7f6e20 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -648,10 +648,6 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i return true; } - if (buf->terminal) { - buf_close_terminal(buf); - } - // Always remove the buffer when there is no file name. if (buf->b_ffname == NULL) { del_buf = true; @@ -675,6 +671,15 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i buf->b_nwindows = nwindows; + if (buf->terminal) { + buf_close_terminal(buf); + // Must check this before calling buf_freeall(), otherwise is_curbuf will be true + // in buf_freeall() but still false here, leading to a 0-line buffer. + if (buf == curbuf && !is_curbuf) { + return false; + } + } + buf_freeall(buf, ((del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0) + (ignore_abort ? BFA_IGNORE_ABORT : 0))); diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 96e4237d2a..21fe0a4fe8 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -45,6 +45,18 @@ describe('autocmd TermClose', function() test_termclose_delete_own_buf() end) + it('TermClose deleting all other buffers', function() + local oldbuf = api.nvim_get_current_buf() + -- The terminal process needs to keep running so that TermClose isn't triggered immediately. + api.nvim_set_option_value('shell', string.format('"%s" INTERACT', testprg('shell-test')), {}) + command(('autocmd TermClose * bdelete! %d'):format(oldbuf)) + command('horizontal terminal') + neq(oldbuf, api.nvim_get_current_buf()) + command('bdelete!') + feed('') -- This shouldn't crash due to having a 0-line buffer. + assert_alive() + end) + it('triggers when fast-exiting terminal job stops', function() command('autocmd TermClose * let g:test_termclose = 23') command('terminal') From ad85871ca133580f3e014ef37b9b2dbd46f90d9c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Mon, 5 Jan 2026 19:19:19 +0800 Subject: [PATCH 3/3] fix(terminal): crash when TermClose switches back to terminal buffer Problem: Crash when deleting terminal buffer and TermClose switches back to the terminal buffer. Solution: Set b_locked_split. Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/buffer.c | 3 +++ test/functional/autocmd/termxx_spec.lua | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 666f7f6e20..59d21b4932 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -672,7 +672,10 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i buf->b_nwindows = nwindows; if (buf->terminal) { + buf->b_locked_split++; buf_close_terminal(buf); + buf->b_locked_split--; + // Must check this before calling buf_freeall(), otherwise is_curbuf will be true // in buf_freeall() but still false here, leading to a 0-line buffer. if (buf == curbuf && !is_curbuf) { diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 21fe0a4fe8..99a14dbcf2 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -57,6 +57,17 @@ describe('autocmd TermClose', function() assert_alive() end) + it('TermClose switching back to terminal buffer', function() + local buf = api.nvim_get_current_buf() + api.nvim_open_term(buf, {}) + command(('autocmd TermClose * buffer %d | new'):format(buf)) + eq( + 'TermClose Autocommands for "*": Vim(buffer):E1546: Cannot switch to a closing buffer', + pcall_err(command, 'bwipe!') + ) + assert_alive() + end) + it('triggers when fast-exiting terminal job stops', function() command('autocmd TermClose * let g:test_termclose = 23') command('terminal')