# HG changeset patch # User Christian Brabandt # Date 1709479806 -3600 # Node ID dd8f5311cee5a2f30339a9ef7f61f0aa45325f25 # Parent 055c08ca04462c163da762040380607a444ff2bf patch 9.1.0147: Cannot keep a buffer focused in a window Commit: https://github.com/vim/vim/commit/215703563757a4464907ead6fb9edaeb7f430bea Author: Colin Kennedy Date: Sun Mar 3 16:16:47 2024 +0100 patch 9.1.0147: Cannot keep a buffer focused in a window Problem: Cannot keep a buffer focused in a window (Amit Levy) Solution: Add the 'winfixbuf' window-local option (Colin Kennedy) fixes: #6445 closes: #13903 Signed-off-by: Colin Kennedy Signed-off-by: Christian Brabandt diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -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 You cannot have two buffers with exactly the same name. This includes the path leading to the file. + *E1513* > + Cannot edit buffer. 'winfixbuf' is enabled + +If a window has 'winfixbuf' enabled, you cannot change that window's current +buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to +force the window to switch buffers, if your command supports it. + *E72* Close error on swap file ~ diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -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 shor "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 shor 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| diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -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: *opti '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 diff --git a/runtime/doc/tags b/runtime/doc/tags --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -1294,6 +1294,7 @@ '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* diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -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 w 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 actio the changes, use the ":w" command and then use ":tag" without an argument. This works because the tag is put on the stack anyway. If you want to lose the changes you can use the ":tag!" command. +- If the tag is in another file and the window includes 'winfixbuf', the + command will fail. If the tag is in the same file then it may succeed. *tag-security* Note that Vim forbids some commands, for security reasons. This works like diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -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* diff --git a/runtime/optwin.vim b/runtime/optwin.vim --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -482,6 +482,7 @@ if has("statusline") call AddOption("statusline", gettext("alternate format to be used for a status line")) call OptionG("stl", &stl) endif +call append("$", "\t" .. s:local_to_window) call AddOption("equalalways", gettext("make all windows the same size when adding/removing windows")) call BinOptionG("ea", &ea) call AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\"")) @@ -490,6 +491,8 @@ call AddOption("winheight", gettext call append("$", " \tset wh=" . &wh) call AddOption("winminheight", gettext("minimal number of lines used for any window")) call append("$", " \tset wmh=" . &wmh) +call AddOption("winfixbuf", gettext("keep window focused on a single buffer")) +call OptionG("wfb", &wfb) call AddOption("winfixheight", gettext("keep the height of the window")) call append("$", "\t" .. s:local_to_window) call BinOptionL("wfh") diff --git a/src/arglist.c b/src/arglist.c --- a/src/arglist.c +++ b/src/arglist.c @@ -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; diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -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)) { diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3607,3 +3607,5 @@ EXTERN char e_wrong_number_of_characters 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")); diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -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()) diff --git a/src/ex_cmds.h b/src/ex_cmds.h --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -521,7 +521,7 @@ EXCMD(CMD_doautoall, "doautoall", ex_doa 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, diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -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 diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -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 diff --git a/src/insexpand.c b/src/insexpand.c --- a/src/insexpand.c +++ b/src/insexpand.c @@ -3407,7 +3407,7 @@ get_next_include_file_completion(int com (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 diff --git a/src/normal.c b/src/normal.c --- a/src/normal.c +++ b/src/normal.c @@ -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; } diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -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) diff --git a/src/option.h b/src/option.h --- a/src/option.h +++ b/src/option.h @@ -1309,6 +1309,7 @@ enum #ifdef FEAT_STL_OPT , WV_STL #endif + , WV_WFB , WV_WFH , WV_WFW , WV_WRAP diff --git a/src/optiondefs.h b/src/optiondefs.h --- a/src/optiondefs.h +++ b/src/optiondefs.h @@ -215,6 +215,7 @@ # 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}, diff --git a/src/proto/search.pro b/src/proto/search.pro --- a/src/proto/search.pro +++ b/src/proto/search.pro @@ -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); diff --git a/src/proto/window.pro b/src/proto/window.pro --- a/src/proto/window.pro +++ b/src/proto/window.pro @@ -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); diff --git a/src/quickfix.c b/src/quickfix.c --- a/src/quickfix.c +++ b/src/quickfix.c @@ -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_ 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) diff --git a/src/search.c b/src/search.c --- a/src/search.c +++ b/src/search.c @@ -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 diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -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; diff --git a/src/tag.c b/src/tag.c --- 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; diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- a/src/testdir/Make_all.mak +++ b/src/testdir/Make_all.mak @@ -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 --- /dev/null +++ b/src/testdir/test_winfixbuf.vim @@ -0,0 +1,3131 @@ +" Test 'winfixbuf' + +source check.vim + +" Find the number of open windows in the current tab +func s:get_windows_count() + return tabpagewinnr(tabpagenr(), '$') +endfunc + +" Create some unnamed buffers. +func s:make_buffers_list() + enew + file first + let l:first = bufnr() + + enew + file middle + let l:middle = bufnr() + + enew + file last + let l:last = bufnr() + + set winfixbuf + + return [l:first, l:last] +endfunc + +" Create some unnamed buffers and add them to an args list +func s:make_args_list() + let [l:first, l:last] = s:make_buffers_list() + + args! first middle last + + return [l:first, l:last] +endfunc + +" Create two buffers and then set the window to 'winfixbuf' +func s:make_buffer_pairs(...) + let l:reversed = get(a:, 1, 0) + + if l:reversed == 1 + enew + file original + + set winfixbuf + + enew! + file other + let l:other = bufnr() + + return l:other + endif + + enew + file other + let l:other = bufnr() + + enew + file current + + set winfixbuf + + return l:other +endfunc + +" Create 3 quick buffers and set the window to 'winfixbuf' +func s:make_buffer_trio() + edit first + let l:first = bufnr() + edit second + let l:second = bufnr() + + set winfixbuf + + edit! third + let l:third = bufnr() + + execute ":buffer! " . l:second + + return [l:first, l:second, l:third] +endfunc + +" Create a location list with at least 2 entries + a 'winfixbuf' window. +func s:make_simple_location_list() + enew + file middle + let l:middle = bufnr() + call append(0, ["winfix search-term", "another line"]) + + enew! + file first + let l:first = bufnr() + call append(0, "first search-term") + + enew! + file last + let l:last = bufnr() + call append(0, "last search-term") + + call setloclist( + \ 0, + \ [ + \ { + \ "filename": "first", + \ "bufnr": l:first, + \ "lnum": 1, + \ }, + \ { + \ "filename": "middle", + \ "bufnr": l:middle, + \ "lnum": 1, + \ }, + \ { + \ "filename": "middle", + \ "bufnr": l:middle, + \ "lnum": 2, + \ }, + \ { + \ "filename": "last", + \ "bufnr": l:last, + \ "lnum": 1, + \ }, + \ ] + \) + + set winfixbuf + + return [l:first, l:middle, l:last] +endfunc + +" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window. +func s:make_simple_quickfix() + enew + file current + let l:current = bufnr() + call append(0, ["winfix search-term", "another line"]) + + enew! + file first + let l:first = bufnr() + call append(0, "first search-term") + + enew! + file last + let l:last = bufnr() + call append(0, "last search-term") + + call setqflist( + \ [ + \ { + \ "filename": "first", + \ "bufnr": l:first, + \ "lnum": 1, + \ }, + \ { + \ "filename": "current", + \ "bufnr": l:current, + \ "lnum": 1, + \ }, + \ { + \ "filename": "current", + \ "bufnr": l:current, + \ "lnum": 2, + \ }, + \ { + \ "filename": "last", + \ "bufnr": l:last, + \ "lnum": 1, + \ }, + \ ] + \) + + set winfixbuf + + return [l:current, l:last] +endfunc + +" Create a quickfix with at least 2 entries that are in the current 'winfixbuf' window. +func s:make_quickfix_windows() + let [l:current, _] = s:make_simple_quickfix() + execute "buffer! " . l:current + + split + let l:first_window = win_getid() + execute "normal \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 \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 \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 \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 \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 \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 \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 \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 \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 \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 \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 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 \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 \j" + normal j + + call assert_fails(".ll", "E1513:") + execute "normal \k" + call assert_equal(l:first, bufnr()) + execute "normal \j" + .ll! + execute "normal \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 \" + + 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 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\", "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 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 \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal g\", "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 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 \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "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 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 \" + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Disallow 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 \", "E1513:") + call assert_equal(l:current, bufnr()) +endfunc + +" Allow 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 \" + + set winfixbuf + + let l:line = getcurpos()[1] + execute "normal 1\" + call assert_notequal(l:line, getcurpos()[1]) +endfunc + +" Disallow 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 \", "E1513:") + call assert_equal(l:current, bufnr()) +endfunc + +" Allow 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 \" + call assert_equal(l:current, bufnr()) +endfunc + +" Fail to jump to a tag with 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 \", "E1513:") + call assert_equal(l:current, bufnr()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Allow 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 \\" + call assert_equal(l:current_windows + 1, s:get_windows_count()) + + set tags& + call delete("Xtags") + call delete("Xfile") + call delete("Xother") +endfunc + +" Allow g 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 \g\" + 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 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 \", "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 [ 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 ]\ + + set winfixbuf + + let l:current = bufnr() + + call assert_fails("normal [\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal [\" + call assert_notequal(l:current, bufnr()) + + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail to go to a C macro with ] 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 ]\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal ]\" + call assert_notequal(l:current, bufnr()) + + call delete("main.c") + call delete(l:include_file) +endfunc + +" Fail to go to a C macro with [ 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 [\", "E1513:") + + set nowinfixbuf + + execute "normal [\" + 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 ] 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 ]\", "E1513:") + call assert_equal(l:current, bufnr()) + + set nowinfixbuf + + execute "normal ]\" + 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 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\", "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 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\", "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 \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 + + 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 \", "E1513:") + call assert_equal(l:current, bufnr()) + + " Disallow by default but allow it if the command does something else + nnoremap :echo "hello!" + + execute "normal \" + call assert_equal(l:current, bufnr()) + + nunmap +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 \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 \" + 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 \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 \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 \" + + 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 \" + 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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 147, +/**/ 146, /**/ 145, diff --git a/src/window.c b/src/window.c --- a/src/window.c +++ b/src/window.c @@ -159,6 +159,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;