commit 1e1619de830ec64ec887911b444753486db63476
parent 6a409e05071735c4b1c0272d51d8871b58384eee
Author: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Date: Tue, 26 Aug 2025 21:13:05 +0100
fix(eval): winnrs of unfocusable/hidden windows #35474
Problem: various functions may return incorrect window numbers for unfocusable
or hidden windows.
Solution: fix the checks. Make sure current windows in non-current tabpages have
a window number.
Fixes #35453
Diffstat:
4 files changed, 64 insertions(+), 18 deletions(-)
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
@@ -380,8 +380,8 @@ static void buf_win_common(typval_T *argvars, typval_T *rettv, bool get_nr)
int winid;
bool found_buf = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- winnr += win_has_winnr(wp);
- if (wp->w_buffer == buf) {
+ winnr += win_has_winnr(wp, curtab);
+ if (wp->w_buffer == buf && (!get_nr || win_has_winnr(wp, curtab))) {
found_buf = true;
winid = wp->handle;
break;
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
@@ -38,9 +38,11 @@ static const char *e_invalwindow = N_("E957: Invalid window number");
static const char e_cannot_resize_window_in_another_tab_page[]
= N_("E1308: Cannot resize a window in another tab page");
-bool win_has_winnr(win_T *wp)
+bool win_has_winnr(win_T *wp, tabpage_T *tp)
+ FUNC_ATTR_NONNULL_ALL
{
- return wp == curwin || (!wp->w_config.hide && wp->w_config.focusable);
+ return (wp == (tp == curtab ? curwin : tp->tp_curwin))
+ || (!wp->w_config.hide && wp->w_config.focusable);
}
static int win_getid(typval_T *argvars)
@@ -54,10 +56,11 @@ static int win_getid(typval_T *argvars)
return 0;
}
+ tabpage_T *tp = NULL;
if (argvars[1].v_type == VAR_UNKNOWN) {
+ tp = curtab;
wp = firstwin;
} else {
- tabpage_T *tp = NULL;
int tabnr = (int)tv_get_number(&argvars[1]);
FOR_ALL_TABS(tp2) {
if (--tabnr == 0) {
@@ -75,7 +78,7 @@ static int win_getid(typval_T *argvars)
}
}
for (; wp != NULL; wp = wp->w_next) {
- if ((winnr -= win_has_winnr(wp)) == 0) {
+ if ((winnr -= win_has_winnr(wp, tp)) == 0) {
return wp->handle;
}
}
@@ -123,9 +126,9 @@ static int win_id2win(typval_T *argvars)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->handle == id) {
- return (win_has_winnr(wp) ? nr : 0);
+ return (win_has_winnr(wp, curtab) ? nr : 0);
}
- nr += win_has_winnr(wp);
+ nr += win_has_winnr(wp, curtab);
}
return 0;
}
@@ -296,7 +299,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
semsg(_(e_invexpr2), arg);
nr = 0;
}
- } else if (!win_has_winnr(twin)) {
+ } else if (!win_has_winnr(twin, tp)) {
nr = 0;
}
@@ -307,7 +310,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
nr = 0;
win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
for (; wp != NULL; wp = wp->w_next) {
- nr += win_has_winnr(wp);
+ nr += win_has_winnr(wp, tp);
if (wp == twin) {
break;
}
@@ -423,11 +426,11 @@ void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tabnr++;
int16_t winnr = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- winnr += win_has_winnr(wp);
+ winnr += win_has_winnr(wp, tp);
if (wparg != NULL && wp != wparg) {
continue;
}
- dict_T *const d = get_win_info(wp, tabnr, winnr);
+ dict_T *const d = get_win_info(wp, tabnr, win_has_winnr(wp, tp) ? winnr : 0);
tv_list_append_dict(rettv->vval.v_list, d);
if (wparg != NULL) {
// found information about a specific window
@@ -842,7 +845,7 @@ void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
for (int i = 0; i < 2; i++) {
int winnr = 1;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (!win_has_winnr(wp)) {
+ if (!win_has_winnr(wp, curtab)) {
continue;
}
snprintf(buf, sizeof(buf), "%dresize %d|", winnr,
diff --git a/src/nvim/window.c b/src/nvim/window.c
@@ -7501,11 +7501,13 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr)
FOR_ALL_TABS(tp) {
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
if (wp->handle == id) {
- *winnr = wnum;
- *tabnr = tnum;
+ if (win_has_winnr(wp, tp)) {
+ *winnr = wnum;
+ *tabnr = tnum;
+ }
return;
}
- wnum += win_has_winnr(wp);
+ wnum += win_has_winnr(wp, tp);
}
tnum++;
wnum = 1;
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
@@ -9,6 +9,7 @@ local command, feed_command = n.command, n.feed_command
local eval = n.eval
local eq = t.eq
local neq = t.neq
+local matches = t.matches
local expect = n.expect
local exec = n.exec
local exec_lua = n.exec_lua
@@ -899,14 +900,54 @@ describe('float window', function()
end)
it('non-visible/focusable are not assigned a window number', function()
- local win = api.nvim_open_win(0, false, { relative = 'editor', width = 2, height = 2, row = 2, col = 2, focusable = false })
+ command('tabnew')
+ local tp = api.nvim_get_current_tabpage()
+ local split_win = api.nvim_get_current_win()
+ local float_buf = api.nvim_create_buf(true, true)
+ local win = api.nvim_open_win(float_buf, false, { relative = 'editor', width = 2, height = 2, row = 2, col = 2, focusable = false })
api.nvim_open_win(0, false, { relative = 'editor', width = 2, height = 2, row = 2, col = 2, hide = true })
api.nvim_open_win(0, false, { relative = 'editor', width = 2, height = 2, row = 2, col = 2 })
+
eq(2, fn.winnr('$'))
eq(0, fn.win_id2win(win))
+ eq(0, fn.getwininfo(win)[1].winnr)
+ eq({ 0, 0 }, fn.win_id2tabwin(win))
+ eq(2, fn.tabpagewinnr(2, '$'))
+ eq(0, fn.win_getid(3))
+ eq(0, fn.win_getid(3, 2))
+ eq(-1, fn.bufwinnr(float_buf))
+ eq(win, fn.bufwinid(float_buf)) -- bufwinid unaffected.
+ eq(nil, fn.winrestcmd():match('3resize'))
+
-- Unless it is the current window.
api.nvim_set_current_win(win)
- eq({ 3, 3 }, { fn.winnr(), fn.win_id2win(win) })
+ eq(3, fn.winnr('$'))
+ eq(3, fn.winnr())
+ eq(3, fn.win_id2win(win))
+ eq(3, fn.getwininfo(win)[1].winnr)
+ eq({ 2, 3 }, fn.win_id2tabwin(win))
+ eq(3, fn.tabpagewinnr(2, '$'))
+ eq(3, fn.tabpagewinnr(2))
+ eq(win, fn.win_getid(3))
+ eq(win, fn.win_getid(3, 2))
+ eq(3, fn.bufwinnr(float_buf))
+ matches('3resize', fn.winrestcmd())
+
+ -- When switching tabpages it should still have a winnr, as it's current in the other tabpage.
+ command('tabfirst')
+ eq({ 2, 3 }, fn.win_id2tabwin(win))
+ eq(3, fn.getwininfo(win)[1].winnr)
+ eq(win, fn.win_getid(3, 2))
+ eq(3, fn.tabpagewinnr(2, '$'))
+ eq(3, fn.tabpagewinnr(2))
+
+ -- ...but not if it's non-current in that tabpage.
+ api.nvim_tabpage_set_win(tp, split_win)
+ eq({ 0, 0 }, fn.win_id2tabwin(win))
+ eq(0, fn.getwininfo(win)[1].winnr)
+ eq(0, fn.win_getid(3, 2))
+ eq(2, fn.tabpagewinnr(2, '$'))
+ eq(1, fn.tabpagewinnr(2))
end)
it('no crash for unallocated relative window grid', function()