fix(eval): winnr('$') counts non-current hidden/unfocusable windows #34207

Problem:  Non-visible/focusable windows are assigned a window number,
          whereas commands that use this window number skip over them.

Solution: Skip over non-visible/focusable windows when computing
          the window number, unless it is made the current window
          through the API in which case an identifiable window number
          is still useful. This also ensures it matches the window
          number of the window entered by `<winnr>wincmd w` since
          403fcacf.
This commit is contained in:
luukvbaal
2025-06-02 00:12:12 +02:00
committed by GitHub
parent 5cfbc35aa8
commit 981d4ba45e
10 changed files with 62 additions and 34 deletions

View File

@ -12124,7 +12124,8 @@ winline() *winline()*
winnr([{arg}]) *winnr()*
The result is a Number, which is the number of the current
window. The top window has number 1.
Returns zero for a popup window.
Returns zero for a hidden or non |focusable| window, unless
it is the current window.
The optional argument {arg} supports the following values:
$ the number of the last window (the window

View File

@ -68,10 +68,11 @@ The main Vim window can hold several split windows. There are also tab pages
If a window is focusable, it is part of the "navigation stack", that is,
editor commands such as :windo, |CTRL-W|, etc., will consider the window as
one that can be made the "current window". A non-focusable window will be
skipped by such commands (though it can be explicitly focused by
|nvim_set_current_win()|). Non-focusable windows are not listed by |:tabs|,
or counted by the default 'tabline'. Their buffer content is not included
in 'complete' "w" completion.
skipped by such commands as it isn't assigned a window number. It can be
explicitly focused by |nvim_set_current_win()|, because it is still
assigned a |window-ID|. If it is focused, it will also have a window number.
Non-focusable windows are not listed by |:tabs|, or counted by the default
'tabline'. Their buffer content is not included in 'complete' "w" completion.
Windows (especially floating windows) can have many other |api-win_config|
properties such as "hide" and "fixed" which also affect behavior.

View File

@ -11019,7 +11019,8 @@ function vim.fn.winline() end
--- The result is a Number, which is the number of the current
--- window. The top window has number 1.
--- Returns zero for a popup window.
--- Returns zero for a hidden or non |focusable| window, unless
--- it is the current window.
---
--- The optional argument {arg} supports the following values:
--- $ the number of the last window (the window

View File

@ -13347,7 +13347,8 @@ M.funcs = {
desc = [=[
The result is a Number, which is the number of the current
window. The top window has number 1.
Returns zero for a popup window.
Returns zero for a hidden or non |focusable| window, unless
it is the current window.
The optional argument {arg} supports the following values:
$ the number of the last window (the window

View File

@ -16,6 +16,7 @@
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/globals.h"
#include "nvim/macros_defs.h"
#include "nvim/memline.h"
@ -381,7 +382,7 @@ 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++;
winnr += win_has_winnr(wp);
if (wp->w_buffer == buf) {
found_buf = true;
winid = wp->handle;

View File

@ -40,6 +40,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)
{
return wp == curwin || (!wp->w_config.hide && wp->w_config.focusable);
}
static int win_getid(typval_T *argvars)
{
if (argvars[0].v_type == VAR_UNKNOWN) {
@ -72,7 +77,7 @@ static int win_getid(typval_T *argvars)
}
}
for (; wp != NULL; wp = wp->w_next) {
if (--winnr == 0) {
if ((winnr -= win_has_winnr(wp)) == 0) {
return wp->handle;
}
}
@ -120,9 +125,9 @@ static int win_id2win(typval_T *argvars)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->handle == id) {
return nr;
return (win_has_winnr(wp) ? nr : 0);
}
nr++;
nr += win_has_winnr(wp);
}
return 0;
}
@ -292,20 +297,24 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
semsg(_(e_invexpr2), arg);
nr = 0;
}
} else if (!win_has_winnr(twin)) {
nr = 0;
}
if (nr <= 0) {
return 0;
}
for (win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
wp != twin; wp = wp->w_next) {
if (wp == NULL) {
// didn't find it in this tabpage
nr = 0;
nr = 0;
win_T *wp = (tp == curtab) ? firstwin : tp->tp_firstwin;
for (; wp != NULL; wp = wp->w_next) {
nr += win_has_winnr(wp);
if (wp == twin) {
break;
}
nr++;
}
if (wp == NULL) {
nr = 0; // didn't find it in this tabpage
}
return nr;
}
@ -415,7 +424,7 @@ void f_getwininfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tabnr++;
int16_t winnr = 0;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
winnr++;
winnr += win_has_winnr(wp);
if (wparg != NULL && wp != wparg) {
continue;
}
@ -834,6 +843,9 @@ 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)) {
continue;
}
snprintf(buf, sizeof(buf), "%dresize %d|", winnr,
wp->w_height);
ga_concat(&ga, buf);

View File

@ -7468,7 +7468,7 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr)
*tabnr = tnum;
return;
}
wnum++;
wnum += win_has_winnr(wp);
}
tnum++;
wnum = 1;

View File

@ -377,7 +377,7 @@ describe('autocmd', function()
-- Also check with win_splitmove().
exec_lua [[
vim._with({buf = _G.buf}, function()
vim.fn.win_splitmove(vim.fn.winnr(), vim.fn.win_getid(1))
vim.fn.win_splitmove(vim.fn.win_getid(), vim.fn.win_getid(1))
end)
]]
screen:expect_unchanged()

View File

@ -761,7 +761,7 @@ describe('startup', function()
\ 'row': 3,
\ 'col': 3
\ }
autocmd WinEnter * call nvim_open_win(bufnr, v:false, config)]]
autocmd WinEnter * let g:float_win = nvim_open_win(bufnr, v:false, config)]]
)
finally(function()
os.remove('Xdiff.vim')
@ -769,7 +769,7 @@ describe('startup', function()
clear { args = { '-u', 'Xdiff.vim', '-d', 'Xdiff.vim', 'Xdiff.vim' } }
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(1) }))
eq(true, api.nvim_get_option_value('diff', { win = fn.win_getid(2) }))
local float_win = fn.win_getid(3)
local float_win = eval('g:float_win')
eq('editor', api.nvim_win_get_config(float_win).relative)
eq(false, api.nvim_get_option_value('diff', { win = float_win }))
end)

View File

@ -1090,6 +1090,17 @@ 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 })
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))
-- Unless it is the current window.
api.nvim_set_current_win(win)
eq({ 3, 3 }, { fn.winnr(), fn.win_id2win(win) })
end)
local function with_ext_multigrid(multigrid)
local screen, attrs
before_each(function()
@ -6417,32 +6428,32 @@ describe('float window', function()
api.nvim_open_win(0, false, { relative = 'editor', width = 1, height = 1, row = 0, col = 0, focusable = true })
api.nvim_open_win(0, false, { relative = 'editor', width = 1, height = 1, row = 0, col = 0, focusable = false })
local nr_focusable = {}
for nr = 1, fn.winnr('$') do
table.insert(nr_focusable, api.nvim_win_get_config(fn.win_getid(nr)).focusable)
for _, winid in ipairs(api.nvim_tabpage_list_wins(0)) do
table.insert(nr_focusable, api.nvim_win_get_config(winid).focusable)
end
eq({ true, false, true, false, false, true, false }, nr_focusable)
command('1wincmd w')
eq(1, fn.winnr())
eq({ 1, 1000 }, { fn.winnr(), fn.win_getid() })
command('2wincmd w')
eq(3, fn.winnr())
eq({ 2, 1005 }, { fn.winnr(), fn.win_getid() })
command('3wincmd w')
eq(3, fn.winnr())
eq({ 2, 1005 }, { fn.winnr(), fn.win_getid() })
command('4wincmd w')
eq(6, fn.winnr())
eq({ 3, 1002 }, { fn.winnr(), fn.win_getid() })
command('5wincmd w')
eq(6, fn.winnr())
eq({ 3, 1002 }, { fn.winnr(), fn.win_getid() })
command('6wincmd w')
eq(6, fn.winnr())
eq({ 3, 1002 }, { fn.winnr(), fn.win_getid() })
command('7wincmd w')
eq(6, fn.winnr())
eq({ 3, 1002 }, { fn.winnr(), fn.win_getid() })
feed('1<c-w>w')
eq(1, fn.winnr())
eq({ 1, 1000 }, { fn.winnr(), fn.win_getid() })
feed('2<c-w>w')
eq(3, fn.winnr())
eq({ 2, 1005 }, { fn.winnr(), fn.win_getid() })
feed('999<c-w>w')
eq(6, fn.winnr())
eq({ 3, 1002 }, { fn.winnr(), fn.win_getid() })
end)
it('W', function()