]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0147: Cannot keep a buffer focused in a window v9.1.0147
authorColin Kennedy <colinvfx@gmail.com>
Sun, 3 Mar 2024 15:16:47 +0000 (16:16 +0100)
committerChristian Brabandt <cb@256bit.org>
Sun, 3 Mar 2024 15:16:47 +0000 (16:16 +0100)
Problem:  Cannot keep a buffer focused in a window
          (Amit Levy)
Solution: Add the 'winfixbuf' window-local option
          (Colin Kennedy)

fixes:  #6445
closes: #13903

Signed-off-by: Colin Kennedy <colinvfx@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
29 files changed:
runtime/doc/message.txt
runtime/doc/options.txt
runtime/doc/quickref.txt
runtime/doc/tags
runtime/doc/tagsrch.txt
runtime/doc/version9.txt
runtime/optwin.vim
src/arglist.c
src/buffer.c
src/errors.h
src/ex_cmds.c
src/ex_cmds.h
src/ex_cmds2.c
src/ex_docmd.c
src/insexpand.c
src/normal.c
src/option.c
src/option.h
src/optiondefs.h
src/proto/search.pro
src/proto/window.pro
src/quickfix.c
src/search.c
src/structs.h
src/tag.c
src/testdir/Make_all.mak
src/testdir/test_winfixbuf.vim [new file with mode: 0644]
src/version.c
src/window.c

index 133d47ad11955451dbba00bce8e09d891d35c9d3..58480740eda64576e6e39523e993bb1f8d4e77c0 100644 (file)
@@ -1,4 +1,4 @@
-*message.txt*   For Vim version 9.1.  Last change: 2023 Dec 20
+*message.txt*   For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -122,6 +122,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 ~
 
index 210bfdcbd6d9f887656ce1560a5bc60fe2c89647..e38aa81a808c4ba57df2409d7ada1e9934094a23 100644 (file)
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2024 Feb 24
+*options.txt*  For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -8021,6 +8021,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)
@@ -9471,6 +9473,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'*
+'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|
index dcbb52013bcb86b823d9103f7ae29498c886d0ba..517fa30426c1591d9d3e38fabe1362d3e7629a69 100644 (file)
@@ -1,4 +1,4 @@
-*quickref.txt*  For Vim version 9.1.  Last change: 2023 Dec 05
+*quickref.txt*  For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -1005,6 +1005,7 @@ Short explanation of each option:         *option-list*
 'winaltkeys'     'wak'     when the windows system handles ALT keys
 'wincolor'       'wcr'     window-local highlighting
 '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
index 8af0b573a5e6084ae6f18c0a703c7a53957b2f77..efecedfb2a0f0cbaf3e9871bc6fd5eec84f38527 100644 (file)
@@ -1294,6 +1294,7 @@ $quote    eval.txt        /*$quote*
 'winaltkeys'   options.txt     /*'winaltkeys'*
 'wincolor'     options.txt     /*'wincolor'*
 'window'       options.txt     /*'window'*
+'winfixbuf'    options.txt     /*'winfixbuf'*
 'winfixheight' options.txt     /*'winfixheight'*
 'winfixwidth'  options.txt     /*'winfixwidth'*
 'winheight'    options.txt     /*'winheight'*
@@ -4541,6 +4542,7 @@ E151      helphelp.txt    /*E151*
 E1510  change.txt      /*E1510*
 E1511  options.txt     /*E1511*
 E1512  options.txt     /*E1512*
+E1513  message.txt     /*E1513*
 E152   helphelp.txt    /*E152*
 E153   helphelp.txt    /*E153*
 E154   helphelp.txt    /*E154*
index aa7b9dd48d30eb50cc9319dc2013b6659b093a2f..ce6d44634b263499b35a7bdfc1caaef974a9b28e 100644 (file)
@@ -1,4 +1,4 @@
-*tagsrch.txt*   For Vim version 9.1.  Last change: 2023 Feb 13
+*tagsrch.txt*   For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -409,17 +409,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
@@ -435,6 +440,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
index 7b9a3664a03b03d69c7cbebf057fea9112659e7d..7947cb281ca277fe47bd08c10fab22c463fe0156 100644 (file)
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Feb 21
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Mar 03
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41575,6 +41575,8 @@ Commands: ~
 
 Options: ~
 
+'winfixbuf'            Keep buffer focused in a window
+
 ==============================================================================
 INCOMPATIBLE CHANGES                           *incompatible-9.2*
 
index 6e133ce6732c4ba3c78bf36d02ffbba6c1d4a83b..d3e1605ed062f46a19b24fa27b52ac8d4d74a101 100644 (file)
@@ -482,6 +482,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\""))
@@ -490,6 +491,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")
index 723133254acd0bdf506672276d67bf6447c6cd05..187e16e8354b14805f0217dfed4a3351097c6905 100644 (file)
@@ -682,6 +682,7 @@ do_argfile(exarg_T *eap, int argn)
     int                other;
     char_u     *p;
     int                old_arg_idx = curwin->w_arg_idx;
+    int is_split_cmd = *eap->cmd == 's';
 
     if (ERROR_IF_ANY_POPUP_WINDOW)
        return;
@@ -697,13 +698,18 @@ 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();
 #ifdef FEAT_GUI
     need_mouse_correct = TRUE;
 #endif
 
     // 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;
index 8d62e643686f392aa0a5f6b03069677478560c4b..36396e8d284e3d0ff6f937d15099555e15a3f262 100644 (file)
@@ -1370,6 +1370,13 @@ do_buffer_ext(
     if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf))
        return OK;
 #endif
+    if (
+       action == DOBUF_GOTO
+       && buf != curbuf
+       && !check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) ? TRUE : FALSE))
+      // disallow navigating to another buffer when 'winfixbuf' is applied
+      return FAIL;
+
     if ((action == DOBUF_GOTO || action == DOBUF_SPLIT)
                                                  && (buf->b_flags & BF_DUMMY))
     {
index dd2bc95b76a9d69bd516ad03049e2f68d81925cf..65ee4e826e47f988da6d0584f1e120e3842a2475 100644 (file)
@@ -3607,3 +3607,5 @@ EXTERN char e_wrong_number_of_characters_for_field_str[]
        INIT(= N_("E1511: Wrong number of characters for field \"%s\""));
 EXTERN char e_wrong_character_width_for_field_str[]
        INIT(= N_("E1512: Wrong character width for field \"%s\""));
+EXTERN char e_winfixbuf_cannot_go_to_buffer[]
+       INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled"));
index 720e918bb49c27325dd6e76ff62283c6fc4a2389..a12d819b3f528a1c95ae4aa77437fe7bf1c721f5 100644 (file)
@@ -2428,6 +2428,9 @@ getfile(
     int                retval;
     char_u     *free_me = NULL;
 
+    if (!check_can_set_curbuf_forceit(forceit))
+       return GETFILE_ERROR;
+
     if (text_locked())
        return GETFILE_ERROR;
     if (curbuf_locked())
index 4ae6cc229348c18f87d6065ee67ab91f1a65c8cf..bd26e81dccb804e3cacd4cdec5b7add1429707bc 100644 (file)
@@ -521,7 +521,7 @@ EXCMD(CMD_doautoall,        "doautoall",    ex_doautoall,
        EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
        ADDR_NONE),
 EXCMD(CMD_drop,                "drop",         ex_drop,
-       EX_FILES|EX_CMDARG|EX_NEEDARG|EX_ARGOPT|EX_TRLBAR,
+       EX_BANG|EX_FILES|EX_CMDARG|EX_NEEDARG|EX_ARGOPT|EX_TRLBAR,
        ADDR_NONE),
 EXCMD(CMD_dsearch,     "dsearch",      ex_findpat,
        EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA|EX_CMDWIN|EX_LOCK_OK,
index 0bde73070e5707f429c43652c21ec08b94b00e96..c9834d2232b023a092d156ec4b32f1fdfdaab873 100644 (file)
@@ -457,6 +457,31 @@ ex_listdo(exarg_T *eap)
     tabpage_T  *tp;
     buf_T      *buf = curbuf;
     int                next_fnum = 0;
+
+    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;
+            CLEAR_FIELD(new_eap);
+            new_eap.cmdidx = CMD_split;
+            new_eap.cmd = (char_u *)"split";
+            new_eap.arg = (char_u *)"";
+            ex_splitview(&new_eap);
+        }
+    }
+
 #if defined(FEAT_SYN_HL)
     char_u     *save_ei = NULL;
 #endif
index c18a9107ec77ce09571d0a366bf6756506e9b404..19b1d85c6868d0e20430c11ad7f706de20aa62b1 100644 (file)
@@ -7164,6 +7164,9 @@ ex_resize(exarg_T *eap)
     static void
 ex_find(exarg_T *eap)
 {
+    if (!check_can_set_curbuf_forceit(eap->forceit))
+        return;
+
     char_u     *fname;
     int                count;
     char_u     *file_to_find = NULL;
@@ -7245,6 +7248,14 @@ ex_open(exarg_T *eap)
     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);
 }
 
@@ -9031,7 +9042,7 @@ ex_checkpath(exarg_T *eap)
 {
     find_pattern_in_path(NULL, 0, 0, FALSE, FALSE, CHECK_PATH, 1L,
                                   eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
-                                             (linenr_T)1, (linenr_T)MAXLNUM);
+                                             (linenr_T)1, (linenr_T)MAXLNUM, eap->forceit);
 }
 
 #if defined(FEAT_QUICKFIX)
@@ -9101,7 +9112,7 @@ ex_findpat(exarg_T *eap)
        find_pattern_in_path(eap->arg, 0, (int)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);
 }
 #endif
 
index 68e970a71a3cd430afd938a83c85a5f87abf79f2..0847b6c051d397836028acc9f3af721369defd46 100644 (file)
@@ -3407,7 +3407,7 @@ get_next_include_file_completion(int compl_type)
            (compl_type == CTRL_X_PATH_DEFINES
             && !(compl_cont_status & CONT_SOL))
            ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND,
-           (linenr_T)1, (linenr_T)MAXLNUM);
+           (linenr_T)1, (linenr_T)MAXLNUM, FALSE);
 }
 #endif
 
index 791b02f1cd7df09a4c17d9ae1920f0ec4f09f9af..5ef3a9277c4adf6ad8fd3013c03efbee45997fce 100644 (file)
@@ -4073,6 +4073,9 @@ nv_gotofile(cmdarg_T *cap)
        return;
 #endif
 
+    if (!check_can_set_curbuf_disabled())
+      return;
+
     ptr = grab_file_name(cap->count1, &lnum);
 
     if (ptr != NULL)
@@ -4475,7 +4478,8 @@ nv_brackets(cmdarg_T *cap)
                SAFE_isupper(cap->nchar) ? ACTION_SHOW_ALL :
                            SAFE_islower(cap->nchar) ? ACTION_SHOW : ACTION_GOTO,
                cap->cmdchar == ']' ? curwin->w_cursor.lnum + 1 : (linenr_T)1,
-               (linenr_T)MAXLNUM);
+               (linenr_T)MAXLNUM,
+               FALSE);
            vim_free(ptr);
            curwin->w_set_curswant = TRUE;
        }
index dd3542f8951480493d689ffd95dff77bccfb41ae..8123a2a2c631505cbd34fd9f5f79902df6e17596 100644 (file)
@@ -6420,6 +6420,7 @@ get_varp(struct vimoption *p)
 #ifdef FEAT_LINEBREAK
        case PV_NUW:    return (char_u *)&(curwin->w_p_nuw);
 #endif
+       case PV_WFB:    return (char_u *)&(curwin->w_p_wfb);
        case PV_WFH:    return (char_u *)&(curwin->w_p_wfh);
        case PV_WFW:    return (char_u *)&(curwin->w_p_wfw);
 #if defined(FEAT_QUICKFIX)
index 75940cce0a3056a22aa3ec36c42f79f3d470498d..bf889e47de3488f03e68abce57ce419418514ba8 100644 (file)
@@ -1309,6 +1309,7 @@ enum
 #ifdef FEAT_STL_OPT
     , WV_STL
 #endif
+    , WV_WFB
     , WV_WFH
     , WV_WFW
     , WV_WRAP
index 1a09e1c7fbcc0f03cf5fcd180bc6928632b0f5e8..4ee2e20de376eb4769d99f5a22847327f712331a 100644 (file)
 # define PV_STL                OPT_BOTH(OPT_WIN(WV_STL))
 #endif
 #define PV_UL          OPT_BOTH(OPT_BUF(BV_UL))
+# define PV_WFB                OPT_WIN(WV_WFB)
 # define PV_WFH                OPT_WIN(WV_WFH)
 # define PV_WFW                OPT_WIN(WV_WFW)
 #define PV_WRAP                OPT_WIN(WV_WRAP)
@@ -2850,6 +2851,9 @@ static struct vimoption options[] =
     {"window",     "wi",   P_NUM|P_VI_DEF,
                            (char_u *)&p_window, PV_NONE, did_set_window, NULL,
                            {(char_u *)0L, (char_u *)0L} SCTX_INIT},
+    {"winfixbuf", "wfb", P_BOOL|P_VI_DEF|P_RWIN,
+                           (char_u *)VAR_WIN, PV_WFB, NULL, NULL,
+                           {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
     {"winfixheight", "wfh", P_BOOL|P_VI_DEF|P_RSTAT,
                            (char_u *)VAR_WIN, PV_WFH, NULL, NULL,
                            {(char_u *)FALSE, (char_u *)0L} SCTX_INIT},
index 99e279dadf7b051f571432b59ab9385acd3d0cde..5b2b889317193b08ebb47e113ed8b05570da63af 100644 (file)
@@ -32,7 +32,7 @@ int check_linecomment(char_u *line);
 void showmatch(int c);
 int current_search(long count, int forward);
 int linewhite(linenr_T lnum);
-void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum);
+void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum, int forceit);
 spat_T *get_spat(int idx);
 int get_spat_last_idx(void);
 void f_searchcount(typval_T *argvars, typval_T *rettv);
index e5c03969fb4ebefb4fd141458d44beae4b72a67e..9e66db5a7fc07901cf06cc08c9f4bce5aad56373 100644 (file)
@@ -1,4 +1,6 @@
 /* window.c */
+int check_can_set_curbuf_disabled(void);
+int check_can_set_curbuf_forceit(int forceit);
 int window_layout_locked(enum CMD_index cmd);
 win_T *prevwin_curwin(void);
 win_T *swbuf_goto_win_with_buf(buf_T *buf);
index d8bcc1232afd8549caf5dc2c9960579cb18e782e..1f4176fe5427b66ebf12e18521fe79f16eed43ec 100644 (file)
@@ -3146,7 +3146,7 @@ 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;
@@ -3158,7 +3158,7 @@ qf_goto_win_with_qfl_file(int qf_fnum)
        }
 
        // Remember a usable window.
-       if (altwin == NULL && !win->w_p_pvw && bt_normal(win->w_buffer))
+       if (altwin == NULL && !win->w_p_pvw && !win->w_p_wfb && bt_normal(win->w_buffer))
            altwin = win;
     }
 
@@ -3261,8 +3261,32 @@ qf_jump_edit_buffer(
                prev_winid == curwin->w_id ? 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;
+               CLEAR_FIELD(new_eap);
+               new_eap.cmdidx = CMD_split;
+               new_eap.cmd = (char_u *)"split";
+               new_eap.arg = (char_u *)"";
+               ex_splitview(&new_eap);
+           }
+       }
+
        retval = buflist_getfile(qf_ptr->qf_fnum,
                (linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit);
+    }
 
     // If a location list, check whether the associated window is still
     // present.
@@ -4991,6 +5015,11 @@ qf_jump_first(qf_info_T *qi, int_u 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);
@@ -5907,7 +5936,7 @@ 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.
@@ -6497,6 +6526,9 @@ ex_vimgrep(exarg_T *eap)
     char_u     *au_name =  NULL;
     int                status;
 
+    if (!check_can_set_curbuf_forceit(eap->forceit))
+       return;
+
     au_name = vgr_get_auname(eap->cmdidx);
     if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
                                               curbuf->b_fname, TRUE, curbuf))
@@ -6558,7 +6590,7 @@ ex_vimgrep(exarg_T *eap)
        goto theend;
     }
 
-    // Jump to first match.
+    // Jump to first match if the current window is not 'winfixbuf'
     if (!qf_list_empty(qf_get_curlist(qi)))
     {
        if ((args.flags & VGR_NOJUMP) == 0)
index 1d0542b658c6c2bdf9bb6e31089759b038d193f4..83aaf0ac313f8569850acd49e74bf812d4842226 100644 (file)
@@ -3292,7 +3292,8 @@ find_pattern_in_path(
     long       count,
     int                action,         // What to do when we find it
     linenr_T   start_lnum,     // first line to start searching
-    linenr_T   end_lnum)       // last line for searching
+    linenr_T   end_lnum,       // last line for searching
+    int                forceit)        // If true, always switch to the found path
 {
     SearchedFile *files;               // Stack of included files
     SearchedFile *bigger;              // When we need more space
@@ -3829,7 +3830,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
@@ -3842,7 +3843,7 @@ search_line:
                    {
                        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
                        // want that here
index 5b88260f357e1c5c7036b992ce6b4a8778149cf7..df2c005e3d7334af10e2ae3192520362237ffbfd 100644 (file)
@@ -246,6 +246,8 @@ typedef struct
     long       wo_nuw;
 # define w_p_nuw w_onebuf_opt.wo_nuw   // 'numberwidth'
 #endif
+    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;
index 3df767d192b4bd9a8075c13c4e9a103dffca7898..2ac0da266689841e3bd19b210f20af2e3e57f9f0 100644 (file)
--- a/src/tag.c
+++ b/src/tag.c
@@ -289,6 +289,9 @@ do_tag(
     static char_u      **matches = NULL;
     static int         flags;
 
+    if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit))
+        return FALSE;
+
 #ifdef FEAT_EVAL
     if (tfu_in_use)
     {
@@ -3705,6 +3708,9 @@ jumpto_tag(
     size_t     len;
     char_u     *lbuf;
 
+    if (postponed_split == 0 && !check_can_set_curbuf_forceit(forceit))
+        return FAIL;
+
     // Make a copy of the line, it can become invalid when an autocommand calls
     // back here recursively.
     len = matching_line_len(lbuf_arg) + 1;
index 8dd04e79e3c3602b6145f9894ae5c0f279238bf2..d365dfc84f4086b8997fdef6feefd4ef090ee019 100644 (file)
@@ -325,6 +325,7 @@ NEW_TESTS = \
        test_window_cmd \
        test_window_id \
        test_windows_home \
+       test_winfixbuf \
        test_wnext \
        test_wordcount \
        test_writefile \
diff --git a/src/testdir/test_winfixbuf.vim b/src/testdir/test_winfixbuf.vim
new file mode 100644 (file)
index 0000000..0b15983
--- /dev/null
@@ -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.command 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.command("buffer " .. ' . l:previous . ')')
+  call assert_equal(l:current, bufnr())
+
+  execute 'lua vim.command("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 /Vim(pydo):vim.error: Vim: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 /Vim(pyfile):vim.error: Vim: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 /Vim(python):vim\.error: Vim: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
index e7ee9466b392201585c67832f4d48f73d31a9b8d..fc595f098c4e2c7352ecf631af1e848afbbafe66 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    147,
 /**/
     146,
 /**/
index bda24fc4e517ad92c18c52fcbff5e11f353989a2..435bdc9e1525b2488f23dacc02a363e7965033f0 100644 (file)
@@ -158,6 +158,37 @@ log_frame_layout(frame_T *frame)
 }
 #endif
 
+/*
+ * Check if the current window is allowed to move to a different buffer.
+ * If the window has 'winfixbuf', this function will return FALSE.
+ */
+    int
+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.
+ * If the window has 'winfixbuf', then forceit must be TRUE or this function
+ * will return FALSE.
+ */
+    int
+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".
@@ -667,7 +698,7 @@ wingotofile:
 
                find_pattern_in_path(ptr, 0, len, TRUE,
                        Prenum == 0 ? TRUE : FALSE, type,
-                       Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM);
+                       Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM, FALSE);
                vim_free(ptr);
                curwin->w_set_curswant = TRUE;
                break;