diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index 9328c8d064..297b63e553 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -18,6 +18,8 @@ #include "nvim/drawscreen.h" #include "nvim/errors.h" #include "nvim/eval/window.h" +#include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/globals.h" #include "nvim/highlight_group.h" #include "nvim/macros_defs.h" @@ -391,6 +393,20 @@ static bool win_can_move_tp(win_T *wp, tabpage_T *tp, Error *err) api_set_error(err, kErrorTypeException, "Cannot move last non-floating window"); return false; } + // Like closing, moving windows between tabpages makes win_valid return false. Helpful when e.g: + // walking the window list, as w_next/w_prev can unexpectedly refer to windows in another tabpage! + // Check related locks, in case they were set to avoid checking win_valid. + if (win_locked(wp)) { + api_set_error(err, kErrorTypeException, "Cannot move window to another tabpage whilst in use"); + return false; + } + if (window_layout_locked_err(CMD_SIZE, err)) { + return false; // error already set + } + if (textlock || expr_map_locked()) { + api_set_error(err, kErrorTypeException, "%s", e_textlock); + return false; + } if (is_aucmd_win(wp)) { api_set_error(err, kErrorTypeException, "Cannot move autocmd window to another tabpage"); return false; diff --git a/src/nvim/window.c b/src/nvim/window.c index ca1689803f..da4f74fc76 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -28,7 +28,6 @@ #include "nvim/eval/window.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" -#include "nvim/ex_cmds_defs.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/ex_getln.h" @@ -143,12 +142,26 @@ bool frames_locked(void) /// error message. When closing window(s) and the command isn't easy to know, /// passing CMD_SIZE will also work. bool window_layout_locked(cmdidx_T cmd) +{ + Error err = ERROR_INIT; + const bool locked = window_layout_locked_err(cmd, &err); + if (ERROR_SET(&err)) { + emsg(_(err.msg)); + api_clear_error(&err); + } + return locked; +} + +/// Like `window_layout_locked`, but set `err` to the (untranslated) error message when locked. +/// @see window_layout_locked +bool window_layout_locked_err(cmdidx_T cmd, Error *err) { if (split_disallowed > 0 || close_disallowed > 0) { if (close_disallowed == 0 && cmd == CMD_tabnew) { - emsg(_(e_cannot_split_window_when_closing_buffer)); + api_set_error(err, kErrorTypeException, "%s", e_cannot_split_window_when_closing_buffer); } else { - emsg(_(e_not_allowed_to_change_window_layout_in_this_autocmd)); + api_set_error(err, kErrorTypeException, "%s", + e_not_allowed_to_change_window_layout_in_this_autocmd); } return true; } diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 9937c5270b..f7a50bfa02 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -3168,7 +3168,6 @@ describe('API/win', function() ) command('quit!') - -- Can't switch away from window before moving it to a different tabpage during textlock. exec(([[ new call setline(1, 'foo') @@ -3180,6 +3179,41 @@ describe('API/win', function() pcall_err(command, 'normal! ==') ) eq(cur_win, api.nvim_get_current_win()) + exec(([[ + wincmd p + call setline(1, 'bar') + setlocal indentexpr=nvim_win_set_config(win_getid(winnr('#')),#{split:'left',win:%d}) + ]]):format(t2_win)) + neq(cur_win, api.nvim_get_current_win()) + matches( + 'E565: Not allowed to change text or change window$', + pcall_err(command, 'normal! ==') + ) + -- expr_map_lock + exec(([[ + nnoremap @ nvim_win_set_config(win_getid(winnr('#')),#{split:'left',win:%d}) + ]]):format(t2_win)) + neq(cur_win, api.nvim_get_current_win()) + matches( + 'E565: Not allowed to change text or change window$', + pcall_err(fn.feedkeys, '@', 'x') + ) + + exec(([[ + wincmd p + autocmd WinNewPre * ++once call nvim_win_set_config(0, #{relative:'editor', win:%d, row:0, col:0, width:1, height:1}) + ]]):format(t2_win)) + matches( + 'E1312: Not allowed to change the window layout in this autocmd$', + pcall_err(command, 'split') + ) + eq(cur_win, api.nvim_get_current_win()) -- :split didn't enter new window due to error + + exec(([[ + autocmd WinLeave * ++once call nvim_win_set_config(0, #{relative:'editor', win:%d, row:0, col:0, width:1, height:1}) + ]]):format(t2_win)) + matches('Cannot move window to another tabpage whilst in use$', pcall_err(command, 'quit')) + eq(cur_win, api.nvim_get_current_win()) -- :quit didn't close window due to error end) it('updates statusline when moving bottom split', function()