diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 09993d03de..4597d70f9c 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -46,6 +46,7 @@ #include "nvim/errors.h" #include "nvim/eval/typval.h" #include "nvim/eval/vars.h" +#include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_cmds_defs.h" @@ -1462,11 +1463,21 @@ static int do_buffer_ext(int action, int start, int dir, int count, int flags) } close_windows(buf, false); - // close_windows keeps the last non-float window. If it still - // shows buf, jump there and retry so curbuf gets replaced. - if (bufref_valid(&bufref) && buf->b_nwindows > 0) { - win_T *holder = buf_jump_open_win(buf); - if (holder != NULL && !holder->w_floating) { + // close_windows() refuses to close curtab's last non-float window. + // If it still shows buf, retry the delete from there. + win_T *holder = close_windows(buf, false); + if (buf != curbuf && bufref_valid(&bufref) && holder != NULL) { + if (curwin->w_floating) { + // swap into holder without entering it: caller keeps focus, + // BufEnter doesn't fire for the deleted buffer. + switchwin_T switchwin; + const int rv = switch_win_noblock(&switchwin, holder, curtab, true); + assert(rv == OK); + (void)rv; + do_buffer_ext(action, start, dir, count, flags); + restore_win_noblock(&switchwin, true); + } else { + buf_jump_open_win(buf); return do_buffer_ext(action, start, dir, count, flags); } } diff --git a/src/nvim/window.c b/src/nvim/window.c index a74816f3a4..8199843024 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2593,7 +2593,8 @@ void curwin_init(void) /// Closes all windows for buffer `buf` unless there is only one non-floating window. /// /// @param keep_curwin don't close `curwin` -void close_windows(buf_T *buf, bool keep_curwin) +/// @return the kept holder window, or NULL. +win_T *close_windows(buf_T *buf, bool keep_curwin) { RedrawingDisabled++; @@ -2646,6 +2647,12 @@ void close_windows(buf_T *buf, bool keep_curwin) theend: RedrawingDisabled--; + + // If buf is still shown in the last non-float window, let the caller retry there. + if (firstwin->w_buffer == buf && !firstwin->w_floating) { + return firstwin; + } + return NULL; } /// Check if "win" is the last non-floating window that exists. diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 7c4387ed6e..8978ccd783 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -764,6 +764,7 @@ describe('float window', function() it('if called from floating window with another buffer', function() api.nvim_set_current_win(other_buf_float) api.nvim_buf_delete(old_buf, { force = true }) + eq(other_buf_float, curwin()) end) end) describe('creates an empty buffer when there is only one listed buffer', function() @@ -776,13 +777,11 @@ describe('float window', function() command('set nobuflisted') api.nvim_set_current_win(old_win) end) - after_each(function() - expect('') - eq(2, #api.nvim_list_wins()) - end) it('if called from non-floating window', function() api.nvim_buf_delete(old_buf, { force = true }) eq(old_win, curwin()) + expect('') + eq(2, #api.nvim_list_wins()) end) it('if called from floating window with the same buffer', function() api.nvim_set_current_win(same_buf_float) @@ -792,12 +791,36 @@ describe('float window', function() eq(same_buf_float, eval('g:win_leave')) eq(old_win, eval('g:win_enter')) eq(old_win, curwin()) + expect('') + eq(2, #api.nvim_list_wins()) end) it('if called from floating window with an unlisted buffer', function() api.nvim_set_current_win(unlisted_buf_float) api.nvim_buf_delete(old_buf, { force = true }) + eq(unlisted_buf_float, curwin()) + expect('unlisted') + eq('', fn.bufname(api.nvim_win_get_buf(old_win))) + eq(false, api.nvim_buf_is_valid(old_buf)) + eq(2, #api.nvim_list_wins()) end) end) + it('keeps focus in the floating window #39800', function() + api.nvim_open_win(old_buf, false, float_opts) + local other_float = api.nvim_open_win(api.nvim_create_buf(true, false), true, float_opts) + api.nvim_buf_delete(old_buf, { force = true }) + eq(other_float, curwin()) + end) + + it('does not trigger BufEnter for deleted buffer #39800', function() + api.nvim_open_win(old_buf, false, float_opts) + api.nvim_open_win(api.nvim_create_buf(true, false), true, float_opts) + command('let g:abufs = []') + command('autocmd BufEnter * call add(g:abufs, +expand(""))') + api.nvim_buf_delete(old_buf, { force = true }) + for _, b in ipairs(eval('g:abufs')) do + neq(old_buf, b) + end + end) end) describe('with splits, deleting the last listed buffer creates an empty buffer', function() describe('when a non-floating window has an unlisted buffer', function()