mirror of
https://github.com/neovim/neovim.git
synced 2026-08-26 09:01:57 +00:00
fix(ui2): :$q should not target hidden windows #40992
Similar to #36123, the ui2 windows confuse `:$q`, which doesn't close the last window on the tab, but targets a hidden ui2 window. The problem seems to be a combination of using `FOR_ALL_WINDOWS_IN_TAB()`, which includes hidden windows, and then counting the windows without factoring in `win_has_winnr()`. There are examples of using `win_has_winnr()`, such as: - [`eval/buffer.c`'s `buf_win_common()`](4a5062cda6/src/nvim/eval/buffer.c (L474-L475)) - [`eval/window.c`'s `f_getwininfo()`](4a5062cda6/src/nvim/eval/window.c (L130-L135)) - [`window.c`'s `win_get_tabwin()`](4a5062cda6/src/nvim/window.c (L7860-L7869)) But possibly problematic ones: - `ex_docmd.c`'s: - [`invalid_range()`](4a5062cda6/src/nvim/ex_docmd.c (L3891-L3893)) - ^ should this include hidden windows? Can a user address them by number? `win_has_winnr()` suggests not - [`ex_close()`](4a5062cda6/src/nvim/ex_docmd.c (L5233-L5244)) - [`ex_hide()`](4a5062cda6/src/nvim/ex_docmd.c (L5478-L5484)) - `eval/window.c`'s: - [`f_winrestcmd()`](4a5062cda6/src/nvim/eval/window.c (L797-L808)) - [`find_win_by_nr()`](4a5062cda6/src/nvim/eval/window.c (L172-L180))
This commit is contained in:
@@ -48,6 +48,20 @@ bool win_has_winnr(win_T *wp, tabpage_T *tp)
|
||||
|| (!wp->w_config.hide && wp->w_config.focusable);
|
||||
}
|
||||
|
||||
win_T *win_find_nr(int nr, tabpage_T *tp)
|
||||
{
|
||||
if (tp == NULL) {
|
||||
assert(curtab);
|
||||
tp = curtab;
|
||||
}
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
|
||||
if ((nr -= win_has_winnr(wp, tp)) <= 0) {
|
||||
return wp;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int win_getid(typval_T *argvars)
|
||||
{
|
||||
if (argvars[0].v_type == VAR_UNKNOWN) {
|
||||
@@ -169,16 +183,17 @@ win_T *find_win_by_nr(typval_T *vp, tabpage_T *tp)
|
||||
tp = curtab;
|
||||
}
|
||||
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
|
||||
if (nr >= LOWEST_WIN_ID) {
|
||||
if (nr >= LOWEST_WIN_ID) {
|
||||
// window id
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
|
||||
if (wp->handle == nr) {
|
||||
return wp;
|
||||
}
|
||||
} else if (--nr <= 0) {
|
||||
return wp;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
return win_find_nr(nr, tp);
|
||||
}
|
||||
|
||||
/// Find a window: When using a Window ID in any tab page, when using a number
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "nvim/eval/typval_defs.h"
|
||||
#include "nvim/eval/userfunc.h"
|
||||
#include "nvim/eval/vars.h"
|
||||
#include "nvim/eval/window.h"
|
||||
#include "nvim/eval_defs.h"
|
||||
#include "nvim/event/loop.h"
|
||||
#include "nvim/event/multiqueue.h"
|
||||
@@ -1075,14 +1076,24 @@ static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int o
|
||||
|
||||
/// @return the window number of "win" or,
|
||||
/// the number of windows if "win" is NULL
|
||||
static int current_win_nr(const win_T *win)
|
||||
static int current_win_nr(const win_T *win, exarg_T *cmdarg)
|
||||
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
|
||||
{
|
||||
// Whether to also match hidden / non-focusable windows (in current tab).
|
||||
bool nonnr = cmdarg != NULL && cmdarg->cmdidx != CMD_quit;
|
||||
int nr = 0;
|
||||
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
nr++;
|
||||
// only include non-hidden and focusable windows
|
||||
// (so ui2 windows are excluded)
|
||||
bool include_this = nonnr || win_has_winnr(wp, curtab);
|
||||
nr += include_this;
|
||||
|
||||
if (wp == win) {
|
||||
// same as in win_id2win()
|
||||
if (!include_this) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1102,8 +1113,8 @@ static int current_tab_nr(tabpage_T *tab)
|
||||
return nr;
|
||||
}
|
||||
|
||||
#define CURRENT_WIN_NR current_win_nr(curwin)
|
||||
#define LAST_WIN_NR current_win_nr(NULL)
|
||||
#define CURRENT_WIN_NR current_win_nr(curwin, NULL)
|
||||
#define LAST_WIN_NR(cmdarg) current_win_nr(NULL, (cmdarg))
|
||||
#define CURRENT_TAB_NR current_tab_nr(curtab)
|
||||
#define LAST_TAB_NR current_tab_nr(NULL)
|
||||
|
||||
@@ -1304,7 +1315,7 @@ void set_cmd_dflall_range(exarg_T *eap)
|
||||
eap->line2 = lastbuf->b_fnum;
|
||||
break;
|
||||
case ADDR_WINDOWS:
|
||||
eap->line2 = LAST_WIN_NR;
|
||||
eap->line2 = LAST_WIN_NR(eap);
|
||||
break;
|
||||
case ADDR_TABS:
|
||||
eap->line2 = LAST_TAB_NR;
|
||||
@@ -2854,7 +2865,7 @@ int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent)
|
||||
if (IS_USER_CMDIDX(eap->cmdidx)) {
|
||||
eap->line1 = 1;
|
||||
eap->line2 = eap->addr_type == ADDR_WINDOWS
|
||||
? LAST_WIN_NR : LAST_TAB_NR;
|
||||
? LAST_WIN_NR(false) : LAST_TAB_NR;
|
||||
} else {
|
||||
// there is no Vim command which uses '%' and
|
||||
// ADDR_WINDOWS or ADDR_TABS
|
||||
@@ -3429,7 +3440,7 @@ linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, bool skip,
|
||||
lnum = curbuf->b_ml.ml_line_count;
|
||||
break;
|
||||
case ADDR_WINDOWS:
|
||||
lnum = LAST_WIN_NR;
|
||||
lnum = LAST_WIN_NR(eap);
|
||||
break;
|
||||
case ADDR_ARGUMENTS:
|
||||
lnum = ARGCOUNT;
|
||||
@@ -3773,7 +3784,7 @@ char *invalid_range(exarg_T *eap)
|
||||
}
|
||||
break;
|
||||
case ADDR_WINDOWS:
|
||||
if (eap->line2 > LAST_WIN_NR) {
|
||||
if (eap->line2 > LAST_WIN_NR(eap)) {
|
||||
return _(e_invrange);
|
||||
}
|
||||
break;
|
||||
@@ -5124,24 +5135,17 @@ fail_1:
|
||||
/// ":close": close current window, unless it is the last one
|
||||
static void ex_close(exarg_T *eap)
|
||||
{
|
||||
win_T *win = NULL;
|
||||
int winnr = 0;
|
||||
if (!text_locked() && !curbuf_locked()) {
|
||||
win_T *win;
|
||||
if (eap->addr_count == 0) {
|
||||
ex_win_close(eap->forceit, curwin, NULL);
|
||||
win = curwin;
|
||||
} else {
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
winnr++;
|
||||
if (winnr == eap->line2) {
|
||||
win = wp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
win = win_find_nr((int)eap->line2, curtab);
|
||||
if (win == NULL) {
|
||||
win = lastwin;
|
||||
}
|
||||
ex_win_close(eap->forceit, win, NULL);
|
||||
}
|
||||
ex_win_close(eap->forceit, win, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5369,19 +5373,11 @@ static void ex_hide(exarg_T *eap)
|
||||
return;
|
||||
}
|
||||
|
||||
win_T *win = NULL;
|
||||
win_T *win;
|
||||
if (eap->addr_count == 0) {
|
||||
win = curwin;
|
||||
} else {
|
||||
int winnr = 0;
|
||||
|
||||
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
|
||||
winnr++;
|
||||
if (winnr == eap->line2) {
|
||||
win = wp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
win = win_find_nr((int)eap->line2, curtab);
|
||||
if (win == NULL) {
|
||||
win = lastwin;
|
||||
}
|
||||
|
||||
@@ -324,6 +324,16 @@ describe('cmdline2', function()
|
||||
{16::}{15:call} {25:foo}{16:(}{25:bar}{16:(}^ |
|
||||
]])
|
||||
end)
|
||||
|
||||
it("doesn't interfere with :$q", function()
|
||||
exec('split b')
|
||||
exec('wincmd J')
|
||||
exec('wincmd p')
|
||||
exec('$q') -- this would close a ui2 window, not the second visible window
|
||||
|
||||
local nwins = vim.fn.winnr('$')
|
||||
assert(nwins == 1)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('cmdline2', function()
|
||||
|
||||
Reference in New Issue
Block a user