From bcb70eeac48040fd6d6bfc20cf7fb6f41374a67c Mon Sep 17 00:00:00 2001 From: Sean Dewar Date: Sun, 4 Feb 2024 00:42:36 +0000 Subject: [PATCH] fix(api): win_set_config autocmds crash when moving win to other tabpage Problem: win_enter autocommands can close new_curwin, crashing if it was the last window in its tabpage after removing win, or can close parent, crashing when attempting to split it later. Solution: remove win first, check that parent is valid after win_enter. NOTE: This isn't actually quite right, as this means win is not in the window list or even has a frame when triggering enter autocommands (so it's not considered valid in the tabpage). This is addressed in later commits. --- src/nvim/api/win_config.c | 7 ++++++- test/functional/api/window_spec.lua | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 3cc520dc78..bb1117b3fe 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -478,12 +478,17 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) int dir; new_curwin = winframe_remove(win, &dir, win_tp == curtab ? NULL : win_tp); } + win_remove(win, win_tp == curtab ? NULL : win_tp); // move to neighboring window if we're moving the current window to a new tabpage if (curwin == win && parent != NULL && new_curwin != NULL && win_tp != win_find_tabpage(parent)) { win_enter(new_curwin, true); + if (!win_valid_any_tab(parent)) { + // win_enter autocommands closed the `parent` to split from. + api_set_error(err, kErrorTypeException, "Window to split was closed"); + return; + } } - win_remove(win, win_tp == curtab ? NULL : win_tp); } else { win_remove(win, win_tp == curtab ? NULL : win_tp); ui_comp_remove_grid(&win->w_grid_alloc); diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 097a546ef2..a812d502eb 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1663,6 +1663,27 @@ describe('API/win', function() }, }, fn.winlayout()) end) + + it('closing new curwin when moving window to other tabpage works', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + local t = api.nvim_get_current_tabpage() + command('tabfirst | autocmd WinEnter * ++once quit') + api.nvim_win_set_config(0, { win = w, split = 'left' }) + -- New tabpage is now the only one, as WinEnter closed the new curwin in the original. + eq(t, api.nvim_get_current_tabpage()) + eq({ t }, api.nvim_list_tabpages()) + end) + + it('closing split parent when moving window to other tabpage aborts', function() + command('split | tabnew') + local w = api.nvim_get_current_win() + command('tabfirst | autocmd WinEnter * call nvim_win_close(' .. w .. ', 1)') + eq( + 'Window to split was closed', + pcall_err(api.nvim_win_set_config, 0, { win = w, split = 'left' }) + ) + end) end) describe('get_config', function()