aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Kennedy <colinvfx@gmail.com>2023-12-25 20:41:09 -0800
committerzeertzjq <zeertzjq@outlook.com>2024-03-11 11:38:13 +0800
commit141182d6c6c06ad56413b81a518ba9b777a0cbe0 (patch)
tree451ef95315a55caf7ca95c9ef6eb8bedd48e77d9
parenta09ddd7ce55037edc9747a682810fba6a26bc201 (diff)
downloadrneovim-141182d6c6c06ad56413b81a518ba9b777a0cbe0.tar.gz
rneovim-141182d6c6c06ad56413b81a518ba9b777a0cbe0.tar.bz2
rneovim-141182d6c6c06ad56413b81a518ba9b777a0cbe0.zip
vim-patch:9.1.0147: Cannot keep a buffer focused in a window
Problem: Cannot keep a buffer focused in a window (Amit Levy) Solution: Add the 'winfixbuf' window-local option (Colin Kennedy) fixes: vim/vim#6445 closes: vim/vim#13903 https://github.com/vim/vim/commit/215703563757a4464907ead6fb9edaeb7f430bea N/A patch: vim-patch:58f1e5c0893a
-rw-r--r--runtime/doc/message.txt7
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/doc/options.txt11
-rw-r--r--runtime/doc/quickref.txt1
-rw-r--r--runtime/doc/tagsrch.txt29
-rw-r--r--runtime/lua/vim/_meta/options.lua14
-rw-r--r--runtime/optwin.vim3
-rw-r--r--src/nvim/api/vim.c5
-rw-r--r--src/nvim/api/window.c6
-rw-r--r--src/nvim/arglist.c10
-rw-r--r--src/nvim/buffer.c6
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/ex_cmds.c4
-rw-r--r--src/nvim/ex_cmds.lua2
-rw-r--r--src/nvim/ex_cmds2.c21
-rw-r--r--src/nvim/ex_docmd.c16
-rw-r--r--src/nvim/globals.h3
-rw-r--r--src/nvim/insexpand.c2
-rw-r--r--src/nvim/normal.c7
-rw-r--r--src/nvim/option.c2
-rw-r--r--src/nvim/options.lua19
-rw-r--r--src/nvim/quickfix.c33
-rw-r--r--src/nvim/search.c8
-rw-r--r--src/nvim/tag.c8
-rw-r--r--src/nvim/window.c31
-rw-r--r--test/functional/options/winfixbuf_spec.lua54
-rw-r--r--test/old/testdir/test_winfixbuf.vim3131
27 files changed, 3414 insertions, 23 deletions
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index c3154fc372..16d88407d5 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -114,6 +114,13 @@ wiped out a buffer which contains a mark or is referenced in another way.
You cannot have two buffers with exactly the same name. This includes the
path leading to the file.
+ *E1513* >
+ Cannot edit buffer. 'winfixbuf' is enabled
+
+If a window has 'winfixbuf' enabled, you cannot change that window's current
+buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to
+force the window to switch buffers, if your command supports it.
+
*E72* >
Close error on swap file
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 3029414500..3ba7c5e681 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -160,6 +160,8 @@ The following new APIs and features were added.
• 'breakindent' performance is significantly improved for wrapped lines.
• Cursor movement, insertion with [count] and |screenpos()| are now faster.
+• |'winfixbuf'| keeps a window focused onto a specific buffer
+
• |vim.iter()| provides a generic iterator interface for tables and Lua
iterators |for-in|.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index edd5149621..f35700218c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -6271,6 +6271,8 @@ A jump table for the options with a short description can be found at |Q_op|.
"split" when both are present.
uselast If included, jump to the previously used window when
jumping to errors with |quickfix| commands.
+ If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
+ applied to the split window.
*'synmaxcol'* *'smc'*
'synmaxcol' 'smc' number (default 3000)
@@ -7170,6 +7172,15 @@ A jump table for the options with a short description can be found at |Q_op|.
Note: Do not confuse this with the height of the Vim window, use
'lines' for that.
+ *'winfixbuf'* *'wfb'* *'nowinfixbuf'* *'nowfb'*
+'winfixbuf' 'wfb' boolean (default off)
+ local to window
+ If enabled, the buffer and any window that displays it are paired.
+ For example, attempting to change the buffer with |:edit| will fail.
+ Other commands which change a window's buffer such as |:cnext| will
+ also skip any window with 'winfixbuf' enabled. However if a command
+ has an "!" option, a window can be forced to switch buffers.
+
*'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'*
'winfixheight' 'wfh' boolean (default off)
local to window |local-noglobal|
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index 572dc8a841..4ef4392f92 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -939,6 +939,7 @@ Short explanation of each option: *option-list*
'wildoptions' 'wop' specifies how command line completion is done
'winaltkeys' 'wak' when the windows system handles ALT keys
'window' 'wi' nr of lines to scroll for CTRL-F and CTRL-B
+'winfixbuf' 'wfb' keep window focused on a single buffer
'winfixheight' 'wfh' keep window height when opening/closing windows
'winfixwidth' 'wfw' keep window width when opening/closing windows
'winheight' 'wh' minimum number of lines for the current window
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index 2b5b253a09..ac2bf9337b 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -402,17 +402,22 @@ If the tag is in the current file this will always work. Otherwise the
performed actions depend on whether the current file was changed, whether a !
is added to the command and on the 'autowrite' option:
- tag in file autowrite ~
-current file changed ! option action ~
- ---------------------------------------------------------------------------
- yes x x x goto tag
- no no x x read other file, goto tag
- no yes yes x abandon current file, read other file, goto
- tag
- no yes no on write current file, read other file, goto
- tag
- no yes no off fail
- ---------------------------------------------------------------------------
+ tag in file autowrite ~
+current file changed ! winfixbuf option action ~
+ -----------------------------------------------------------------------------
+ yes x x no x goto tag
+ no no x no x read other file, goto tag
+ no yes yes no x abandon current file,
+ read other file, goto tag
+ no yes no no on write current file,
+ read other file, goto tag
+ no yes no no off fail
+ yes x yes x x goto tag
+ no no no yes x fail
+ no yes no yes x fail
+ no yes no yes on fail
+ no yes no yes off fail
+ -----------------------------------------------------------------------------
- If the tag is in the current file, the command will always work.
- If the tag is in another file and the current file was not changed, the
@@ -428,6 +433,8 @@ current file changed ! option action ~
the changes, use the ":w" command and then use ":tag" without an argument.
This works because the tag is put on the stack anyway. If you want to lose
the changes you can use the ":tag!" command.
+- If the tag is in another file and the window includes 'winfixbuf', the
+ command will fail. If the tag is in the same file then it may succeed.
*tag-security*
Note that Vim forbids some commands, for security reasons. This works like
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 757720d8fb..e9ac2fe08f 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -6746,6 +6746,8 @@ vim.bo.swf = vim.bo.swapfile
--- "split" when both are present.
--- uselast If included, jump to the previously used window when
--- jumping to errors with `quickfix` commands.
+--- If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
+--- applied to the split window.
---
--- @type string
vim.o.switchbuf = "uselast"
@@ -7874,6 +7876,18 @@ vim.o.wi = vim.o.window
vim.go.window = vim.o.window
vim.go.wi = vim.go.window
+--- If enabled, the buffer and any window that displays it are paired.
+--- For example, attempting to change the buffer with `:edit` will fail.
+--- Other commands which change a window's buffer such as `:cnext` will
+--- also skip any window with 'winfixbuf' enabled. However if a command
+--- has an "!" option, a window can be forced to switch buffers.
+---
+--- @type boolean
+vim.o.winfixbuf = false
+vim.o.wfb = vim.o.winfixbuf
+vim.wo.winfixbuf = vim.o.winfixbuf
+vim.wo.wfb = vim.wo.winfixbuf
+
--- Keep the window height when windows are opened or closed and
--- 'equalalways' is set. Also for `CTRL-W_=`. Set by default for the
--- `preview-window` and `quickfix-window`.
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index fc60f70335..5b5b33e4ad 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -444,6 +444,7 @@ if has("statusline")
call <SID>AddOption("statusline", gettext("alternate format to be used for a status line"))
call <SID>OptionG("stl", &stl)
endif
+call append("$", "\t" .. s:local_to_window)
call <SID>AddOption("equalalways", gettext("make all windows the same size when adding/removing windows"))
call <SID>BinOptionG("ea", &ea)
call <SID>AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\""))
@@ -452,6 +453,8 @@ call <SID>AddOption("winheight", gettext("minimal number of lines used for the c
call append("$", " \tset wh=" . &wh)
call <SID>AddOption("winminheight", gettext("minimal number of lines used for any window"))
call append("$", " \tset wmh=" . &wmh)
+call <SID>AddOption("winfixbuf", gettext("keep window focused on a single buffer"))
+call <SID>OptionG("wfb", &wfb)
call <SID>AddOption("winfixheight", gettext("keep the height of the window"))
call append("$", "\t" .. s:local_to_window)
call <SID>BinOptionL("wfh")
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index 84a2f24dbc..24ad7d5fbc 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -876,6 +876,11 @@ void nvim_set_current_buf(Buffer buffer, Error *err)
return;
}
+ if (curwin->w_p_wfb) {
+ api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+ return;
+ }
+
try_start();
int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
if (!try_end(err) && result == FAIL) {
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index ed51eedf1b..1a80e9ea16 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -61,6 +61,12 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
if (!win || !buf) {
return;
}
+
+ if (win->w_p_wfb) {
+ api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
+ return;
+ }
+
if (win == cmdwin_win || win == cmdwin_old_curwin || buf == cmdwin_buf) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return;
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
index a02c22deae..4d493c9d03 100644
--- a/src/nvim/arglist.c
+++ b/src/nvim/arglist.c
@@ -623,6 +623,8 @@ void ex_argument(exarg_T *eap)
/// Edit file "argn" of the argument lists.
void do_argfile(exarg_T *eap, int argn)
{
+ bool is_split_cmd = *eap->cmd == 's';
+
int old_arg_idx = curwin->w_arg_idx;
if (argn < 0 || argn >= ARGCOUNT) {
@@ -637,10 +639,16 @@ void do_argfile(exarg_T *eap, int argn)
return;
}
+ if (!is_split_cmd
+ && (&ARGLIST[argn])->ae_fnum != curbuf->b_fnum
+ && !check_can_set_curbuf_forceit(eap->forceit)) {
+ return;
+ }
+
setpcmark();
// split window or create new tab page first
- if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) {
+ if (is_split_cmd || cmdmod.cmod_tab != 0) {
if (win_split(0, 0) == FAIL) {
return;
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 7154be36be..e141706edd 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1305,6 +1305,12 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
return FAIL;
}
+
+ if (action == DOBUF_GOTO && buf != curbuf && !check_can_set_curbuf_forceit(forceit)) {
+ // disallow navigating to another buffer when 'winfixbuf' is applied
+ return FAIL;
+ }
+
if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) {
// disallow navigating to the dummy buffer
semsg(_(e_nobufnr), count);
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 1e5086309c..7f7300706c 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -139,6 +139,8 @@ typedef struct {
#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
OptInt wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
+ int wo_wfb;
+#define w_p_wfb w_onebuf_opt.wo_wfb // 'winfixbuf'
int wo_wfh;
#define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
int wo_wfw;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 74ad8e95a2..14bd2b87e3 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -2008,6 +2008,10 @@ static int check_readonly(int *forceit, buf_T *buf)
/// GETFILE_OPEN_OTHER for successfully opening another file.
int getfile(int fnum, char *ffname_arg, char *sfname_arg, bool setpm, linenr_T lnum, bool forceit)
{
+ if (!check_can_set_curbuf_forceit(forceit)) {
+ return GETFILE_ERROR;
+ }
+
char *ffname = ffname_arg;
char *sfname = sfname_arg;
bool other;
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 1318eda5eb..e2196f99ec 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -812,7 +812,7 @@ module.cmds = {
},
{
command = 'drop',
- flags = bit.bor(FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR),
+ flags = bit.bor(BANG, FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR),
addr_type = 'ADDR_NONE',
func = 'ex_drop',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 8016e37ca7..3120868350 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -444,6 +444,27 @@ int buf_write_all(buf_T *buf, bool forceit)
/// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
void ex_listdo(exarg_T *eap)
{
+ if (curwin->w_p_wfb) {
+ if ((eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) && !eap->forceit) {
+ // Disallow :ldo if 'winfixbuf' is applied
+ semsg("%s", e_winfixbuf_cannot_go_to_buffer);
+ return;
+ }
+
+ if (win_valid(prevwin)) {
+ // Change the current window to another because 'winfixbuf' is enabled
+ curwin = prevwin;
+ } else {
+ // Split the window, which will be 'nowinfixbuf', and set curwin to that
+ exarg_T new_eap = {
+ .cmdidx = CMD_split,
+ .cmd = "split",
+ .arg = "",
+ };
+ ex_splitview(&new_eap);
+ }
+ }
+
char *save_ei = NULL;
// Temporarily override SHM_OVER and SHM_OVERALL to avoid that file
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 2913f6d4e9..1b4e83d392 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -5334,6 +5334,10 @@ static void ex_resize(exarg_T *eap)
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
+ if (!check_can_set_curbuf_forceit(eap->forceit)) {
+ return;
+ }
+
char *file_to_find = NULL;
char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
@@ -5364,6 +5368,14 @@ static void ex_find(exarg_T *eap)
/// ":edit", ":badd", ":balt", ":visual".
static void ex_edit(exarg_T *eap)
{
+ // Exclude commands which keep the window's current buffer
+ if (eap->cmdidx != CMD_badd
+ && eap->cmdidx != CMD_balt
+ // All other commands must obey 'winfixbuf' / ! rules
+ && !check_can_set_curbuf_forceit(eap->forceit)) {
+ return;
+ }
+
do_exedit(eap, NULL);
}
@@ -6670,7 +6682,7 @@ static void ex_checkpath(exarg_T *eap)
{
find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
- 1, (linenr_T)MAXLNUM);
+ 1, (linenr_T)MAXLNUM, eap->forceit);
}
/// ":psearch"
@@ -6729,7 +6741,7 @@ static void ex_findpat(exarg_T *eap)
if (!eap->skip) {
find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit,
*eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
- n, action, eap->line1, eap->line2);
+ n, action, eap->line1, eap->line2, eap->forceit);
}
}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 113985cb52..aecb9d1116 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -971,6 +971,9 @@ EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
+EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
+INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled"));
+
EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 41b964323e..d0cd24773f 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -3027,7 +3027,7 @@ static void get_next_include_file_completion(int compl_type)
((compl_type == CTRL_X_PATH_DEFINES
&& !(compl_cont_status & CONT_SOL))
? FIND_DEFINE : FIND_ANY),
- 1, ACTION_EXPAND, 1, MAXLNUM);
+ 1, ACTION_EXPAND, 1, MAXLNUM, false);
}
/// Get the next set of words matching "compl_pattern" in dictionary or
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index f586ad6704..aae9621d4a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -3896,6 +3896,10 @@ static void nv_gotofile(cmdarg_T *cap)
return;
}
+ if (!check_can_set_curbuf_disabled()) {
+ return;
+ }
+
char *ptr = grab_file_name(cap->count1, &lnum);
if (ptr != NULL) {
@@ -4232,7 +4236,8 @@ static void nv_brackets(cmdarg_T *cap)
(cap->cmdchar == ']'
? curwin->w_cursor.lnum + 1
: 1),
- MAXLNUM);
+ MAXLNUM,
+ false);
xfree(ptr);
curwin->w_set_curswant = true;
}
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 0ac65ed95d..fcc5b5eb06 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -4629,6 +4629,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return &(win->w_p_rnu);
case PV_NUW:
return &(win->w_p_nuw);
+ case PV_WFB:
+ return &(win->w_p_wfb);
case PV_WFH:
return &(win->w_p_wfh);
case PV_WFW:
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 72f9ff849d..5e8bc1361c 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -8406,6 +8406,8 @@ return {
"split" when both are present.
uselast If included, jump to the previously used window when
jumping to errors with |quickfix| commands.
+ If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
+ applied to the split window.
]=],
expand_cb = 'expand_set_switchbuf',
full_name = 'switchbuf',
@@ -9817,6 +9819,23 @@ return {
varname = 'p_window',
},
{
+ abbreviation = 'wfb',
+ defaults = { if_true = false },
+ desc = [=[
+ If enabled, the buffer and any window that displays it are paired.
+ For example, attempting to change the buffer with |:edit| will fail.
+ Other commands which change a window's buffer such as |:cnext| will
+ also skip any window with 'winfixbuf' enabled. However if a command
+ has an "!" option, a window can be forced to switch buffers.
+ ]=],
+ full_name = 'winfixbuf',
+ pv_name = 'p_wfb',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('pin a window to a specific buffer'),
+ type = 'boolean',
+ },
+ {
abbreviation = 'wfh',
defaults = { if_true = false },
desc = [=[
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 651ebc9f93..a88b781f32 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -2699,7 +2699,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
// Didn't find it, go to the window before the quickfix
// window, unless 'switchbuf' contains 'uselast': in this case we
// try to jump to the previously used window first.
- if ((swb_flags & SWB_USELAST) && win_valid(prevwin)) {
+ if ((swb_flags & SWB_USELAST) && !prevwin->w_p_wfb && win_valid(prevwin)) {
win = prevwin;
} else if (altwin != NULL) {
win = altwin;
@@ -2714,6 +2714,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
// Remember a usable window.
if (altwin == NULL
&& !win->w_p_pvw
+ && !win->w_p_wfb
&& bt_normal(win->w_buffer)) {
altwin = win;
}
@@ -2802,6 +2803,25 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
ECMD_HIDE + ECMD_SET_HELP,
prev_winid == curwin->handle ? curwin : NULL);
} else {
+ if (!forceit && curwin->w_p_wfb) {
+ if (qi->qfl_type == QFLT_LOCATION) {
+ // Location lists cannot split or reassign their window
+ // so 'winfixbuf' windows must fail
+ semsg("%s", e_winfixbuf_cannot_go_to_buffer);
+ return QF_ABORT;
+ }
+
+ if (!win_valid(prevwin)) {
+ // Split the window, which will be 'nowinfixbuf', and set curwin to that
+ exarg_T new_eap = {
+ .cmdidx = CMD_split,
+ .cmd = "split",
+ .arg = "",
+ };
+ ex_splitview(&new_eap);
+ }
+ }
+
retval = buflist_getfile(qf_ptr->qf_fnum, 1,
GETF_SETMARK | GETF_SWITCH, forceit);
}
@@ -4297,6 +4317,11 @@ static void qf_jump_first(qf_info_T *qi, unsigned save_qfid, int forceit)
if (qf_restore_list(qi, save_qfid) == FAIL) {
return;
}
+
+ if (!check_can_set_curbuf_forceit(forceit)) {
+ return;
+ }
+
// Autocommands might have cleared the list, check for that
if (!qf_list_empty(qf_get_curlist(qi))) {
qf_jump(qi, 0, 0, forceit);
@@ -5125,7 +5150,7 @@ void ex_cfile(exarg_T *eap)
// This function is used by the :cfile, :cgetfile and :caddfile
// commands.
- // :cfile always creates a new quickfix list and jumps to the
+ // :cfile always creates a new quickfix list and may jump to the
// first error.
// :cgetfile creates a new quickfix list but doesn't jump to the
// first error.
@@ -5587,6 +5612,10 @@ theend:
/// ":lvimgrepadd {pattern} file(s)"
void ex_vimgrep(exarg_T *eap)
{
+ if (!check_can_set_curbuf_forceit(eap->forceit)) {
+ return;
+ }
+
char *au_name = vgr_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, true, curbuf)) {
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 48e41c290d..2fea28ba7c 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -3564,8 +3564,10 @@ static char *get_line_and_copy(linenr_T lnum, char *buf)
/// @param action What to do when we find it
/// @param start_lnum first line to start searching
/// @param end_lnum last line for searching
+/// @param forceit If true, always switch to the found path
void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments,
- int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum)
+ int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum,
+ int forceit)
{
SearchedFile *files; // Stack of included files
SearchedFile *bigger; // When we need more space
@@ -4025,7 +4027,7 @@ search_line:
break;
}
if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL,
- NULL, true, lnum, false))) {
+ NULL, true, lnum, forceit))) {
break; // failed to jump to file
}
} else {
@@ -4035,7 +4037,7 @@ search_line:
check_cursor();
} else {
if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true,
- files[depth].lnum, false))) {
+ files[depth].lnum, forceit))) {
break; // failed to jump to file
}
// autocommands may have changed the lnum, we don't
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index ab5bfc6773..776498fa29 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -290,6 +290,10 @@ void set_buflocal_tfu_callback(buf_T *buf)
/// @param verbose print "tag not found" message
void do_tag(char *tag, int type, int count, int forceit, bool verbose)
{
+ if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit)) {
+ return;
+ }
+
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
@@ -2784,6 +2788,10 @@ static char *tag_full_fname(tagptrs_T *tagp)
/// @return OK for success, NOTAGFILE when file not found, FAIL otherwise.
static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help)
{
+ if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit)) {
+ return FAIL;
+ }
+
char *pbuf_end;
char *tofree_fname = NULL;
tagptrs_T tagp;
diff --git a/src/nvim/window.c b/src/nvim/window.c
index ff40a9adef..9f84713ee7 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -133,6 +133,35 @@ static void log_frame_layout(frame_T *frame)
}
#endif
+/// Check if the current window is allowed to move to a different buffer.
+///
+/// @return If the window has 'winfixbuf', or this function will return false.
+bool check_can_set_curbuf_disabled(void)
+{
+ if (curwin->w_p_wfb) {
+ semsg("%s", e_winfixbuf_cannot_go_to_buffer);
+ return false;
+ }
+
+ return true;
+}
+
+/// Check if the current window is allowed to move to a different buffer.
+///
+/// @param forceit If true, do not error. If false and 'winfixbuf' is enabled, error.
+///
+/// @return If the window has 'winfixbuf', then forceit must be true
+/// or this function will return false.
+bool check_can_set_curbuf_forceit(int forceit)
+{
+ if (!forceit && curwin->w_p_wfb) {
+ semsg("%s", e_winfixbuf_cannot_go_to_buffer);
+ return false;
+ }
+
+ return true;
+}
+
/// @return the current window, unless in the cmdline window and "prevwin" is
/// set, then return "prevwin".
win_T *prevwin_curwin(void)
@@ -597,7 +626,7 @@ wingotofile:
ptr = xmemdupz(ptr, len);
find_pattern_in_path(ptr, 0, len, true, Prenum == 0,
- type, Prenum1, ACTION_SPLIT, 1, MAXLNUM);
+ type, Prenum1, ACTION_SPLIT, 1, MAXLNUM, false);
xfree(ptr);
curwin->w_set_curswant = true;
break;
diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua
new file mode 100644
index 0000000000..20407b9bb7
--- /dev/null
+++ b/test/functional/options/winfixbuf_spec.lua
@@ -0,0 +1,54 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+
+describe("Nvim API calls with 'winfixbuf'", function()
+ before_each(function()
+ clear()
+ end)
+
+ it("Calling vim.api.nvim_win_set_buf with 'winfixbuf'", function()
+ local results = exec_lua([[
+ local function _setup_two_buffers()
+ local buffer = vim.api.nvim_create_buf(true, true)
+
+ vim.api.nvim_create_buf(true, true) -- Make another buffer
+
+ local current_window = 0
+ vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window})
+
+ return buffer
+ end
+
+ local other_buffer = _setup_two_buffers()
+ local current_window = 0
+ local results, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer)
+
+ return results
+ ]])
+
+ assert(results == false)
+ end)
+
+ it("Calling vim.api.nvim_set_current_buf with 'winfixbuf'", function()
+ local results = exec_lua([[
+ local function _setup_two_buffers()
+ local buffer = vim.api.nvim_create_buf(true, true)
+
+ vim.api.nvim_create_buf(true, true) -- Make another buffer
+
+ local current_window = 0
+ vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window})
+
+ return buffer
+ end
+
+ local other_buffer = _setup_two_buffers()
+ local results, _ = pcall(vim.api.nvim_set_current_buf, other_buffer)
+
+ return results
+ ]])
+
+ assert(results == false)
+ end)
+end)
diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim
new file mode 100644
index 0000000000..2948be7859
--- /dev/null
+++ b/test/old/testdir/test_winfixbuf.vim
@@ -0,0 +1,3131 @@
+" Test 'winfixbuf'
+
+source check.vim
+
+" Find the number of open windows in the current tab
+func s:get_windows_count()
+ return tabpagewinnr(tabpagenr(), '$')
+endfunc
+
+" Create some unnamed buffers.
+func s:make_buffers_list()
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file middle
+ let l:middle = bufnr()
+
+ enew
+ file last
+ let l:last = bufnr()
+
+ set winfixbuf
+
+ return [l:first, l:last]
+endfunc
+
+" Create some unnamed buffers and add them to an args list
+func s:make_args_list()
+ let [l:first, l:last] = s:make_buffers_list()
+
+ args! first middle last
+
+ return [l:first, l:last]
+endfunc
+
+" Create two buffers and then set the window to 'winfixbuf'
+func s:make_buffer_pairs(...)
+ let l:reversed = get(a:, 1, 0)
+
+ if l:reversed == 1
+ enew
+ file original
+
+ set winfixbuf
+
+ enew!
+ file other
+ let l:other = bufnr()
+
+ return l:other
+ endif
+
+ enew
+ file other
+ let l:other = bufnr()
+
+ enew
+ file current
+
+ set winfixbuf
+
+ return l:other
+endfunc
+
+" Create 3 quick buffers and set the window to 'winfixbuf'
+func s:make_buffer_trio()
+ edit first
+ let l:first = bufnr()
+ edit second
+ let l:second = bufnr()
+
+ set winfixbuf
+
+ edit! third
+ let l:third = bufnr()
+
+ execute ":buffer! " . l:second
+
+ return [l:first, l:second, l:third]
+endfunc
+
+" Create a location list with at least 2 entries + a 'winfixbuf' window.
+func s:make_simple_location_list()
+ enew
+ file middle
+ let l:middle = bufnr()
+ call append(0, ["winfix search-term", "another line"])
+
+ enew!
+ file first
+ let l:first = bufnr()
+ call append(0, "first search-term")
+
+ enew!
+ file last
+ let l:last = bufnr()
+ call append(0, "last search-term")
+
+ call setloclist(
+ \ 0,
+ \ [
+ \ {
+ \ "filename": "first",
+ \ "bufnr": l:first,
+ \ "lnum": 1,
+ \ },
+ \ {
+ \ "filename": "middle",
+ \ "bufnr": l:middle,
+ \ "lnum": 1,
+ \ },
+ \ {
+ \ "filename": "middle",
+ \ "bufnr": l:middle,
+ \ "lnum": 2,
+ \ },
+ \ {
+ \ "filename": "last",
+ \ "bufnr": l:last,
+ \ "lnum": 1,
+ \ },
+ \ ]
+ \)
+
+ set winfixbuf
+
+ return [l:first, l:middle, l:last]
+endfunc
+
+" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window.
+func s:make_simple_quickfix()
+ enew
+ file current
+ let l:current = bufnr()
+ call append(0, ["winfix search-term", "another line"])
+
+ enew!
+ file first
+ let l:first = bufnr()
+ call append(0, "first search-term")
+
+ enew!
+ file last
+ let l:last = bufnr()
+ call append(0, "last search-term")
+
+ call setqflist(
+ \ [
+ \ {
+ \ "filename": "first",
+ \ "bufnr": l:first,
+ \ "lnum": 1,
+ \ },
+ \ {
+ \ "filename": "current",
+ \ "bufnr": l:current,
+ \ "lnum": 1,
+ \ },
+ \ {
+ \ "filename": "current",
+ \ "bufnr": l:current,
+ \ "lnum": 2,
+ \ },
+ \ {
+ \ "filename": "last",
+ \ "bufnr": l:last,
+ \ "lnum": 1,
+ \ },
+ \ ]
+ \)
+
+ set winfixbuf
+
+ return [l:current, l:last]
+endfunc
+
+" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window.
+func s:make_quickfix_windows()
+ let [l:current, _] = s:make_simple_quickfix()
+ execute "buffer! " . l:current
+
+ split
+ let l:first_window = win_getid()
+ execute "normal \<C-w>j"
+ let l:winfix_window = win_getid()
+
+ " Open the quickfix in a separate split and go to it
+ copen
+ let l:quickfix_window = win_getid()
+
+ return [l:first_window, l:winfix_window, l:quickfix_window]
+endfunc
+
+" Revert all changes that occurred in any past test
+func s:reset_all_buffers()
+ %bwipeout!
+ set nowinfixbuf
+
+ call setqflist([])
+
+ for l:window_info in getwininfo()
+ call setloclist(l:window_info["winid"], [])
+ endfor
+
+ delmarks A-Z0-9
+endfunc
+
+" Find and set the first quickfix entry that points to `buffer`
+func s:set_quickfix_by_buffer(buffer)
+ let l:index = 1 " quickfix indices start at 1
+ for l:entry in getqflist()
+ if l:entry["bufnr"] == a:buffer
+ execute l:index . "cc"
+
+ return
+ endif
+
+ let l:index += 1
+ endfor
+
+ echoerr 'No quickfix entry matching "' . a:buffer . '" could be found.'
+endfunc
+
+" Fail to call :Next on a 'winfixbuf' window unless :Next! is used.
+func Test_Next()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("Next", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ Next!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Call :argdo and choose the next available 'nowinfixbuf' window.
+func Test_argdo_choose_available_window()
+ call s:reset_all_buffers()
+
+ let [_, l:last] = s:make_args_list()
+
+ " Make a split window that is 'nowinfixbuf' but make it the second-to-last
+ " window so that :argdo will first try the 'winfixbuf' window, pass over it,
+ " and prefer the other 'nowinfixbuf' window, instead.
+ "
+ " +-------------------+
+ " | 'nowinfixbuf' |
+ " +-------------------+
+ " | 'winfixbuf' | <-- Cursor is here
+ " +-------------------+
+ split
+ let l:nowinfixbuf_window = win_getid()
+ " Move to the 'winfixbuf' window now
+ execute "normal \<C-w>j"
+ let l:winfixbuf_window = win_getid()
+ let l:expected_windows = s:get_windows_count()
+
+ argdo echo ''
+ call assert_equal(l:nowinfixbuf_window, win_getid())
+ call assert_equal(l:last, bufnr())
+ call assert_equal(l:expected_windows, s:get_windows_count())
+endfunc
+
+" Call :argdo and create a new split window if all available windows are 'winfixbuf'.
+func Test_argdo_make_new_window()
+ call s:reset_all_buffers()
+
+ let [l:first, l:last] = s:make_args_list()
+ let l:current = win_getid()
+ let l:current_windows = s:get_windows_count()
+
+ argdo echo ''
+ call assert_notequal(l:current, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:first, bufnr())
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+endfunc
+
+" Fail :argedit but :argedit! is allowed
+func Test_argedit()
+ call s:reset_all_buffers()
+
+ args! first middle last
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file middle
+ let l:middle = bufnr()
+
+ enew
+ file last
+ let l:last = bufnr()
+
+ set winfixbuf
+
+ let l:current = bufnr()
+ call assert_fails("argedit first middle last", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ argedit! first middle last
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :arglocal but :arglocal! is allowed
+func Test_arglocal()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+ argglobal! other
+ execute "buffer! " . l:current
+
+ call assert_fails("arglocal other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ arglocal! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :argglobal but :argglobal! is allowed
+func Test_argglobal()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("argglobal other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ argglobal! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :args but :args! is allowed
+func Test_args()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_buffers_list()
+ let l:current = bufnr()
+
+ call assert_fails("args first middle last", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ args! first middle last
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :bNext but :bNext! is allowed
+func Test_bNext()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ call assert_fails("bNext", "E1513:")
+ let l:current = bufnr()
+
+ call assert_equal(l:current, bufnr())
+
+ bNext!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Allow :badd because it doesn't actually change the current window's buffer
+func Test_badd()
+ call s:reset_all_buffers()
+
+ call s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ badd other
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Allow :balt because it doesn't actually change the current window's buffer
+func Test_balt()
+ call s:reset_all_buffers()
+
+ call s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ balt other
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Fail :bfirst but :bfirst! is allowed
+func Test_bfirst()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("bfirst", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ bfirst!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :blast but :blast! is allowed
+func Test_blast()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs(1)
+ bfirst!
+ let l:current = bufnr()
+
+ call assert_fails("blast", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ blast!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :bmodified but :bmodified! is allowed
+func Test_bmodified()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ execute "buffer! " . l:other
+ set modified
+ execute "buffer! " . l:current
+
+ call assert_fails("bmodified", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ bmodified!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :bnext but :bnext! is allowed
+func Test_bnext()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("bnext", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ bnext!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :bprevious but :bprevious! is allowed
+func Test_bprevious()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("bprevious", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ bprevious!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :brewind but :brewind! is allowed
+func Test_brewind()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("brewind", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ brewind!
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :browse edit but :browse edit! is allowed
+func Test_browse_edit_fail()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("browse edit other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ browse edit! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Allow :browse w because it doesn't change the buffer in the current file
+func Test_browse_edit_pass()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ browse write other
+
+ call delete("other")
+endfunc
+
+" Call :bufdo and choose the next available 'nowinfixbuf' window.
+func Test_bufdo_choose_available_window()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+
+ " Make a split window that is 'nowinfixbuf' but make it the second-to-last
+ " window so that :bufdo will first try the 'winfixbuf' window, pass over it,
+ " and prefer the other 'nowinfixbuf' window, instead.
+ "
+ " +-------------------+
+ " | 'nowinfixbuf' |
+ " +-------------------+
+ " | 'winfixbuf' | <-- Cursor is here
+ " +-------------------+
+ split
+ let l:nowinfixbuf_window = win_getid()
+ " Move to the 'winfixbuf' window now
+ execute "normal \<C-w>j"
+ let l:winfixbuf_window = win_getid()
+
+ let l:current = bufnr()
+ let l:expected_windows = s:get_windows_count()
+
+ call assert_notequal(l:current, l:other)
+
+ bufdo echo ''
+ call assert_equal(l:nowinfixbuf_window, win_getid())
+ call assert_notequal(l:other, bufnr())
+ call assert_equal(l:expected_windows, s:get_windows_count())
+endfunc
+
+" Call :bufdo and create a new split window if all available windows are 'winfixbuf'.
+func Test_bufdo_make_new_window()
+ call s:reset_all_buffers()
+
+ let [l:first, l:last] = s:make_buffers_list()
+ execute "buffer! " . l:first
+ let l:current = win_getid()
+ let l:current_windows = s:get_windows_count()
+
+ bufdo echo ''
+ call assert_notequal(l:current, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:first, bufnr())
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+endfunc
+
+" Fail :buffer but :buffer! is allowed
+func Test_buffer()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("buffer " . l:other, "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ execute "buffer! " . l:other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Allow :buffer on a 'winfixbuf' window if there is no change in buffer
+func Test_buffer_same_buffer()
+ call s:reset_all_buffers()
+
+ call s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ execute "buffer " . l:current
+ call assert_equal(l:current, bufnr())
+
+ execute "buffer! " . l:current
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Allow :cNext but the 'nowinfixbuf' window is selected, instead
+func Test_cNext()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cNext` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cNext
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :cNfile but the 'nowinfixbuf' window is selected, instead
+func Test_cNfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cNfile` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+ cnext!
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cNfile
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :caddexpr because it doesn't change the current buffer
+func Test_caddexpr()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file_path = tempname()
+ call writefile(["Error - bad-thing-found"], l:file_path)
+ execute "edit " . l:file_path
+ let l:file_buffer = bufnr()
+ let l:current = bufnr()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+
+ edit! other.unittest
+
+ set winfixbuf
+
+ execute "buffer! " . l:file_buffer
+
+ execute 'caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
+ call assert_equal(l:current, bufnr())
+
+ call delete(l:file_path)
+endfunc
+
+" Fail :cbuffer but :cbuffer! is allowed
+func Test_cbuffer()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file_path = tempname()
+ call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path)
+ execute "edit " . l:file_path
+ let l:file_buffer = bufnr()
+ let l:current = bufnr()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+
+ edit! other.unittest
+
+ set winfixbuf
+
+ execute "buffer! " . l:file_buffer
+
+ call assert_fails("cbuffer " . l:file_buffer)
+ call assert_equal(l:current, bufnr())
+
+ execute "cbuffer! " . l:file_buffer
+ call assert_equal("first.unittest", expand("%:t"))
+
+ call delete(l:file_path)
+endfunc
+
+" Allow :cc but the 'nowinfixbuf' window is selected, instead
+func Test_cc()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+ " Go up one line in the quickfix window to an quickfix entry that doesn't
+ " point to a winfixbuf buffer
+ normal k
+ " Attempt to make the previous window, winfixbuf buffer, to go to the
+ " non-winfixbuf quickfix entry
+ .cc
+
+ " Confirm that :.cc did not change the winfixbuf-enabled window
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Call :cdo and choose the next available 'nowinfixbuf' window.
+func Test_cdo_choose_available_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current, l:last] = s:make_simple_quickfix()
+ execute "buffer! " . l:current
+
+ " Make a split window that is 'nowinfixbuf' but make it the second-to-last
+ " window so that :cdo will first try the 'winfixbuf' window, pass over it,
+ " and prefer the other 'nowinfixbuf' window, instead.
+ "
+ " +-------------------+
+ " | 'nowinfixbuf' |
+ " +-------------------+
+ " | 'winfixbuf' | <-- Cursor is here
+ " +-------------------+
+ split
+ let l:nowinfixbuf_window = win_getid()
+ " Move to the 'winfixbuf' window now
+ execute "normal \<C-w>j"
+ let l:winfixbuf_window = win_getid()
+ let l:expected_windows = s:get_windows_count()
+
+ cdo echo ''
+
+ call assert_equal(l:nowinfixbuf_window, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:current, bufnr())
+ call assert_equal(l:expected_windows, s:get_windows_count())
+endfunc
+
+" Call :cdo and create a new split window if all available windows are 'winfixbuf'.
+func Test_cdo_make_new_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current_buffer, l:last] = s:make_simple_quickfix()
+ execute "buffer! " . l:current_buffer
+
+ let l:current_window = win_getid()
+ let l:current_windows = s:get_windows_count()
+
+ cdo echo ''
+ call assert_notequal(l:current_window, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:current_buffer, bufnr())
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+endfunc
+
+" Fail :cexpr but :cexpr! is allowed
+func Test_cexpr()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ let l:entry = '["' . l:file . ':1:bar"]'
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("cexpr " . l:entry)
+ call assert_equal(l:current, bufnr())
+
+ execute "cexpr! " . l:entry
+ call assert_equal(fnamemodify(l:file, ":t"), expand("%:t"))
+endfunc
+
+" Call :cfdo and choose the next available 'nowinfixbuf' window.
+func Test_cfdo_choose_available_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current, l:last] = s:make_simple_quickfix()
+ execute "buffer! " . l:current
+
+ " Make a split window that is 'nowinfixbuf' but make it the second-to-last
+ " window so that :cfdo will first try the 'winfixbuf' window, pass over it,
+ " and prefer the other 'nowinfixbuf' window, instead.
+ "
+ " +-------------------+
+ " | 'nowinfixbuf' |
+ " +-------------------+
+ " | 'winfixbuf' | <-- Cursor is here
+ " +-------------------+
+ split
+ let l:nowinfixbuf_window = win_getid()
+ " Move to the 'winfixbuf' window now
+ execute "normal \<C-w>j"
+ let l:winfixbuf_window = win_getid()
+ let l:expected_windows = s:get_windows_count()
+
+ cfdo echo ''
+
+ call assert_equal(l:nowinfixbuf_window, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:current, bufnr())
+ call assert_equal(l:expected_windows, s:get_windows_count())
+endfunc
+
+" Call :cfdo and create a new split window if all available windows are 'winfixbuf'.
+func Test_cfdo_make_new_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current_buffer, l:last] = s:make_simple_quickfix()
+ execute "buffer! " . l:current_buffer
+
+ let l:current_window = win_getid()
+ let l:current_windows = s:get_windows_count()
+
+ cfdo echo ''
+ call assert_notequal(l:current_window, win_getid())
+ call assert_equal(l:last, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:current_buffer, bufnr())
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+endfunc
+
+" Fail :cfile but :cfile! is allowed
+func Test_cfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+ write
+ let l:first = bufnr()
+
+ edit! second.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ let l:file = tempname()
+ call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file)
+
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails(":cfile " . l:file)
+ call assert_equal(l:current, bufnr())
+
+ execute ":cfile! " . l:file
+ call assert_equal(l:first, bufnr())
+
+ call delete(l:file)
+ call delete("first.unittest")
+ call delete("second.unittest")
+endfunc
+
+" Allow :cfirst but the 'nowinfixbuf' window is selected, instead
+func Test_cfirst()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cfirst` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cfirst
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :clast but the 'nowinfixbuf' window is selected, instead
+func Test_clast()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:clast` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ clast
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :cnext but the 'nowinfixbuf' window is selected, instead
+" Make sure no new windows are created and previous windows are reused
+func Test_cnext()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+ let l:expected = s:get_windows_count()
+
+ " The call to `:cnext` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ cnext!
+ call assert_equal(l:expected, s:get_windows_count())
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cnext
+ call assert_equal(l:first_window, win_getid())
+ call assert_equal(l:expected, s:get_windows_count())
+endfunc
+
+" Make sure :cnext creates a split window if no previous window exists
+func Test_cnext_no_previous_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current, _] = s:make_simple_quickfix()
+ execute "buffer! " . l:current
+
+ let l:expected = s:get_windows_count()
+
+ " Open the quickfix in a separate split and go to it
+ copen
+
+ call assert_equal(l:expected + 1, s:get_windows_count())
+endfunc
+
+" Allow :cnext and create a 'nowinfixbuf' window if none exists
+func Test_cnext_make_new_window()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:current, _] = s:make_simple_quickfix()
+ let l:current = win_getid()
+
+ cfirst!
+
+ let l:windows = s:get_windows_count()
+ let l:expected = l:windows + 1 " We're about to create a new split window
+
+ cnext
+ call assert_equal(l:expected, s:get_windows_count())
+
+ cnext!
+ call assert_equal(l:expected, s:get_windows_count())
+endfunc
+
+" Allow :cprevious but the 'nowinfixbuf' window is selected, instead
+func Test_cprevious()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cprevious` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cprevious
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :cnfile but the 'nowinfixbuf' window is selected, instead
+func Test_cnfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cnfile` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+ cnext!
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cnfile
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :cpfile but the 'nowinfixbuf' window is selected, instead
+func Test_cpfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:cpfile` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+ cnext!
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ cpfile
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow :crewind but the 'nowinfixbuf' window is selected, instead
+func Test_crewind()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first_window, l:winfix_window, l:quickfix_window] = s:make_quickfix_windows()
+
+ " The call to `:crewind` succeeds but it selects the window with 'nowinfixbuf' instead
+ call s:set_quickfix_by_buffer(winbufnr(l:winfix_window))
+ cnext!
+
+ " Make sure the previous window has 'winfixbuf' so we can test that our
+ " "skip 'winfixbuf' window" logic works.
+ call win_gotoid(l:winfix_window)
+ call win_gotoid(l:quickfix_window)
+
+ crewind
+ call assert_equal(l:first_window, win_getid())
+endfunc
+
+" Allow <C-w>f because it opens in a new split
+func Test_ctrl_w_f()
+ call s:reset_all_buffers()
+
+ enew
+ let l:file_name = tempname()
+ call writefile([], l:file_name)
+ let l:file_buffer = bufnr()
+
+ enew
+ file other
+ let l:other_buffer = bufnr()
+
+ set winfixbuf
+
+ call setline(1, l:file_name)
+ let l:current_windows = s:get_windows_count()
+ execute "normal \<C-w>f"
+
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+
+ call delete(l:file_name)
+endfunc
+
+" Fail :djump but :djump! is allowed
+func Test_djump()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile(["min(1, 12);",
+ \ '#include "' . l:include_file . '"'
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("djump 1 /min/", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ djump! 1 /min/
+ call assert_notequal(l:current, bufnr())
+
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail :drop but :drop! is allowed
+func Test_drop()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("drop other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ drop! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :edit but :edit! is allowed
+func Test_edit()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("edit other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ edit! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :enew but :enew! is allowed
+func Test_enew()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("enew", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ enew!
+ call assert_notequal(l:other, bufnr())
+ call assert_notequal(3, bufnr())
+endfunc
+
+" Fail :ex but :ex! is allowed
+func Test_ex()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("ex other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ ex! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :find but :find! is allowed
+func Test_find()
+ call s:reset_all_buffers()
+
+ let l:current = bufnr()
+ let l:file = tempname()
+ call writefile([], l:file)
+ let l:directory = fnamemodify(l:file, ":p:h")
+ let l:name = fnamemodify(l:file, ":p:t")
+
+ let l:original_path = &path
+ execute "set path=" . l:directory
+
+ set winfixbuf
+
+ call assert_fails("execute 'find " . l:name . "'", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ execute "find! " . l:name
+ call assert_equal(l:file, expand("%:p"))
+
+ execute "set path=" . l:original_path
+ call delete(l:file)
+endfunc
+
+" Fail :first but :first! is allowed
+func Test_first()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("first", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ first!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :grep but :grep! is allowed
+func Test_grep()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:first = bufnr()
+
+ edit current.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:current = bufnr()
+
+ edit! last.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:last = bufnr()
+
+ set winfixbuf
+
+ buffer! current.unittest
+
+ call assert_fails("silent! grep some-search-term *.unittest", "E1513:")
+ call assert_equal(l:current, bufnr())
+ execute "edit! " . l:first
+
+ silent! grep! some-search-term *.unittest
+ call assert_notequal(l:first, bufnr())
+
+ call delete("first.unittest")
+ call delete("current.unittest")
+ call delete("last.unittest")
+endfunc
+
+" Fail :ijump but :ijump! is allowed
+func Test_ijump()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile([
+ \ '#include "' . l:include_file . '"'
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ set define=^\\s*#\\s*define
+ set include=^\\s*#\\s*include
+ set path=.,/usr/include,,
+
+ call assert_fails("ijump /min/", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set nowinfixbuf
+
+ ijump! /min/
+ call assert_notequal(l:current, bufnr())
+
+ set define&
+ set include&
+ set path&
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail :lNext but :lNext! is allowed
+func Test_lNext()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, _] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lNext", "E1513:")
+ call assert_equal(l:middle, bufnr())
+
+ lnext! " Reset for the next test
+
+ lNext!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :lNfile but :lNfile! is allowed
+func Test_lNfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:current, _] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lNfile", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ lnext! " Reset for the next test
+
+ lNfile!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Allow :laddexpr because it doesn't change the current buffer
+func Test_laddexpr()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file_path = tempname()
+ call writefile(["Error - bad-thing-found"], l:file_path)
+ execute "edit " . l:file_path
+ let l:file_buffer = bufnr()
+ let l:current = bufnr()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+
+ edit! other.unittest
+
+ set winfixbuf
+
+ execute "buffer! " . l:file_buffer
+
+ execute 'laddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".")'
+ call assert_equal(l:current, bufnr())
+
+ call delete(l:file_path)
+endfunc
+
+" Fail :last but :last! is allowed
+func Test_last()
+ call s:reset_all_buffers()
+
+ let [_, l:last] = s:make_args_list()
+ next!
+
+ call assert_fails("last", "E1513:")
+ call assert_notequal(l:last, bufnr())
+
+ last!
+ call assert_equal(l:last, bufnr())
+endfunc
+
+" Fail :lbuffer but :lbuffer! is allowed
+func Test_lbuffer()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file_path = tempname()
+ call writefile(["first.unittest:1:Error - bad-thing-found"], l:file_path)
+ execute "edit " . l:file_path
+ let l:file_buffer = bufnr()
+ let l:current = bufnr()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+
+ edit! other.unittest
+
+ set winfixbuf
+
+ execute "buffer! " . l:file_buffer
+
+ call assert_fails("lbuffer " . l:file_buffer)
+ call assert_equal(l:current, bufnr())
+
+ execute "lbuffer! " . l:file_buffer
+ call assert_equal("first.unittest", expand("%:t"))
+
+ call delete(l:file_path)
+endfunc
+
+" Fail :ldo but :ldo! is allowed
+func Test_ldo()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, l:last] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails('execute "ldo buffer ' . l:first . '"', "E1513:")
+ call assert_equal(l:middle, bufnr())
+ execute "ldo! buffer " . l:first
+ call assert_notequal(l:last, bufnr())
+endfunc
+
+" Fail :lfdo but :lfdo! is allowed
+func Test_lexpr()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ let l:entry = '["' . l:file . ':1:bar"]'
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("lexpr " . l:entry)
+ call assert_equal(l:current, bufnr())
+
+ execute "lexpr! " . l:entry
+ call assert_equal(fnamemodify(l:file, ":t"), expand("%:t"))
+endfunc
+
+" Fail :lfdo but :lfdo! is allowed
+func Test_lfdo()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, l:last] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails('execute "lfdo buffer ' . l:first . '"', "E1513:")
+ call assert_equal(l:middle, bufnr())
+ execute "lfdo! buffer " . l:first
+ call assert_notequal(l:last, bufnr())
+endfunc
+
+" Fail :lfile but :lfile! is allowed
+func Test_lfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term bad-thing-found"])
+ write
+ let l:first = bufnr()
+
+ edit! second.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ let l:file = tempname()
+ call writefile(["first.unittest:1:Error - bad-thing-found was detected"], l:file)
+
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails(":lfile " . l:file)
+ call assert_equal(l:current, bufnr())
+
+ execute ":lfile! " . l:file
+ call assert_equal(l:first, bufnr())
+
+ call delete(l:file)
+ call delete("first.unittest")
+ call delete("second.unittest")
+endfunc
+
+" Fail :ll but :ll! is allowed
+func Test_ll()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, l:last] = s:make_simple_location_list()
+ lopen
+ lfirst!
+ execute "normal \<C-w>j"
+ normal j
+
+ call assert_fails(".ll", "E1513:")
+ execute "normal \<C-w>k"
+ call assert_equal(l:first, bufnr())
+ execute "normal \<C-w>j"
+ .ll!
+ execute "normal \<C-w>k"
+ call assert_equal(l:middle, bufnr())
+endfunc
+
+" Fail :llast but :llast! is allowed
+func Test_llast()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, _, l:last] = s:make_simple_location_list()
+ lfirst!
+
+ call assert_fails("llast", "E1513:")
+ call assert_equal(l:first, bufnr())
+
+ llast!
+ call assert_equal(l:last, bufnr())
+endfunc
+
+" Fail :lnext but :lnext! is allowed
+func Test_lnext()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, l:last] = s:make_simple_location_list()
+ ll!
+
+ call assert_fails("lnext", "E1513:")
+ call assert_equal(l:first, bufnr())
+
+ lnext!
+ call assert_equal(l:middle, bufnr())
+endfunc
+
+" Fail :lnfile but :lnfile! is allowed
+func Test_lnfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [_, l:current, l:last] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lnfile", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ lprevious! " Reset for the next test call
+
+ lnfile!
+ call assert_equal(l:last, bufnr())
+endfunc
+
+" Fail :lpfile but :lpfile! is allowed
+func Test_lpfile()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:current, _] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lpfile", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ lnext! " Reset for the next test call
+
+ lpfile!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :lprevious but :lprevious! is allowed
+func Test_lprevious()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, _] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lprevious", "E1513:")
+ call assert_equal(l:middle, bufnr())
+
+ lnext! " Reset for the next test call
+
+ lprevious!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :lrewind but :lrewind! is allowed
+func Test_lrewind()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ let [l:first, l:middle, _] = s:make_simple_location_list()
+ lnext!
+
+ call assert_fails("lrewind", "E1513:")
+ call assert_equal(l:middle, bufnr())
+
+ lrewind!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail :ltag but :ltag! is allowed
+func Test_ltag()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+ execute "normal \<C-]>"
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("ltag one", "E1513:")
+
+ ltag! one
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail vim.cmd if we try to change buffers while 'winfixbuf' is set
+func Test_lua_command()
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let l:previous = bufnr()
+
+ enew
+ file second
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails('lua vim.cmd("buffer " .. ' . l:previous . ')')
+ call assert_equal(l:current, bufnr())
+
+ execute 'lua vim.cmd("buffer! " .. ' . l:previous . ')'
+ call assert_equal(l:previous, bufnr())
+endfunc
+
+" Fail :lvimgrep but :lvimgrep! is allowed
+func Test_lvimgrep()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ edit winfix.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ edit! last.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:last = bufnr()
+
+ buffer! winfix.unittest
+
+ call assert_fails("lvimgrep /some-search-term/ *.unittest", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ lvimgrep! /some-search-term/ *.unittest
+ call assert_notequal(l:current, bufnr())
+
+ call delete("first.unittest")
+ call delete("winfix.unittest")
+ call delete("last.unittest")
+endfunc
+
+" Fail :lvimgrepadd but :lvimgrepadd! is allowed
+func Test_lvimgrepadd()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ edit winfix.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ edit! last.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:last = bufnr()
+
+ buffer! winfix.unittest
+
+ call assert_fails("lvimgrepadd /some-search-term/ *.unittest")
+ call assert_equal(l:current, bufnr())
+
+ lvimgrepadd! /some-search-term/ *.unittest
+ call assert_notequal(l:current, bufnr())
+
+ call delete("first.unittest")
+ call delete("winfix.unittest")
+ call delete("last.unittest")
+endfunc
+
+" Don't allow global marks to change the current 'winfixbuf' window
+func Test_marks_mappings_fail()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+ execute "buffer! " . l:other
+ normal mA
+ execute "buffer! " . l:current
+ normal mB
+
+ call assert_fails("normal `A", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ call assert_fails("normal 'A", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set nowinfixbuf
+
+ normal `A
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Allow global marks in a 'winfixbuf' window if the jump is the same buffer
+func Test_marks_mappings_pass_intra_move()
+ call s:reset_all_buffers()
+
+ let l:current = bufnr()
+ call append(0, ["some line", "another line"])
+ normal mA
+ normal j
+ normal mB
+
+ set winfixbuf
+
+ normal `A
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Fail :next but :next! is allowed
+func Test_next()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ first!
+
+ call assert_fails("next", "E1513:")
+ call assert_equal(l:first, bufnr())
+
+ next!
+ call assert_notequal(l:first, bufnr())
+endfunc
+
+" Ensure :mksession saves 'winfixbuf' details
+func Test_mksession()
+ CheckFeature mksession
+ call s:reset_all_buffers()
+
+ set sessionoptions+=options
+ set winfixbuf
+
+ mksession test_winfixbuf_Test_mksession.vim
+
+ call s:reset_all_buffers()
+ let l:winfixbuf = &winfixbuf
+ call assert_equal(0, l:winfixbuf)
+
+ source test_winfixbuf_Test_mksession.vim
+
+ let l:winfixbuf = &winfixbuf
+ call assert_equal(1, l:winfixbuf)
+
+ set sessionoptions&
+ call delete("test_winfixbuf_Test_mksession.vim")
+endfunc
+
+" Allow :next if the next index is the same as the current buffer
+func Test_next_same_buffer()
+ call s:reset_all_buffers()
+
+ enew
+ file foo
+ enew
+ file bar
+ enew
+ file fizz
+ enew
+ file buzz
+ args foo foo bar fizz buzz
+
+ edit foo
+ set winfixbuf
+ let l:current = bufnr()
+
+ " Allow :next because the args list is `[foo] foo bar fizz buzz
+ next
+ call assert_equal(l:current, bufnr())
+
+ " Fail :next because the args list is `foo [foo] bar fizz buzz
+ " and the next buffer would be bar, which is a different buffer
+ call assert_fails("next", "E1513:")
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Fail to jump to a tag with g<C-]> if 'winfixbuf' is enabled
+func Test_normal_g_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal g\<C-]>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with g<RightMouse> if 'winfixbuf' is enabled
+func Test_normal_g_rightmouse()
+ call s:reset_all_buffers()
+ set mouse=n
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+ execute "normal \<C-]>"
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal g\<RightMouse>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ set mouse&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with g] if 'winfixbuf' is enabled
+func Test_normal_g_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal g]", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with <C-RightMouse> if 'winfixbuf' is enabled
+func Test_normal_ctrl_rightmouse()
+ call s:reset_all_buffers()
+ set mouse=n
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+ execute "normal \<C-]>"
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal \<C-RightMouse>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ set mouse&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with <C-t> if 'winfixbuf' is enabled
+func Test_normal_ctrl_t()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+ execute "normal \<C-]>"
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal \<C-t>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Disallow <C-^> in 'winfixbuf' windows
+func Test_normal_ctrl_hat()
+ call s:reset_all_buffers()
+ clearjumps
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file current
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("normal \<C-^>", "E1513:")
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Allow <C-i> in 'winfixbuf' windows if the movement stays within the buffer
+func Test_normal_ctrl_i_pass()
+ call s:reset_all_buffers()
+ clearjumps
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew!
+ file current
+ let l:current = bufnr()
+ " Add some lines so we can populate a jumplist"
+ call append(0, ["some line", "another line"])
+ " Add an entry to the jump list
+ " Go up another line
+ normal m`
+ normal k
+ execute "normal \<C-o>"
+
+ set winfixbuf
+
+ let l:line = getcurpos()[1]
+ execute "normal 1\<C-i>"
+ call assert_notequal(l:line, getcurpos()[1])
+endfunc
+
+" Disallow <C-o> in 'winfixbuf' windows if it would cause the buffer to switch
+func Test_normal_ctrl_o_fail()
+ call s:reset_all_buffers()
+ clearjumps
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file current
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("normal \<C-o>", "E1513:")
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Allow <C-o> in 'winfixbuf' windows if the movement stays within the buffer
+func Test_normal_ctrl_o_pass()
+ call s:reset_all_buffers()
+ clearjumps
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew!
+ file current
+ let l:current = bufnr()
+ " Add some lines so we can populate a jumplist
+ call append(0, ["some line", "another line"])
+ " Add an entry to the jump list
+ " Go up another line
+ normal m`
+ normal k
+
+ set winfixbuf
+
+ execute "normal \<C-o>"
+ call assert_equal(l:current, bufnr())
+endfunc
+
+" Fail to jump to a tag with <C-]> if 'winfixbuf' is enabled
+func Test_normal_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal \<C-]>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Allow <C-w><C-]> with 'winfixbuf' enabled because it runs in a new, split window
+func Test_normal_ctrl_w_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current_windows = s:get_windows_count()
+ execute "normal \<C-w>\<C-]>"
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Allow <C-w>g<C-]> with 'winfixbuf' enabled because it runs in a new, split window
+func Test_normal_ctrl_w_g_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current_windows = s:get_windows_count()
+ execute "normal \<C-w>g\<C-]>"
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with <C-]> if 'winfixbuf' is enabled
+func Test_normal_gt()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one", "two", "three"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal \<C-]>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Prevent gF from switching a 'winfixbuf' window's buffer
+func Test_normal_gF()
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ call append(0, [l:file])
+ call writefile([], l:file)
+ " Place the cursor onto the line that has `l:file`
+ normal gg
+ " Prevent Vim from erroring with "No write since last change @ command
+ " line" when we try to call gF, later.
+ set hidden
+
+ set winfixbuf
+
+ let l:buffer = bufnr()
+
+ call assert_fails("normal gF", "E1513:")
+ call assert_equal(l:buffer, bufnr())
+
+ set nowinfixbuf
+
+ normal gF
+ call assert_notequal(l:buffer, bufnr())
+
+ call delete(l:file)
+endfunc
+
+" Prevent gf from switching a 'winfixbuf' window's buffer
+func Test_normal_gf()
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ call append(0, [l:file])
+ call writefile([], l:file)
+ " Place the cursor onto the line that has `l:file`
+ normal gg
+ " Prevent Vim from erroring with "No write since last change @ command
+ " line" when we try to call gf, later.
+ set hidden
+
+ set winfixbuf
+
+ let l:buffer = bufnr()
+
+ call assert_fails("normal gf", "E1513:")
+ call assert_equal(l:buffer, bufnr())
+
+ set nowinfixbuf
+
+ normal gf
+ call assert_notequal(l:buffer, bufnr())
+
+ call delete(l:file)
+endfunc
+
+" Fail "goto file under the cursor" (using [f, which is the same as `:normal gf`)
+func Test_normal_square_bracket_left_f()
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ call append(0, [l:file])
+ call writefile([], l:file)
+ " Place the cursor onto the line that has `l:file`
+ normal gg
+ " Prevent Vim from erroring with "No write since last change @ command
+ " line" when we try to call gf, later.
+ set hidden
+
+ set winfixbuf
+
+ let l:buffer = bufnr()
+
+ call assert_fails("normal [f", "E1513:")
+ call assert_equal(l:buffer, bufnr())
+
+ set nowinfixbuf
+
+ normal [f
+ call assert_notequal(l:buffer, bufnr())
+
+ call delete(l:file)
+endfunc
+
+" Fail to go to a C macro with [<C-d> if 'winfixbuf' is enabled
+func Test_normal_square_bracket_left_ctrl_d()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile(["min(1, 12);",
+ \ '#include "' . l:include_file . '"'
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+ normal ]\<C-d>
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal [\<C-d>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set nowinfixbuf
+
+ execute "normal [\<C-d>"
+ call assert_notequal(l:current, bufnr())
+
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail to go to a C macro with ]<C-d> if 'winfixbuf' is enabled
+func Test_normal_square_bracket_right_ctrl_d()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile(["min(1, 12);",
+ \ '#include "' . l:include_file . '"'
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal ]\<C-d>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set nowinfixbuf
+
+ execute "normal ]\<C-d>"
+ call assert_notequal(l:current, bufnr())
+
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail to go to a C macro with [<C-i> if 'winfixbuf' is enabled
+func Test_normal_square_bracket_left_ctrl_i()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile(['#include "' . l:include_file . '"',
+ \ "min(1, 12);",
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+ " Move to the line with `min(1, 12);` on it"
+ normal j
+
+ set define=^\\s*#\\s*define
+ set include=^\\s*#\\s*include
+ set path=.,/usr/include,,
+
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("normal [\<C-i>", "E1513:")
+
+ set nowinfixbuf
+
+ execute "normal [\<C-i>"
+ call assert_notequal(l:current, bufnr())
+
+ set define&
+ set include&
+ set path&
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail to go to a C macro with ]<C-i> if 'winfixbuf' is enabled
+func Test_normal_square_bracket_right_ctrl_i()
+ call s:reset_all_buffers()
+
+ let l:include_file = tempname() . ".h"
+ call writefile(["min(1, 12);",
+ \ '#include "' . l:include_file . '"'
+ \ ],
+ \ "main.c")
+ call writefile(["#define min(X, Y) ((X) < (Y) ? (X) : (Y))"], l:include_file)
+ edit main.c
+
+ set winfixbuf
+
+ set define=^\\s*#\\s*define
+ set include=^\\s*#\\s*include
+ set path=.,/usr/include,,
+
+ let l:current = bufnr()
+
+ call assert_fails("normal ]\<C-i>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set nowinfixbuf
+
+ execute "normal ]\<C-i>"
+ call assert_notequal(l:current, bufnr())
+
+ set define&
+ set include&
+ set path&
+ call delete("main.c")
+ call delete(l:include_file)
+endfunc
+
+" Fail "goto file under the cursor" (using ]f, which is the same as `:normal gf`)
+func Test_normal_square_bracket_right_f()
+ call s:reset_all_buffers()
+
+ let l:file = tempname()
+ call append(0, [l:file])
+ call writefile([], l:file)
+ " Place the cursor onto the line that has `l:file`
+ normal gg
+ " Prevent Vim from erroring with "No write since last change @ command
+ " line" when we try to call gf, later.
+ set hidden
+
+ set winfixbuf
+
+ let l:buffer = bufnr()
+
+ call assert_fails("normal ]f", "E1513:")
+ call assert_equal(l:buffer, bufnr())
+
+ set nowinfixbuf
+
+ normal ]f
+ call assert_notequal(l:buffer, bufnr())
+
+ call delete(l:file)
+endfunc
+
+" Fail to jump to a tag with v<C-]> if 'winfixbuf' is enabled
+func Test_normal_v_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal v\<C-]>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail to jump to a tag with vg<C-]> if 'winfixbuf' is enabled
+func Test_normal_v_g_ctrl_square_bracket_right()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("normal vg\<C-]>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Allow :pedit because, unlike :edit, it uses a separate window
+func Test_pedit()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+
+ pedit other
+
+ execute "normal \<C-w>w"
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :pop but :pop! is allowed
+func Test_pop()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "thesame\tXfile\t1;\"\td\tfile:",
+ \ "thesame\tXfile\t2;\"\td\tfile:",
+ \ "thesame\tXfile\t3;\"\td\tfile:",
+ \ ],
+ \ "Xtags")
+ call writefile(["thesame one", "thesame two", "thesame three"], "Xfile")
+ call writefile(["thesame one"], "Xother")
+ edit Xother
+
+ tag thesame
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("pop", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ pop!
+ call assert_notequal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail :previous but :previous! is allowed
+func Test_previous()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("previous", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ previous!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Fail pydo if it changes a window with 'winfixbuf' is set
+func Test_python_pydo()
+ CheckFeature pythonx
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let g:_previous_buffer = bufnr()
+
+ enew
+ file second
+
+ set winfixbuf
+
+ python << EOF
+import vim
+
+def test_winfixbuf_Test_python_pydo_set_buffer():
+ buffer = vim.vars['_previous_buffer']
+ vim.current.buffer = vim.buffers[buffer]
+EOF
+
+ try
+ pydo test_winfixbuf_Test_python_pydo_set_buffer()
+ catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/
+ let l:caught = 1
+ endtry
+
+ call assert_equal(1, l:caught)
+
+ unlet g:_previous_buffer
+endfunc
+
+" Fail pyfile if it changes a window with 'winfixbuf' is set
+func Test_python_pyfile()
+ CheckFeature pythonx
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let g:_previous_buffer = bufnr()
+
+ enew
+ file second
+
+ set winfixbuf
+
+ call writefile(["import vim",
+ \ "buffer = vim.vars['_previous_buffer']",
+ \ "vim.current.buffer = vim.buffers[buffer]",
+ \ ],
+ \ "file.py")
+
+ try
+ pyfile file.py
+ catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/
+ let l:caught = 1
+ endtry
+
+ call assert_equal(1, l:caught)
+
+ call delete("file.py")
+ unlet g:_previous_buffer
+endfunc
+
+" Fail vim.current.buffer if 'winfixbuf' is set
+func Test_python_vim_current_buffer()
+ CheckFeature pythonx
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let g:_previous_buffer = bufnr()
+
+ enew
+ file second
+
+ let l:caught = 0
+
+ set winfixbuf
+
+ try
+ python << EOF
+import vim
+
+buffer = vim.vars["_previous_buffer"]
+vim.current.buffer = vim.buffers[buffer]
+EOF
+ catch /pynvim\.api\.common\.NvimError: E1513: Cannot edit buffer\. 'winfixbuf' is enabled/
+ let l:caught = 1
+ endtry
+
+ call assert_equal(1, l:caught)
+ unlet g:_previous_buffer
+endfunc
+
+" Ensure remapping to a disabled action still triggers failures
+func Test_remap_key_fail()
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file current
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ nnoremap g <C-^>
+
+ call assert_fails("normal g", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ nunmap g
+endfunc
+
+" Ensure remapping a disabled key to something valid does trigger any failures
+func Test_remap_key_pass()
+ call s:reset_all_buffers()
+
+ enew
+ file first
+ let l:first = bufnr()
+
+ enew
+ file current
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ call assert_fails("normal \<C-^>", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ " Disallow <C-^> by default but allow it if the command does something else
+ nnoremap <C-^> :echo "hello!"
+
+ execute "normal \<C-^>"
+ call assert_equal(l:current, bufnr())
+
+ nunmap <C-^>
+endfunc
+
+" Fail :rewind but :rewind! is allowed
+func Test_rewind()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("rewind", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ rewind!
+ call assert_equal(l:first, bufnr())
+endfunc
+
+" Allow :sblast because it opens the buffer in a new, split window
+func Test_sblast()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs(1)
+ bfirst!
+ let l:current = bufnr()
+
+ sblast
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :sbprevious but :sbprevious! is allowed
+func Test_sbprevious()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ sbprevious
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Make sure 'winfixbuf' can be set using 'winfixbuf' or 'wfb'
+func Test_short_option()
+ call s:reset_all_buffers()
+
+ call s:make_buffer_pairs()
+
+ set winfixbuf
+ call assert_fails("edit something_else", "E1513")
+
+ set nowinfixbuf
+ set wfb
+ call assert_fails("edit another_place", "E1513")
+
+ set nowfb
+ edit last_place
+endfunc
+
+" Allow :snext because it makes a new window
+func Test_snext()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ first!
+
+ let l:current_window = win_getid()
+
+ snext
+ call assert_notequal(l:current_window, win_getid())
+ call assert_notequal(l:first, bufnr())
+endfunc
+
+" Ensure the first has 'winfixbuf' and a new split window is 'nowinfixbuf'
+func Test_split_window()
+ call s:reset_all_buffers()
+
+ split
+ execute "normal \<C-w>j"
+
+ set winfixbuf
+
+ let l:winfix_window_1 = win_getid()
+ vsplit
+ let l:winfix_window_2 = win_getid()
+
+ call assert_equal(1, getwinvar(l:winfix_window_1, "&winfixbuf"))
+ call assert_equal(0, getwinvar(l:winfix_window_2, "&winfixbuf"))
+endfunc
+
+" Fail :tNext but :tNext! is allowed
+func Test_tNext()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "thesame\tXfile\t1;\"\td\tfile:",
+ \ "thesame\tXfile\t2;\"\td\tfile:",
+ \ "thesame\tXfile\t3;\"\td\tfile:",
+ \ ],
+ \ "Xtags")
+ call writefile(["thesame one", "thesame two", "thesame three"], "Xfile")
+ call writefile(["thesame one"], "Xother")
+ edit Xother
+
+ tag thesame
+ execute "normal \<C-^>"
+ tnext!
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tNext", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tNext!
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Call :tabdo and choose the next available 'nowinfixbuf' window.
+func Test_tabdo_choose_available_window()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+
+ " Make a split window that is 'nowinfixbuf' but make it the second-to-last
+ " window so that :tabdo will first try the 'winfixbuf' window, pass over it,
+ " and prefer the other 'nowinfixbuf' window, instead.
+ "
+ " +-------------------+
+ " | 'nowinfixbuf' |
+ " +-------------------+
+ " | 'winfixbuf' | <-- Cursor is here
+ " +-------------------+
+ split
+ let l:nowinfixbuf_window = win_getid()
+ " Move to the 'winfixbuf' window now
+ execute "normal \<C-w>j"
+ let l:winfixbuf_window = win_getid()
+
+ let l:expected_windows = s:get_windows_count()
+ tabdo echo ''
+ call assert_equal(l:nowinfixbuf_window, win_getid())
+ call assert_equal(l:first, bufnr())
+ call assert_equal(l:expected_windows, s:get_windows_count())
+endfunc
+
+" Call :tabdo and create a new split window if all available windows are 'winfixbuf'.
+func Test_tabdo_make_new_window()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_buffers_list()
+ execute "buffer! " . l:first
+
+ let l:current = win_getid()
+ let l:current_windows = s:get_windows_count()
+
+ tabdo echo ''
+ call assert_notequal(l:current, win_getid())
+ call assert_equal(l:first, bufnr())
+ execute "normal \<C-w>j"
+ call assert_equal(l:first, bufnr())
+ call assert_equal(l:current_windows + 1, s:get_windows_count())
+endfunc
+
+" Fail :tag but :tag! is allowed
+func Test_tag()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tag one", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tag! one
+ call assert_notequal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+
+" Fail :tfirst but :tfirst! is allowed
+func Test_tfirst()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tfirst", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tfirst!
+ call assert_notequal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail :tjump but :tjump! is allowed
+func Test_tjump()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ call writefile(["one"], "Xother")
+ edit Xother
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tjump one", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tjump! one
+ call assert_notequal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail :tlast but :tlast! is allowed
+func Test_tlast()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "one\tXfile\t1",
+ \ "three\tXfile\t3",
+ \ "two\tXfile\t2"],
+ \ "Xtags")
+ call writefile(["one", "two", "three"], "Xfile")
+ edit Xfile
+ tjump one
+ edit Xfile
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tlast", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tlast!
+ call assert_equal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+endfunc
+
+" Fail :tnext but :tnext! is allowed
+func Test_tnext()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "thesame\tXfile\t1;\"\td\tfile:",
+ \ "thesame\tXfile\t2;\"\td\tfile:",
+ \ "thesame\tXfile\t3;\"\td\tfile:",
+ \ ],
+ \ "Xtags")
+ call writefile(["thesame one", "thesame two", "thesame three"], "Xfile")
+ call writefile(["thesame one"], "Xother")
+ edit Xother
+
+ tag thesame
+ execute "normal \<C-^>"
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tnext", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tnext!
+ call assert_notequal(l:current, bufnr())
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail :tprevious but :tprevious! is allowed
+func Test_tprevious()
+ call s:reset_all_buffers()
+
+ set tags=Xtags
+ call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//",
+ \ "thesame\tXfile\t1;\"\td\tfile:",
+ \ "thesame\tXfile\t2;\"\td\tfile:",
+ \ "thesame\tXfile\t3;\"\td\tfile:",
+ \ ],
+ \ "Xtags")
+ call writefile(["thesame one", "thesame two", "thesame three"], "Xfile")
+ call writefile(["thesame one"], "Xother")
+ edit Xother
+
+ tag thesame
+ execute "normal \<C-^>"
+ tnext!
+
+ set winfixbuf
+
+ let l:current = bufnr()
+
+ call assert_fails("tprevious", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ tprevious!
+
+ set tags&
+ call delete("Xtags")
+ call delete("Xfile")
+ call delete("Xother")
+endfunc
+
+" Fail :view but :view! is allowed
+func Test_view()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("view other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ view! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :visual but :visual! is allowed
+func Test_visual()
+ call s:reset_all_buffers()
+
+ let l:other = s:make_buffer_pairs()
+ let l:current = bufnr()
+
+ call assert_fails("visual other", "E1513:")
+ call assert_equal(l:current, bufnr())
+
+ visual! other
+ call assert_equal(l:other, bufnr())
+endfunc
+
+" Fail :vimgrep but :vimgrep! is allowed
+func Test_vimgrep()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ edit winfix.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ edit! last.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:last = bufnr()
+
+ buffer! winfix.unittest
+
+ call assert_fails("vimgrep /some-search-term/ *.unittest")
+ call assert_equal(l:current, bufnr())
+
+ " Don't error and also do swap to the first match because ! was included
+ vimgrep! /some-search-term/ *.unittest
+ call assert_notequal(l:current, bufnr())
+
+ call delete("first.unittest")
+ call delete("winfix.unittest")
+ call delete("last.unittest")
+endfunc
+
+" Fail :vimgrepadd but ::vimgrepadd! is allowed
+func Test_vimgrepadd()
+ CheckFeature quickfix
+ call s:reset_all_buffers()
+
+ edit first.unittest
+ call append(0, ["some-search-term"])
+ write
+
+ edit winfix.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:current = bufnr()
+
+ set winfixbuf
+
+ edit! last.unittest
+ call append(0, ["some-search-term"])
+ write
+ let l:last = bufnr()
+
+ buffer! winfix.unittest
+
+ call assert_fails("vimgrepadd /some-search-term/ *.unittest")
+ call assert_equal(l:current, bufnr())
+
+ vimgrepadd! /some-search-term/ *.unittest
+ call assert_notequal(l:current, bufnr())
+ call delete("first.unittest")
+ call delete("winfix.unittest")
+ call delete("last.unittest")
+endfunc
+
+" Fail :wNext but :wNext! is allowed
+func Test_wNext()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("wNext", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ wNext!
+ call assert_equal(l:first, bufnr())
+
+ call delete("first")
+ call delete("middle")
+ call delete("last")
+endfunc
+
+" Allow :windo unless `:windo foo` would change a 'winfixbuf' window's buffer
+func Test_windo()
+ call s:reset_all_buffers()
+
+ let l:current_window = win_getid()
+ let l:current_buffer = bufnr()
+ split
+ enew
+ file some_other_buffer
+
+ set winfixbuf
+
+ let l:current = win_getid()
+
+ windo echo ''
+ call assert_equal(l:current_window, win_getid())
+
+ call assert_fails('execute "windo buffer ' . l:current_buffer . '"', "E1513:")
+ call assert_equal(l:current_window, win_getid())
+
+ execute "windo buffer! " . l:current_buffer
+ call assert_equal(l:current_window, win_getid())
+endfunc
+
+" Fail :wnext but :wnext! is allowed
+func Test_wnext()
+ call s:reset_all_buffers()
+
+ let [_, l:last] = s:make_args_list()
+ next!
+
+ call assert_fails("wnext", "E1513:")
+ call assert_notequal(l:last, bufnr())
+
+ wnext!
+ call assert_equal(l:last, bufnr())
+
+ call delete("first")
+ call delete("middle")
+ call delete("last")
+endfunc
+
+" Fail :wprevious but :wprevious! is allowed
+func Test_wprevious()
+ call s:reset_all_buffers()
+
+ let [l:first, _] = s:make_args_list()
+ next!
+
+ call assert_fails("wprevious", "E1513:")
+ call assert_notequal(l:first, bufnr())
+
+ wprevious!
+ call assert_equal(l:first, bufnr())
+
+ call delete("first")
+ call delete("middle")
+ call delete("last")
+endfunc