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 <colinvfx@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Colin Kennedy
2024-03-03 16:16:47 +01:00
committed by Christian Brabandt
parent 353faa373e
commit 2157035637
29 changed files with 3336 additions and 31 deletions

View File

@ -1,4 +1,4 @@
*message.txt* For Vim version 9.1. Last change: 2023 Dec 20
*message.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar
@ -122,6 +122,13 @@ wiped out a buffer which contains a mark or is referenced in another way.
You cannot have two buffers with exactly the same name. This includes the
path leading to the file.
*E1513* >
Cannot edit buffer. 'winfixbuf' is enabled
If a window has 'winfixbuf' enabled, you cannot change that window's current
buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to
force the window to switch buffers, if your command supports it.
*E72*
Close error on swap file ~

View File

@ -1,4 +1,4 @@
*options.txt* For Vim version 9.1. Last change: 2024 Feb 24
*options.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar
@ -8021,6 +8021,8 @@ A jump table for the options with a short description can be found at |Q_op|.
"split" when both are present.
uselast If included, jump to the previously used window when
jumping to errors with |quickfix| commands.
If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
applied to the split window.
*'synmaxcol'* *'smc'*
'synmaxcol' 'smc' number (default 3000)
@ -9471,6 +9473,15 @@ A jump table for the options with a short description can be found at |Q_op|.
Note: Do not confuse this with the height of the Vim window, use
'lines' for that.
*'winfixbuf'*
'winfixbuf' 'wfb' boolean (default off)
local to window
If enabled, the buffer and any window that displays it are paired.
For example, attempting to change the buffer with |:edit| will fail.
Other commands which change a window's buffer such as |:cnext| will
also skip any window with 'winfixbuf' enabled. However if a command
has an "!" option, a window can be forced to switch buffers.
*'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'*
'winfixheight' 'wfh' boolean (default off)
local to window |local-noglobal|

View File

@ -1,4 +1,4 @@
*quickref.txt* For Vim version 9.1. Last change: 2023 Dec 05
*quickref.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar
@ -1005,6 +1005,7 @@ Short explanation of each option: *option-list*
'winaltkeys' 'wak' when the windows system handles ALT keys
'wincolor' 'wcr' window-local highlighting
'window' 'wi' nr of lines to scroll for CTRL-F and CTRL-B
'winfixbuf' 'wfb' keep window focused on a single buffer
'winfixheight' 'wfh' keep window height when opening/closing windows
'winfixwidth' 'wfw' keep window width when opening/closing windows
'winheight' 'wh' minimum number of lines for the current window

View File

@ -1294,6 +1294,7 @@ $quote eval.txt /*$quote*
'winaltkeys' options.txt /*'winaltkeys'*
'wincolor' options.txt /*'wincolor'*
'window' options.txt /*'window'*
'winfixbuf' options.txt /*'winfixbuf'*
'winfixheight' options.txt /*'winfixheight'*
'winfixwidth' options.txt /*'winfixwidth'*
'winheight' options.txt /*'winheight'*
@ -4541,6 +4542,7 @@ E151 helphelp.txt /*E151*
E1510 change.txt /*E1510*
E1511 options.txt /*E1511*
E1512 options.txt /*E1512*
E1513 message.txt /*E1513*
E152 helphelp.txt /*E152*
E153 helphelp.txt /*E153*
E154 helphelp.txt /*E154*

View File

@ -1,4 +1,4 @@
*tagsrch.txt* For Vim version 9.1. Last change: 2023 Feb 13
*tagsrch.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar
@ -409,17 +409,22 @@ If the tag is in the current file this will always work. Otherwise the
performed actions depend on whether the current file was changed, whether a !
is added to the command and on the 'autowrite' option:
tag in file autowrite ~
current file changed ! option action ~
-----------------------------------------------------------------------------
yes x x x goto tag
no no x x read other file, goto tag
no yes yes x abandon current file, read other file, goto
tag
no yes no on write current file, read other file, goto
tag
no yes no off fail
-----------------------------------------------------------------------------
tag in file autowrite ~
current file changed ! winfixbuf option action ~
-----------------------------------------------------------------------------
yes x x no x goto tag
no no x no x read other file, goto tag
no yes yes no x abandon current file,
read other file, goto tag
no yes no no on write current file,
read other file, goto tag
no yes no no off fail
yes x yes x x goto tag
no no no yes x fail
no yes no yes x fail
no yes no yes on fail
no yes no yes off fail
-----------------------------------------------------------------------------
- If the tag is in the current file, the command will always work.
- If the tag is in another file and the current file was not changed, the
@ -435,6 +440,8 @@ current file changed ! option action ~
the changes, use the ":w" command and then use ":tag" without an argument.
This works because the tag is put on the stack anyway. If you want to lose
the changes you can use the ":tag!" command.
- If the tag is in another file and the window includes 'winfixbuf', the
command will fail. If the tag is in the same file then it may succeed.
*tag-security*
Note that Vim forbids some commands, for security reasons. This works like

View File

@ -1,4 +1,4 @@
*version9.txt* For Vim version 9.1. Last change: 2024 Feb 21
*version9.txt* For Vim version 9.1. Last change: 2024 Mar 03
VIM REFERENCE MANUAL by Bram Moolenaar
@ -41575,6 +41575,8 @@ Commands: ~
Options: ~
'winfixbuf' Keep buffer focused in a window
==============================================================================
INCOMPATIBLE CHANGES *incompatible-9.2*

View File

@ -482,6 +482,7 @@ if has("statusline")
call <SID>AddOption("statusline", gettext("alternate format to be used for a status line"))
call <SID>OptionG("stl", &stl)
endif
call append("$", "\t" .. s:local_to_window)
call <SID>AddOption("equalalways", gettext("make all windows the same size when adding/removing windows"))
call <SID>BinOptionG("ea", &ea)
call <SID>AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\""))
@ -490,6 +491,8 @@ call <SID>AddOption("winheight", gettext("minimal number of lines used for the c
call append("$", " \tset wh=" . &wh)
call <SID>AddOption("winminheight", gettext("minimal number of lines used for any window"))
call append("$", " \tset wmh=" . &wmh)
call <SID>AddOption("winfixbuf", gettext("keep window focused on a single buffer"))
call <SID>OptionG("wfb", &wfb)
call <SID>AddOption("winfixheight", gettext("keep the height of the window"))
call append("$", "\t" .. s:local_to_window)
call <SID>BinOptionL("wfh")

View File

@ -682,6 +682,7 @@ do_argfile(exarg_T *eap, int argn)
int other;
char_u *p;
int old_arg_idx = curwin->w_arg_idx;
int is_split_cmd = *eap->cmd == 's';
if (ERROR_IF_ANY_POPUP_WINDOW)
return;
@ -697,13 +698,18 @@ do_argfile(exarg_T *eap, int argn)
return;
}
if (!is_split_cmd
&& (&ARGLIST[argn])->ae_fnum != curbuf->b_fnum
&& !check_can_set_curbuf_forceit(eap->forceit))
return;
setpcmark();
#ifdef FEAT_GUI
need_mouse_correct = TRUE;
#endif
// split window or create new tab page first
if (*eap->cmd == 's' || cmdmod.cmod_tab != 0)
if (is_split_cmd || cmdmod.cmod_tab != 0)
{
if (win_split(0, 0) == FAIL)
return;

View File

@ -1370,6 +1370,13 @@ do_buffer_ext(
if ((flags & DOBUF_NOPOPUP) && bt_popup(buf) && !bt_terminal(buf))
return OK;
#endif
if (
action == DOBUF_GOTO
&& buf != curbuf
&& !check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) ? TRUE : FALSE))
// disallow navigating to another buffer when 'winfixbuf' is applied
return FAIL;
if ((action == DOBUF_GOTO || action == DOBUF_SPLIT)
&& (buf->b_flags & BF_DUMMY))
{

View File

@ -3607,3 +3607,5 @@ EXTERN char e_wrong_number_of_characters_for_field_str[]
INIT(= N_("E1511: Wrong number of characters for field \"%s\""));
EXTERN char e_wrong_character_width_for_field_str[]
INIT(= N_("E1512: Wrong character width for field \"%s\""));
EXTERN char e_winfixbuf_cannot_go_to_buffer[]
INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled"));

View File

@ -2428,6 +2428,9 @@ getfile(
int retval;
char_u *free_me = NULL;
if (!check_can_set_curbuf_forceit(forceit))
return GETFILE_ERROR;
if (text_locked())
return GETFILE_ERROR;
if (curbuf_locked())

View File

@ -521,7 +521,7 @@ EXCMD(CMD_doautoall, "doautoall", ex_doautoall,
EX_EXTRA|EX_TRLBAR|EX_CMDWIN|EX_LOCK_OK,
ADDR_NONE),
EXCMD(CMD_drop, "drop", ex_drop,
EX_FILES|EX_CMDARG|EX_NEEDARG|EX_ARGOPT|EX_TRLBAR,
EX_BANG|EX_FILES|EX_CMDARG|EX_NEEDARG|EX_ARGOPT|EX_TRLBAR,
ADDR_NONE),
EXCMD(CMD_dsearch, "dsearch", ex_findpat,
EX_BANG|EX_RANGE|EX_DFLALL|EX_WHOLEFOLD|EX_EXTRA|EX_CMDWIN|EX_LOCK_OK,

View File

@ -457,6 +457,31 @@ ex_listdo(exarg_T *eap)
tabpage_T *tp;
buf_T *buf = curbuf;
int next_fnum = 0;
if (curwin->w_p_wfb)
{
if ((eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) && !eap->forceit)
{
// Disallow :ldo if 'winfixbuf' is applied
semsg("%s", e_winfixbuf_cannot_go_to_buffer);
return;
}
if (win_valid(prevwin))
// Change the current window to another because 'winfixbuf' is enabled
curwin = prevwin;
else
{
// Split the window, which will be 'nowinfixbuf', and set curwin to that
exarg_T new_eap;
CLEAR_FIELD(new_eap);
new_eap.cmdidx = CMD_split;
new_eap.cmd = (char_u *)"split";
new_eap.arg = (char_u *)"";
ex_splitview(&new_eap);
}
}
#if defined(FEAT_SYN_HL)
char_u *save_ei = NULL;
#endif

View File

@ -7164,6 +7164,9 @@ ex_resize(exarg_T *eap)
static void
ex_find(exarg_T *eap)
{
if (!check_can_set_curbuf_forceit(eap->forceit))
return;
char_u *fname;
int count;
char_u *file_to_find = NULL;
@ -7245,6 +7248,14 @@ ex_open(exarg_T *eap)
static void
ex_edit(exarg_T *eap)
{
// Exclude commands which keep the window's current buffer
if (
eap->cmdidx != CMD_badd
&& eap->cmdidx != CMD_balt
// All other commands must obey 'winfixbuf' / ! rules
&& !check_can_set_curbuf_forceit(eap->forceit))
return;
do_exedit(eap, NULL);
}
@ -9031,7 +9042,7 @@ ex_checkpath(exarg_T *eap)
{
find_pattern_in_path(NULL, 0, 0, FALSE, FALSE, CHECK_PATH, 1L,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
(linenr_T)1, (linenr_T)MAXLNUM);
(linenr_T)1, (linenr_T)MAXLNUM, eap->forceit);
}
#if defined(FEAT_QUICKFIX)
@ -9101,7 +9112,7 @@ ex_findpat(exarg_T *eap)
find_pattern_in_path(eap->arg, 0, (int)STRLEN(eap->arg),
whole, !eap->forceit,
*eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
n, action, eap->line1, eap->line2, eap->forceit);
}
#endif

View File

@ -3407,7 +3407,7 @@ get_next_include_file_completion(int compl_type)
(compl_type == CTRL_X_PATH_DEFINES
&& !(compl_cont_status & CONT_SOL))
? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND,
(linenr_T)1, (linenr_T)MAXLNUM);
(linenr_T)1, (linenr_T)MAXLNUM, FALSE);
}
#endif

View File

@ -4073,6 +4073,9 @@ nv_gotofile(cmdarg_T *cap)
return;
#endif
if (!check_can_set_curbuf_disabled())
return;
ptr = grab_file_name(cap->count1, &lnum);
if (ptr != NULL)
@ -4475,7 +4478,8 @@ nv_brackets(cmdarg_T *cap)
SAFE_isupper(cap->nchar) ? ACTION_SHOW_ALL :
SAFE_islower(cap->nchar) ? ACTION_SHOW : ACTION_GOTO,
cap->cmdchar == ']' ? curwin->w_cursor.lnum + 1 : (linenr_T)1,
(linenr_T)MAXLNUM);
(linenr_T)MAXLNUM,
FALSE);
vim_free(ptr);
curwin->w_set_curswant = TRUE;
}

View File

@ -6420,6 +6420,7 @@ get_varp(struct vimoption *p)
#ifdef FEAT_LINEBREAK
case PV_NUW: return (char_u *)&(curwin->w_p_nuw);
#endif
case PV_WFB: return (char_u *)&(curwin->w_p_wfb);
case PV_WFH: return (char_u *)&(curwin->w_p_wfh);
case PV_WFW: return (char_u *)&(curwin->w_p_wfw);
#if defined(FEAT_QUICKFIX)

View File

@ -1309,6 +1309,7 @@ enum
#ifdef FEAT_STL_OPT
, WV_STL
#endif
, WV_WFB
, WV_WFH
, WV_WFW
, WV_WRAP

View File

@ -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},

View File

@ -32,7 +32,7 @@ int check_linecomment(char_u *line);
void showmatch(int c);
int current_search(long count, int forward);
int linewhite(linenr_T lnum);
void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum);
void find_pattern_in_path(char_u *ptr, int dir, int len, int whole, int skip_comments, int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum, int forceit);
spat_T *get_spat(int idx);
int get_spat_last_idx(void);
void f_searchcount(typval_T *argvars, typval_T *rettv);

View File

@ -1,4 +1,6 @@
/* window.c */
int check_can_set_curbuf_disabled(void);
int check_can_set_curbuf_forceit(int forceit);
int window_layout_locked(enum CMD_index cmd);
win_T *prevwin_curwin(void);
win_T *swbuf_goto_win_with_buf(buf_T *buf);

View File

@ -3146,7 +3146,7 @@ qf_goto_win_with_qfl_file(int qf_fnum)
// Didn't find it, go to the window before the quickfix
// window, unless 'switchbuf' contains 'uselast': in this case we
// try to jump to the previously used window first.
if ((swb_flags & SWB_USELAST) && win_valid(prevwin))
if ((swb_flags & SWB_USELAST) && !prevwin->w_p_wfb && win_valid(prevwin))
win = prevwin;
else if (altwin != NULL)
win = altwin;
@ -3158,7 +3158,7 @@ qf_goto_win_with_qfl_file(int qf_fnum)
}
// Remember a usable window.
if (altwin == NULL && !win->w_p_pvw && bt_normal(win->w_buffer))
if (altwin == NULL && !win->w_p_pvw && !win->w_p_wfb && bt_normal(win->w_buffer))
altwin = win;
}
@ -3261,8 +3261,32 @@ qf_jump_edit_buffer(
prev_winid == curwin->w_id ? curwin : NULL);
}
else
{
if (!forceit && curwin->w_p_wfb)
{
if (qi->qfl_type == QFLT_LOCATION)
{
// Location lists cannot split or reassign their window
// so 'winfixbuf' windows must fail
semsg("%s", e_winfixbuf_cannot_go_to_buffer);
return QF_ABORT;
}
if (!win_valid(prevwin))
{
// Split the window, which will be 'nowinfixbuf', and set curwin to that
exarg_T new_eap;
CLEAR_FIELD(new_eap);
new_eap.cmdidx = CMD_split;
new_eap.cmd = (char_u *)"split";
new_eap.arg = (char_u *)"";
ex_splitview(&new_eap);
}
}
retval = buflist_getfile(qf_ptr->qf_fnum,
(linenr_T)1, GETF_SETMARK | GETF_SWITCH, forceit);
}
// If a location list, check whether the associated window is still
// present.
@ -4991,6 +5015,11 @@ qf_jump_first(qf_info_T *qi, int_u save_qfid, int forceit)
if (qf_restore_list(qi, save_qfid) == FAIL)
return;
if (!check_can_set_curbuf_forceit(forceit))
return;
// Autocommands might have cleared the list, check for that.
if (!qf_list_empty(qf_get_curlist(qi)))
qf_jump(qi, 0, 0, forceit);
@ -5907,7 +5936,7 @@ ex_cfile(exarg_T *eap)
// This function is used by the :cfile, :cgetfile and :caddfile
// commands.
// :cfile always creates a new quickfix list and jumps to the
// :cfile always creates a new quickfix list and may jump to the
// first error.
// :cgetfile creates a new quickfix list but doesn't jump to the
// first error.
@ -6497,6 +6526,9 @@ ex_vimgrep(exarg_T *eap)
char_u *au_name = NULL;
int status;
if (!check_can_set_curbuf_forceit(eap->forceit))
return;
au_name = vgr_get_auname(eap->cmdidx);
if (au_name != NULL && apply_autocmds(EVENT_QUICKFIXCMDPRE, au_name,
curbuf->b_fname, TRUE, curbuf))
@ -6558,7 +6590,7 @@ ex_vimgrep(exarg_T *eap)
goto theend;
}
// Jump to first match.
// Jump to first match if the current window is not 'winfixbuf'
if (!qf_list_empty(qf_get_curlist(qi)))
{
if ((args.flags & VGR_NOJUMP) == 0)

View File

@ -3292,7 +3292,8 @@ find_pattern_in_path(
long count,
int action, // What to do when we find it
linenr_T start_lnum, // first line to start searching
linenr_T end_lnum) // last line for searching
linenr_T end_lnum, // last line for searching
int forceit) // If true, always switch to the found path
{
SearchedFile *files; // Stack of included files
SearchedFile *bigger; // When we need more space
@ -3829,7 +3830,7 @@ search_line:
break;
if (!GETFILE_SUCCESS(getfile(
curwin_save->w_buffer->b_fnum, NULL,
NULL, TRUE, lnum, FALSE)))
NULL, TRUE, lnum, forceit)))
break; // failed to jump to file
}
else
@ -3842,7 +3843,7 @@ search_line:
{
if (!GETFILE_SUCCESS(getfile(
0, files[depth].name, NULL, TRUE,
files[depth].lnum, FALSE)))
files[depth].lnum, forceit)))
break; // failed to jump to file
// autocommands may have changed the lnum, we don't
// want that here

View File

@ -246,6 +246,8 @@ typedef struct
long wo_nuw;
# define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
#endif
int wo_wfb;
#define w_p_wfb w_onebuf_opt.wo_wfb // 'winfixbuf'
int wo_wfh;
# define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
int wo_wfw;

View File

@ -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;

View File

@ -325,6 +325,7 @@ NEW_TESTS = \
test_window_cmd \
test_window_id \
test_windows_home \
test_winfixbuf \
test_wnext \
test_wordcount \
test_writefile \

File diff suppressed because it is too large Load Diff

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
147,
/**/
146,
/**/

View File

@ -158,6 +158,37 @@ log_frame_layout(frame_T *frame)
}
#endif
/*
* Check if the current window is allowed to move to a different buffer.
* If the window has 'winfixbuf', this function will return FALSE.
*/
int
check_can_set_curbuf_disabled(void)
{
if (curwin->w_p_wfb)
{
semsg("%s", e_winfixbuf_cannot_go_to_buffer);
return FALSE;
}
return TRUE;
}
/*
* Check if the current window is allowed to move to a different buffer.
* If the window has 'winfixbuf', then forceit must be TRUE or this function
* will return FALSE.
*/
int
check_can_set_curbuf_forceit(int forceit)
{
if (!forceit && curwin->w_p_wfb)
{
semsg("%s", e_winfixbuf_cannot_go_to_buffer);
return FALSE;
}
return TRUE;
}
/*
* Return the current window, unless in the cmdline window and "prevwin" is
* set, then return "prevwin".
@ -667,7 +698,7 @@ wingotofile:
find_pattern_in_path(ptr, 0, len, TRUE,
Prenum == 0 ? TRUE : FALSE, type,
Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM);
Prenum1, ACTION_SPLIT, (linenr_T)1, (linenr_T)MAXLNUM, FALSE);
vim_free(ptr);
curwin->w_set_curswant = TRUE;
break;