mirror of
https://github.com/neovim/neovim.git
synced 2026-02-21 09:50:19 +10:00
fix(window): crash closing split if autocmds close other splits (#37233)
Problem: Crash when closing a split window if autocmds close other
split windows but there are still floating windows.
Solution: Bail out and give the window back its buffer.
This commit is contained in:
@@ -2650,7 +2650,9 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
|
||||
/// "action" can also be zero (do nothing).
|
||||
/// "abort_if_last" is passed to close_buffer(): abort closing if all other
|
||||
/// windows are closed.
|
||||
static void win_close_buffer(win_T *win, int action, bool abort_if_last)
|
||||
///
|
||||
/// @return whether close_buffer() decremented b_nwindows
|
||||
static bool win_close_buffer(win_T *win, int action, bool abort_if_last)
|
||||
FUNC_ATTR_NONNULL_ALL
|
||||
{
|
||||
// Free independent synblock before the buffer is freed.
|
||||
@@ -2665,12 +2667,13 @@ static void win_close_buffer(win_T *win, int action, bool abort_if_last)
|
||||
win->w_buffer->b_p_bl = false;
|
||||
}
|
||||
|
||||
bool retval = false;
|
||||
// Close the link to the buffer.
|
||||
if (win->w_buffer != NULL) {
|
||||
bufref_T bufref;
|
||||
set_bufref(&bufref, curbuf);
|
||||
win->w_locked = true;
|
||||
close_buffer(win, win->w_buffer, action, abort_if_last, true);
|
||||
retval = close_buffer(win, win->w_buffer, action, abort_if_last, true);
|
||||
if (win_valid_any_tab(win)) {
|
||||
win->w_locked = false;
|
||||
}
|
||||
@@ -2681,6 +2684,27 @@ static void win_close_buffer(win_T *win, int action, bool abort_if_last)
|
||||
curbuf = firstbuf;
|
||||
}
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// When failing to close a window after already calling close_buffer() on it,
|
||||
/// call this to make the window have a buffer again.
|
||||
///
|
||||
/// @param bufref reference to win->w_buffer before calling close_buffer()
|
||||
/// @param did_decrement whether close_buffer() decremented b_nwindows
|
||||
static void win_unclose_buffer(win_T *win, bufref_T *bufref, bool did_decrement)
|
||||
{
|
||||
if (win->w_buffer == NULL) {
|
||||
// If the buffer was removed from the window we have to give it any buffer.
|
||||
win->w_buffer = firstbuf;
|
||||
firstbuf->b_nwindows++;
|
||||
win_init_empty(win);
|
||||
} else if (did_decrement && win->w_buffer == bufref->br_buf && bufref_valid(bufref)) {
|
||||
// close_buffer() decremented the window count, but we're keeping the window.
|
||||
// As the window is still viewing the buffer, increment the count.
|
||||
win->w_buffer->b_nwindows++;
|
||||
}
|
||||
}
|
||||
|
||||
// Close window "win". Only works for the current tab page.
|
||||
@@ -2804,7 +2828,10 @@ int win_close(win_T *win, bool free_buf, bool force)
|
||||
return OK;
|
||||
}
|
||||
|
||||
win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true);
|
||||
bufref_T bufref;
|
||||
set_bufref(&bufref, win->w_buffer);
|
||||
|
||||
bool did_decrement = win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true);
|
||||
|
||||
if (win_valid(win) && win->w_buffer == NULL
|
||||
&& !win->w_floating && last_window(win)) {
|
||||
@@ -2825,8 +2852,17 @@ int win_close(win_T *win, bool free_buf, bool force)
|
||||
|
||||
// Autocommands may have closed the window already, or closed the only
|
||||
// other window or moved to another tab page.
|
||||
if (!win_valid(win) || (!win->w_floating && last_window(win))
|
||||
|| close_last_window_tabpage(win, free_buf, prev_curtab)) {
|
||||
if (!win_valid(win)) {
|
||||
return FAIL;
|
||||
}
|
||||
if (one_window(win, NULL) && (first_tabpage->tp_next == NULL || lastwin->w_floating)) {
|
||||
if (first_tabpage->tp_next != NULL) {
|
||||
emsg(e_floatonly);
|
||||
}
|
||||
win_unclose_buffer(win, &bufref, did_decrement);
|
||||
return FAIL;
|
||||
}
|
||||
if (close_last_window_tabpage(win, free_buf, prev_curtab)) {
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
@@ -3105,16 +3141,7 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
|
||||
|
||||
leave_open:
|
||||
if (win_valid_any_tab(win)) {
|
||||
if (win->w_buffer == NULL) {
|
||||
// If the buffer was removed from the window we have to give it any buffer.
|
||||
win->w_buffer = firstbuf;
|
||||
firstbuf->b_nwindows++;
|
||||
win_init_empty(win);
|
||||
} else if (did_decrement && win->w_buffer == bufref.br_buf && bufref_valid(&bufref)) {
|
||||
// close_buffer decremented the window count, but we're keeping the window.
|
||||
// As the window is still viewing the buffer, increment the count.
|
||||
win->w_buffer->b_nwindows++;
|
||||
}
|
||||
win_unclose_buffer(win, &bufref, did_decrement);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1005,7 +1005,7 @@ describe('float window', function()
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
pending('does not crash if BufUnload makes it the only non-float in tabpage', function()
|
||||
it('does not crash if BufUnload makes it the only non-float in tabpage', function()
|
||||
exec([[
|
||||
tabnew
|
||||
let g:buf = bufnr()
|
||||
@@ -1017,10 +1017,60 @@ describe('float window', function()
|
||||
\ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5})
|
||||
autocmd BufUnload * ++once exe g:buf .. 'bwipe!'
|
||||
]])
|
||||
command('close')
|
||||
eq('Vim(close):E5601: Cannot close window, only floating window would remain', pcall_err(command, 'close'))
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
describe('does not crash if WinClosed makes it the only non-float', function()
|
||||
before_each(function()
|
||||
exec([[
|
||||
let g:buf = bufnr()
|
||||
new
|
||||
setlocal bufhidden=wipe
|
||||
autocmd WinClosed * ++once exe g:buf .. 'bwipe!'
|
||||
]])
|
||||
end)
|
||||
|
||||
local opts = { relative = 'editor', row = 5, col = 5, width = 5, height = 5 }
|
||||
local floatwin
|
||||
|
||||
describe('and there is a float window with the same buffer', function()
|
||||
before_each(function()
|
||||
floatwin = api.nvim_open_win(0, false, opts)
|
||||
end)
|
||||
|
||||
it('with multiple tabpages', function()
|
||||
command('tabnew | tabprev')
|
||||
eq('Vim(close):E5601: Cannot close window, only floating window would remain', pcall_err(command, 'close'))
|
||||
api.nvim_win_close(floatwin, true)
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('with only one tabpage', function()
|
||||
command('close')
|
||||
api.nvim_win_close(floatwin, true)
|
||||
assert_alive()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('and there is a float with a different buffer', function()
|
||||
before_each(function()
|
||||
floatwin = api.nvim_open_win(api.nvim_create_buf(true, false), false, opts)
|
||||
end)
|
||||
|
||||
it('with multiple tabpages', function()
|
||||
command('tabnew | tabprev')
|
||||
eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close'))
|
||||
assert_alive()
|
||||
end)
|
||||
|
||||
it('with only one tabpage', function()
|
||||
eq('Vim(close):E855: Autocommands caused command to abort', pcall_err(command, 'close'))
|
||||
assert_alive()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
it('does not crash if WinClosed from floating window closes it', function()
|
||||
exec([[
|
||||
tabnew
|
||||
|
||||
Reference in New Issue
Block a user