diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 040abb1e3f..56a3f1cf23 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -146,7 +146,11 @@ void nvim_tabpage_set_win(Tabpage tabpage, Window win, Error *err) } if (tp == curtab) { - win_enter(wp, true); + try_start(); + win_goto(wp); + if (!try_end(err) && curwin != wp) { + api_set_error(err, kErrorTypeException, "Failed to switch to window %d", win); + } } else if (tp->tp_curwin != wp) { tp->tp_prevwin = tp->tp_curwin; tp->tp_curwin = wp; diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c index bb28000719..dab1e4e80b 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -451,6 +451,12 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err) if (!check_split_disallowed_err(win, err)) { return; // error already set } + // Can't move the cmdwin or its old curwin to a different tabpage. + if ((win == cmdwin_win || win == cmdwin_old_curwin) && parent != NULL + && win_find_tabpage(parent) != win_tp) { + api_set_error(err, kErrorTypeException, "%s", e_cmdwin); + return; + } bool to_split_ok = false; // If we are moving curwin to another tabpage, switch windows *before* we remove it from the diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 10d8c7ac90..3ddff8aa5a 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -55,6 +55,19 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err) api_set_error(err, kErrorTypeException, "Cannot change window from different tabpage into float"); return NULL; + } else if (cmdwin_win != NULL && !cmdwin_win->w_floating) { + // cmdwin can't become the only non-float. Check for others. + bool other_nonfloat = false; + for (win_T *wp2 = firstwin; wp2 != NULL && !wp2->w_floating; wp2 = wp2->w_next) { + if (wp2 != wp && wp2 != cmdwin_win) { + other_nonfloat = true; + break; + } + } + if (!other_nonfloat) { + api_set_error(err, kErrorTypeException, "%s", e_cmdwin); + return NULL; + } } int dir; winframe_remove(wp, &dir, NULL, NULL); diff --git a/test/functional/api/tabpage_spec.lua b/test/functional/api/tabpage_spec.lua index 36955c4ace..f7e6eed047 100644 --- a/test/functional/api/tabpage_spec.lua +++ b/test/functional/api/tabpage_spec.lua @@ -1,5 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, ok = helpers.clear, helpers.eq, helpers.ok +local exec = helpers.exec +local feed = helpers.feed local api = helpers.api local fn = helpers.fn local request = helpers.request @@ -86,6 +88,30 @@ describe('api/tabpage', function() pcall_err(api.nvim_tabpage_set_win, tab1, win3) ) end) + + it('does not switch window when textlocked or in the cmdwin', function() + local target_win = api.nvim_get_current_win() + feed('q:') + local cur_win = api.nvim_get_current_win() + eq( + 'Vim:E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err(api.nvim_tabpage_set_win, 0, target_win) + ) + eq(cur_win, api.nvim_get_current_win()) + command('quit!') + + exec(([[ + new + call setline(1, 'foo') + setlocal debug=throw indentexpr=nvim_tabpage_set_win(0,%d) + ]]):format(target_win)) + cur_win = api.nvim_get_current_win() + eq( + 'Vim(normal):E5555: API call: Vim:E565: Not allowed to change text or change window', + pcall_err(command, 'normal! ==') + ) + eq(cur_win, api.nvim_get_current_win()) + end) end) describe('{get,set,del}_var', function() diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index abe1ca9344..ae8a519f11 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -2297,29 +2297,82 @@ describe('API/win', function() try_move_t2_wins_to_t1() end) - it('does not switch window when textlocked or in the cmdwin', function() + it('handles cmdwin and textlock restrictions', function() command('tabnew') + local t2 = api.nvim_get_current_tabpage() local t2_win = api.nvim_get_current_win() command('tabfirst') + local t1_move_win = api.nvim_get_current_win() + command('split') + + -- Can't move the cmdwin, or its old curwin to a different tabpage. + local old_curwin = api.nvim_get_current_win() feed('q:') - local cur_win = api.nvim_get_current_win() eq( - 'Failed to switch away from window ' .. cur_win, + 'E11: Invalid in command-line window; executes, CTRL-C quits', pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win }) ) eq( 'E11: Invalid in command-line window; executes, CTRL-C quits', - api.nvim_get_vvar('errmsg') + pcall_err(api.nvim_win_set_config, old_curwin, { split = 'left', win = t2_win }) ) - eq(cur_win, api.nvim_get_current_win()) + -- But we can move other windows. + api.nvim_win_set_config(t1_move_win, { split = 'left', win = t2_win }) + eq(t2, api.nvim_win_get_tabpage(t1_move_win)) command('quit!') + -- Can't configure windows such that the cmdwin would become the only non-float. + command('only!') + feed('q:') + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- old_curwin is now no longer the only other non-float, so we can make it floating now. + local t1_new_win = api.nvim_open_win( + api.nvim_create_buf(true, true), + false, + { split = 'left', win = old_curwin } + ) + api.nvim_win_set_config( + old_curwin, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + eq('editor', api.nvim_win_get_config(old_curwin).relative) + -- ...which means we shouldn't be able to also make the new window floating too! + eq( + 'E11: Invalid in command-line window; executes, CTRL-C quits', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + -- Nothing ought to stop us from making the cmdwin itself floating, though... + api.nvim_win_set_config(0, { relative = 'editor', row = 0, col = 0, width = 5, height = 5 }) + eq('editor', api.nvim_win_get_config(0).relative) + -- We can't make our new window from before floating too, as it's now the only non-float. + eq( + 'Cannot change last window into float', + pcall_err( + api.nvim_win_set_config, + t1_new_win, + { relative = 'editor', row = 0, col = 0, width = 5, height = 5 } + ) + ) + command('quit!') + + -- Can't switch away from window before moving it to a different tabpage during textlock. exec(([[ new call setline(1, 'foo') setlocal debug=throw indentexpr=nvim_win_set_config(0,#{split:'left',win:%d}) ]]):format(t2_win)) - cur_win = api.nvim_get_current_win() + local cur_win = api.nvim_get_current_win() matches( 'E565: Not allowed to change text or change window$', pcall_err(command, 'normal! ==')