neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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:
Msrc/nvim/eval/buffer.c | 4++--
Msrc/nvim/eval/window.c | 25++++++++++++++-----------
Msrc/nvim/window.c | 8+++++---
Mtest/functional/ui/float_spec.lua | 45+++++++++++++++++++++++++++++++++++++++++++--
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()