mirror of
https://github.com/neovim/neovim.git
synced 2026-09-03 04:43:48 +00:00
fix(tabpage): focusing a tab while closing it, fails assert #41475
Problem: If an autocommand handler focuses the tab page we're closing during a `:tabonly` (with some conditions), we hit an assert failure in `win_close_othertab()`. For this to occur, we need: - `nvim_buf_delete()` to trigger `close_windows()` (as is done in `cmdwin.lua`'s `_cleanup()`) - `close_windows()` then calls `win_close_othertab()`, removing the window (`win_free_mem()`) - then in the caller, `tabpage_close_other()`, the loop continues (we don't detect `tp_lastwin == wp` since we've unlinked the window) - the loop assumes that `curtab != tp` but we've refocused `curtab` so the `ex_win_close()` call passes `tp` as `curtab`, causing the assert to fail Solution: Detect the focus of `curtab` and abort closing the tab.
This commit is contained in:
@@ -5165,9 +5165,11 @@ static void ex_pclose(exarg_T *eap)
|
||||
/// Close window "win" and take care of handling closing the last window for a
|
||||
/// modified buffer.
|
||||
///
|
||||
/// @param tp NULL or the tab page "win" is in
|
||||
/// @param tp NULL or the tab page "win" is in, if different from "curtab"
|
||||
void ex_win_close(int forceit, win_T *win, tabpage_T *tp)
|
||||
{
|
||||
assert(tp == NULL || tp != curtab);
|
||||
|
||||
// Never close the autocommand window.
|
||||
if (is_ctx_win(win)) {
|
||||
emsg(_(e_autocmd_close));
|
||||
@@ -5276,6 +5278,8 @@ static void ex_tabonly(exarg_T *eap)
|
||||
/// Close the current tab page.
|
||||
void tabpage_close(int forceit)
|
||||
{
|
||||
int done = 0;
|
||||
|
||||
if (window_layout_locked(CMD_tabclose)) {
|
||||
return;
|
||||
}
|
||||
@@ -5286,9 +5290,13 @@ void tabpage_close(int forceit)
|
||||
|
||||
// First close all the windows but the current one. If that worked then
|
||||
// close the last window in this tab, that will close it.
|
||||
while (curwin->w_floating) {
|
||||
while (curwin->w_floating && ++done < 1000) {
|
||||
ex_win_close(forceit, curwin, NULL);
|
||||
}
|
||||
if (done == 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ONE_WINDOW) {
|
||||
close_others(true, forceit, true);
|
||||
}
|
||||
@@ -5311,6 +5319,8 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
|
||||
int done = 0;
|
||||
char prev_idx[NUMBUFLEN];
|
||||
|
||||
assert(tp != curtab);
|
||||
|
||||
if (window_layout_locked(CMD_SIZE)) {
|
||||
return;
|
||||
}
|
||||
@@ -5323,12 +5333,20 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
|
||||
while (++done < 1000) {
|
||||
snprintf(prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
|
||||
win_T *wp = tp->tp_lastwin;
|
||||
|
||||
// Autocommands (including TabClosedPre above) may change the current tab page,
|
||||
// abort a `:tabonly` (etc) if we're now on the tab we're trying to close
|
||||
if (tp == curtab) {
|
||||
done = 1000;
|
||||
break;
|
||||
}
|
||||
ex_win_close(forceit, wp, tp);
|
||||
|
||||
// Autocommands may delete the tab page under our fingers.
|
||||
if (!valid_tabpage(tp)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// We may fail to close a window with a modified buffer.
|
||||
if (tp->tp_lastwin == wp) {
|
||||
done = 1000;
|
||||
|
||||
@@ -10,6 +10,7 @@ local neq = t.neq
|
||||
local feed = n.feed
|
||||
local eval = n.eval
|
||||
local exec = n.exec
|
||||
local exec_lua = n.exec_lua
|
||||
local fn = n.fn
|
||||
local api = n.api
|
||||
local curwin = n.api.nvim_get_current_win
|
||||
@@ -173,4 +174,28 @@ describe('tabpage', function()
|
||||
quit
|
||||
]])
|
||||
end)
|
||||
|
||||
it(
|
||||
'no crash when :tabclose/:tabonly and WinClosed autocmd wipes buf and switches to closing tab',
|
||||
function()
|
||||
exec_lua(function()
|
||||
local win1 = vim.api.nvim_get_current_win()
|
||||
vim.cmd('botright new')
|
||||
local win2 = vim.api.nvim_get_current_win()
|
||||
local buf2 = vim.api.nvim_get_current_buf()
|
||||
vim.cmd('tabedit')
|
||||
vim.api.nvim_create_autocmd('WinClosed', {
|
||||
pattern = tostring(win2),
|
||||
callback = function()
|
||||
-- Wipe the closing window's buffer and switch back to the original window,
|
||||
-- so `curtab == tp` in tabpage_close_other's loop.
|
||||
vim.api.nvim_buf_delete(buf2, { force = true })
|
||||
vim.api.nvim_set_current_win(win1)
|
||||
end,
|
||||
})
|
||||
vim.cmd('tabonly')
|
||||
end)
|
||||
assert_alive()
|
||||
end
|
||||
)
|
||||
end)
|
||||
|
||||
Reference in New Issue
Block a user