aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dewar <6256228+seandewar@users.noreply.github.com>2024-02-27 13:25:44 +0000
committerSean Dewar <6256228+seandewar@users.noreply.github.com>2024-03-08 23:24:06 +0000
commite7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e (patch)
treeef0a5da5f8708ddd036534367a52975d75b1c37f
parentd942c2b9432d81e4b509519bd48fa886e37e9ca8 (diff)
downloadrneovim-e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e.tar.gz
rneovim-e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e.tar.bz2
rneovim-e7c262f5553c1c6e1de95bcbdc8cfe7cc9d5e55e.zip
fix(api): patch some cmdwin/textlock holes
Problem: there are new ways to escape textlock or break the cmdwin in nvim_win_set_config and nvim_tabpage_set_win. Solution: fix them. Use win_goto to check it in nvim_tabpage_set_win and use the try_start/end pattern like with similar functions such as nvim_set_current_win (which uses the existing msg_list, if set). Careful not to use `wp->handle` when printing the window ID in the error message for nvim_tabpage_set_win, as win_goto autocommands may have freed the window. On a related note, I have a feeling some API functions ought to be checking curbuf_locked...
-rw-r--r--src/nvim/api/tabpage.c6
-rw-r--r--src/nvim/api/win_config.c6
-rw-r--r--src/nvim/winfloat.c13
-rw-r--r--test/functional/api/tabpage_spec.lua26
-rw-r--r--test/functional/api/window_spec.lua65
5 files changed, 109 insertions, 7 deletions
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; <CR> 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; <CR> executes, CTRL-C quits',
pcall_err(api.nvim_win_set_config, 0, { split = 'left', win = t2_win })
)
eq(
'E11: Invalid in command-line window; <CR> 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; <CR> 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; <CR> 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! ==')