commit 981d4ba45ed1169b305d8b96f04062ae997ddd43
parent 5cfbc35aa8381f3e199be248fbb94b05c16f82ff
Author: luukvbaal <luukvbaal@gmail.com>
Date: Mon, 2 Jun 2025 00:12:12 +0200
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.
Diffstat:
10 files changed, 62 insertions(+), 34 deletions(-)
diff --git a/runtime/doc/vimfn.txt b/runtime/doc/vimfn.txt
@@ -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
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
@@ -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.
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
@@ -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
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
@@ -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
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
@@ -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;
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
@@ -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);
diff --git a/src/nvim/window.c b/src/nvim/window.c
@@ -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;
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
@@ -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()
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
@@ -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)
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
@@ -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()