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
This commit is contained in:
Sean Dewar
2025-08-26 21:13:05 +01:00
committed by GitHub
parent 6a409e0507
commit 1e1619de83
4 changed files with 64 additions and 18 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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) {
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;

View File

@@ -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()