From e002e4d7fceeac91adb57bf00bb23e383447c99f Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:37:58 +0000 Subject: [PATCH] vim-patch:9.1.2068: :bd/bw may try to switch to a closing buffer Problem: :bdelete/bunload/bwipeout may attempt to switch to a closing buffer, which fails. (after 9.1.2058) Solution: don't consider switching to closing buffers (Sean Dewar) closes: vim/vim#19107 https://github.com/vim/vim/commit/63d53de72d94b53172070acb2b1c50489d1133bd Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com> --- src/nvim/buffer.c | 17 ++-- test/old/testdir/test_buffer.vim | 146 +++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 7 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 47ab7c8d43..4b94a1eb8f 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -1434,10 +1434,11 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) // Then prefer the buffer we most recently visited. // Else try to find one that is loaded, after the current buffer, // then before the current buffer. - // Finally use any buffer. + // Finally use any buffer. Skip buffers that are closing throughout. buf = NULL; // Selected buffer. bp = NULL; // Used when no loaded buffer found. - if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) { + if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf) + && !au_new_curbuf.br_buf->b_locked_split) { buf = au_new_curbuf.br_buf; } else if (curwin->w_jumplistlen > 0) { if (jop_flags & kOptJopFlagClean) { @@ -1469,8 +1470,9 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) if (buf != NULL) { // Skip current and unlisted bufs. Also skip a quickfix - // buffer, it might be deleted soon. - if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { + // or closing buffer, it might be deleted soon. + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf) + || buf->b_locked_split) { buf = NULL; } else if (buf->b_ml.ml_mfp == NULL) { // skip unloaded buf, but may keep it for later @@ -1514,7 +1516,8 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) continue; } // in non-help buffer, try to skip help buffers, and vv - if (buf->b_help == curbuf->b_help && buf->b_p_bl && !bt_quickfix(buf)) { + if (buf->b_help == curbuf->b_help && buf->b_p_bl + && !bt_quickfix(buf) && !buf->b_locked_split) { if (buf->b_ml.ml_mfp != NULL) { // found loaded buffer break; } @@ -1530,7 +1533,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) } if (buf == NULL) { // No loaded buffer, find listed one FOR_ALL_BUFFERS(buf2) { - if (buf2->b_p_bl && buf2 != curbuf && !bt_quickfix(buf2)) { + if (buf2->b_p_bl && buf2 != curbuf && !bt_quickfix(buf2) && !buf2->b_locked_split) { buf = buf2; break; } @@ -1538,7 +1541,7 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) } if (buf == NULL) { // Still no buffer, just take one buf = curbuf->b_next != NULL ? curbuf->b_next : curbuf->b_prev; - if (bt_quickfix(buf)) { + if (bt_quickfix(buf) || (buf != curbuf && buf->b_locked_split)) { buf = NULL; } } diff --git a/test/old/testdir/test_buffer.vim b/test/old/testdir/test_buffer.vim index ac6def23a9..765faca2f6 100644 --- a/test/old/testdir/test_buffer.vim +++ b/test/old/testdir/test_buffer.vim @@ -736,4 +736,150 @@ func Test_switch_to_previously_viewed_buffer() set startofline& endfunc +func Test_bdelete_skip_closing_bufs() + set hidden + let s:fired = 0 + + edit foo + edit bar + let s:next_new_bufnr = bufnr('$') + 1 + augroup SkipClosing + autocmd! + " Only window and other buffer is closing. + " No choice but to switch to a new, empty buffer. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('bar', bufname()) + \| bdelete + \| call assert_equal('', bufname()) + \| call assert_equal(s:next_new_bufnr, bufnr()) + augroup END + bdelete foo + call assert_equal(1, s:fired) + unlet! s:next_new_bufnr + %bw! + + edit baz + edit bar + edit fleb + edit foo + augroup SkipClosing + autocmd! + " Only window, au_new_curbuf is NOT closing; should end up there. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('foo', bufname()) + \| bwipeout + \| call assert_equal('bar', bufname()) + augroup END + buffer baz + buffer foo + augroup SkipClosing + autocmd BufLeave * ++once ++nested bdelete baz + augroup END + edit bar + call assert_equal(2, s:fired) + %bw! + + edit baz + edit bar + edit fleb + edit foo + augroup SkipClosing + autocmd! + " Like above, but au_new_curbuf IS closing. + " Should use the most recent jumplist buffer instead. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('foo', bufname()) + \| bwipeout + \| call assert_equal('baz', bufname()) + augroup END + buffer baz + buffer foo + augroup SkipClosing + autocmd BufLeave * ++once ++nested bdelete bar + augroup END + edit bar + call assert_equal(3, s:fired) + %bw! + + edit foo + edit floob + edit baz + edit bar + augroup SkipClosing + autocmd! + " Only window, most recent buffer in jumplist is closing. + " Should switch to the next most-recent buffer in the jumplist instead. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('bar', bufname()) + \| bdelete + \| call assert_equal('floob', bufname()) + augroup END + buffer baz + buffer floob + buffer foo + buffer bar + bdelete foo + call assert_equal(4, s:fired) + %bw! + + edit foo + edit baz + edit bar + edit floob + edit bazinga + augroup SkipClosing + autocmd! + " Only window, most recent jumplist buffer is gone, next most-recent is + " closing. Should switch to the 3rd most-recent jumplist buffer. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('bar', bufname()) + \| bwipeout + \| call assert_equal('baz', bufname()) + augroup END + buffer bazinga + buffer baz + buffer floob + buffer foo + buffer bar + noautocmd bdelete foo + bdelete floob + call assert_equal(5, s:fired) + %bw! + + edit foo + edit baz + edit floob + edit bazinga + edit bar + augroup SkipClosing + autocmd! + " Like above, but jumplist cleared, no next buffer in the buffer list and + " previous buffer is closing. Should switch to the buffer before previous. + autocmd BufDelete * ++once let s:fired += 1 + \| call assert_equal(1, winnr('$')) + \| call assert_equal('bar', bufname()) + \| bunload + \| call assert_equal('floob', bufname()) + augroup END + buffer bazinga + buffer baz + buffer floob + buffer foo + buffer bar + noautocmd bdelete foo + clearjumps + bdelete bazinga + call assert_equal(6, s:fired) + + unlet! s:fired + autocmd! SkipClosing + set hidden& + %bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab