mirror of
https://github.com/neovim/neovim.git
synced 2026-04-28 10:14:06 +00: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).
|
/// "action" can also be zero (do nothing).
|
||||||
/// "abort_if_last" is passed to close_buffer(): abort closing if all other
|
/// "abort_if_last" is passed to close_buffer(): abort closing if all other
|
||||||
/// windows are closed.
|
/// 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
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
// Free independent synblock before the buffer is freed.
|
// 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;
|
win->w_buffer->b_p_bl = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool retval = false;
|
||||||
// Close the link to the buffer.
|
// Close the link to the buffer.
|
||||||
if (win->w_buffer != NULL) {
|
if (win->w_buffer != NULL) {
|
||||||
bufref_T bufref;
|
bufref_T bufref;
|
||||||
set_bufref(&bufref, curbuf);
|
set_bufref(&bufref, curbuf);
|
||||||
win->w_locked = true;
|
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)) {
|
if (win_valid_any_tab(win)) {
|
||||||
win->w_locked = false;
|
win->w_locked = false;
|
||||||
}
|
}
|
||||||
@@ -2681,6 +2684,27 @@ static void win_close_buffer(win_T *win, int action, bool abort_if_last)
|
|||||||
curbuf = firstbuf;
|
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.
|
// 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;
|
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
|
if (win_valid(win) && win->w_buffer == NULL
|
||||||
&& !win->w_floating && last_window(win)) {
|
&& !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
|
// Autocommands may have closed the window already, or closed the only
|
||||||
// other window or moved to another tab page.
|
// other window or moved to another tab page.
|
||||||
if (!win_valid(win) || (!win->w_floating && last_window(win))
|
if (!win_valid(win)) {
|
||||||
|| close_last_window_tabpage(win, free_buf, prev_curtab)) {
|
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;
|
return FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3105,16 +3141,7 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
|
|||||||
|
|
||||||
leave_open:
|
leave_open:
|
||||||
if (win_valid_any_tab(win)) {
|
if (win_valid_any_tab(win)) {
|
||||||
if (win->w_buffer == NULL) {
|
win_unclose_buffer(win, &bufref, did_decrement);
|
||||||
// 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++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1005,7 +1005,7 @@ describe('float window', function()
|
|||||||
assert_alive()
|
assert_alive()
|
||||||
end)
|
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([[
|
exec([[
|
||||||
tabnew
|
tabnew
|
||||||
let g:buf = bufnr()
|
let g:buf = bufnr()
|
||||||
@@ -1017,10 +1017,60 @@ describe('float window', function()
|
|||||||
\ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5})
|
\ #{relative: 'editor', row: 5, col: 5, width: 5, height: 5})
|
||||||
autocmd BufUnload * ++once exe g:buf .. 'bwipe!'
|
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()
|
assert_alive()
|
||||||
end)
|
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()
|
it('does not crash if WinClosed from floating window closes it', function()
|
||||||
exec([[
|
exec([[
|
||||||
tabnew
|
tabnew
|
||||||
|
|||||||
Reference in New Issue
Block a user