Skip to content

Commit

Permalink
vim-patch:9.1.0147: Cannot keep a buffer focused in a window
Browse files Browse the repository at this point in the history
Problem:  Cannot keep a buffer focused in a window
          (Amit Levy)
Solution: Add the 'winfixbuf' window-local option
          (Colin Kennedy)

fixes:  vim/vim#6445
closes: vim/vim#13903

vim/vim@2157035

N/A patch:
vim-patch:58f1e5c0893a
  • Loading branch information
ColinKennedy authored and zeertzjq committed Mar 11, 2024
1 parent a09ddd7 commit 141182d
Show file tree
Hide file tree
Showing 27 changed files with 3,414 additions and 23 deletions.
7 changes: 7 additions & 0 deletions runtime/doc/message.txt
Expand Up @@ -114,6 +114,13 @@ wiped out a buffer which contains a mark or is referenced in another way.
You cannot have two buffers with exactly the same name. This includes the
path leading to the file.

*E1513* >
Cannot edit buffer. 'winfixbuf' is enabled
If a window has 'winfixbuf' enabled, you cannot change that window's current
buffer. You need to set 'nowinfixbuf' before continuing. You may use [!] to
force the window to switch buffers, if your command supports it.

*E72* >
Close error on swap file
Expand Down
2 changes: 2 additions & 0 deletions runtime/doc/news.txt
Expand Up @@ -160,6 +160,8 @@ The following new APIs and features were added.
'breakindent' performance is significantly improved for wrapped lines.
• Cursor movement, insertion with [count] and |screenpos()| are now faster.

|'winfixbuf'| keeps a window focused onto a specific buffer

|vim.iter()| provides a generic iterator interface for tables and Lua
iterators |for-in|.

Expand Down
11 changes: 11 additions & 0 deletions runtime/doc/options.txt
Expand Up @@ -6271,6 +6271,8 @@ A jump table for the options with a short description can be found at |Q_op|.
"split" when both are present.
uselast If included, jump to the previously used window when
jumping to errors with |quickfix| commands.
If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
applied to the split window.

*'synmaxcol'* *'smc'*
'synmaxcol' 'smc' number (default 3000)
Expand Down Expand Up @@ -7170,6 +7172,15 @@ A jump table for the options with a short description can be found at |Q_op|.
Note: Do not confuse this with the height of the Vim window, use
'lines' for that.

*'winfixbuf'* *'wfb'* *'nowinfixbuf'* *'nowfb'*
'winfixbuf' 'wfb' boolean (default off)
local to window
If enabled, the buffer and any window that displays it are paired.
For example, attempting to change the buffer with |:edit| will fail.
Other commands which change a window's buffer such as |:cnext| will
also skip any window with 'winfixbuf' enabled. However if a command
has an "!" option, a window can be forced to switch buffers.

*'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'*
'winfixheight' 'wfh' boolean (default off)
local to window |local-noglobal|
Expand Down
1 change: 1 addition & 0 deletions runtime/doc/quickref.txt
Expand Up @@ -939,6 +939,7 @@ Short explanation of each option: *option-list*
'wildoptions' 'wop' specifies how command line completion is done
'winaltkeys' 'wak' when the windows system handles ALT keys
'window' 'wi' nr of lines to scroll for CTRL-F and CTRL-B
'winfixbuf' 'wfb' keep window focused on a single buffer
'winfixheight' 'wfh' keep window height when opening/closing windows
'winfixwidth' 'wfw' keep window width when opening/closing windows
'winheight' 'wh' minimum number of lines for the current window
Expand Down
29 changes: 18 additions & 11 deletions runtime/doc/tagsrch.txt
Expand Up @@ -402,17 +402,22 @@ If the tag is in the current file this will always work. Otherwise the
performed actions depend on whether the current file was changed, whether a !
is added to the command and on the 'autowrite' option:

tag in file autowrite ~
current file changed ! option action ~
---------------------------------------------------------------------------
yes x x x goto tag
no no x x read other file, goto tag
no yes yes x abandon current file, read other file, goto
tag
no yes no on write current file, read other file, goto
tag
no yes no off fail
---------------------------------------------------------------------------
tag in file autowrite ~
current file changed ! winfixbuf option action ~
-----------------------------------------------------------------------------
yes x x no x goto tag
no no x no x read other file, goto tag
no yes yes no x abandon current file,
read other file, goto tag
no yes no no on write current file,
read other file, goto tag
no yes no no off fail
yes x yes x x goto tag
no no no yes x fail
no yes no yes x fail
no yes no yes on fail
no yes no yes off fail
-----------------------------------------------------------------------------

- If the tag is in the current file, the command will always work.
- If the tag is in another file and the current file was not changed, the
Expand All @@ -428,6 +433,8 @@ current file changed ! option action ~
the changes, use the ":w" command and then use ":tag" without an argument.
This works because the tag is put on the stack anyway. If you want to lose
the changes you can use the ":tag!" command.
- If the tag is in another file and the window includes 'winfixbuf', the
command will fail. If the tag is in the same file then it may succeed.

*tag-security*
Note that Vim forbids some commands, for security reasons. This works like
Expand Down
14 changes: 14 additions & 0 deletions runtime/lua/vim/_meta/options.lua

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions runtime/optwin.vim
Expand Up @@ -444,6 +444,7 @@ if has("statusline")
call <SID>AddOption("statusline", gettext("alternate format to be used for a status line"))
call <SID>OptionG("stl", &stl)
endif
call append("$", "\t" .. s:local_to_window)
call <SID>AddOption("equalalways", gettext("make all windows the same size when adding/removing windows"))
call <SID>BinOptionG("ea", &ea)
call <SID>AddOption("eadirection", gettext("in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\""))
Expand All @@ -452,6 +453,8 @@ call <SID>AddOption("winheight", gettext("minimal number of lines used for the c
call append("$", " \tset wh=" . &wh)
call <SID>AddOption("winminheight", gettext("minimal number of lines used for any window"))
call append("$", " \tset wmh=" . &wmh)
call <SID>AddOption("winfixbuf", gettext("keep window focused on a single buffer"))
call <SID>OptionG("wfb", &wfb)
call <SID>AddOption("winfixheight", gettext("keep the height of the window"))
call append("$", "\t" .. s:local_to_window)
call <SID>BinOptionL("wfh")
Expand Down
5 changes: 5 additions & 0 deletions src/nvim/api/vim.c
Expand Up @@ -876,6 +876,11 @@ void nvim_set_current_buf(Buffer buffer, Error *err)
return;
}

if (curwin->w_p_wfb) {
api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
return;
}

try_start();
int result = do_buffer(DOBUF_GOTO, DOBUF_FIRST, FORWARD, buf->b_fnum, 0);
if (!try_end(err) && result == FAIL) {
Expand Down
6 changes: 6 additions & 0 deletions src/nvim/api/window.c
Expand Up @@ -61,6 +61,12 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
if (!win || !buf) {
return;
}

if (win->w_p_wfb) {
api_set_error(err, kErrorTypeException, "%s", e_winfixbuf_cannot_go_to_buffer);
return;
}

if (win == cmdwin_win || win == cmdwin_old_curwin || buf == cmdwin_buf) {
api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
return;
Expand Down
10 changes: 9 additions & 1 deletion src/nvim/arglist.c
Expand Up @@ -623,6 +623,8 @@ void ex_argument(exarg_T *eap)
/// Edit file "argn" of the argument lists.
void do_argfile(exarg_T *eap, int argn)
{
bool is_split_cmd = *eap->cmd == 's';

int old_arg_idx = curwin->w_arg_idx;

if (argn < 0 || argn >= ARGCOUNT) {
Expand All @@ -637,10 +639,16 @@ void do_argfile(exarg_T *eap, int argn)
return;
}

if (!is_split_cmd
&& (&ARGLIST[argn])->ae_fnum != curbuf->b_fnum
&& !check_can_set_curbuf_forceit(eap->forceit)) {
return;
}

setpcmark();

// split window or create new tab page first
if (*eap->cmd == 's' || cmdmod.cmod_tab != 0) {
if (is_split_cmd || cmdmod.cmod_tab != 0) {
if (win_split(0, 0) == FAIL) {
return;
}
Expand Down
6 changes: 6 additions & 0 deletions src/nvim/buffer.c
Expand Up @@ -1305,6 +1305,12 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
return FAIL;
}

if (action == DOBUF_GOTO && buf != curbuf && !check_can_set_curbuf_forceit(forceit)) {
// disallow navigating to another buffer when 'winfixbuf' is applied
return FAIL;
}

if ((action == DOBUF_GOTO || action == DOBUF_SPLIT) && (buf->b_flags & BF_DUMMY)) {
// disallow navigating to the dummy buffer
semsg(_(e_nobufnr), count);
Expand Down
2 changes: 2 additions & 0 deletions src/nvim/buffer_defs.h
Expand Up @@ -139,6 +139,8 @@ typedef struct {
#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
OptInt wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfb;
#define w_p_wfb w_onebuf_opt.wo_wfb // 'winfixbuf'
int wo_wfh;
#define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
int wo_wfw;
Expand Down
4 changes: 4 additions & 0 deletions src/nvim/ex_cmds.c
Expand Up @@ -2008,6 +2008,10 @@ static int check_readonly(int *forceit, buf_T *buf)
/// GETFILE_OPEN_OTHER for successfully opening another file.
int getfile(int fnum, char *ffname_arg, char *sfname_arg, bool setpm, linenr_T lnum, bool forceit)
{
if (!check_can_set_curbuf_forceit(forceit)) {
return GETFILE_ERROR;
}

char *ffname = ffname_arg;
char *sfname = sfname_arg;
bool other;
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/ex_cmds.lua
Expand Up @@ -812,7 +812,7 @@ module.cmds = {
},
{
command = 'drop',
flags = bit.bor(FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR),
flags = bit.bor(BANG, FILES, CMDARG, NEEDARG, ARGOPT, TRLBAR),
addr_type = 'ADDR_NONE',
func = 'ex_drop',
},
Expand Down
21 changes: 21 additions & 0 deletions src/nvim/ex_cmds2.c
Expand Up @@ -444,6 +444,27 @@ int buf_write_all(buf_T *buf, bool forceit)
/// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
void ex_listdo(exarg_T *eap)
{
if (curwin->w_p_wfb) {
if ((eap->cmdidx == CMD_ldo || eap->cmdidx == CMD_lfdo) && !eap->forceit) {
// Disallow :ldo if 'winfixbuf' is applied
semsg("%s", e_winfixbuf_cannot_go_to_buffer);
return;
}

if (win_valid(prevwin)) {
// Change the current window to another because 'winfixbuf' is enabled
curwin = prevwin;
} else {
// Split the window, which will be 'nowinfixbuf', and set curwin to that
exarg_T new_eap = {
.cmdidx = CMD_split,
.cmd = "split",
.arg = "",
};
ex_splitview(&new_eap);
}
}

char *save_ei = NULL;

// Temporarily override SHM_OVER and SHM_OVERALL to avoid that file
Expand Down
16 changes: 14 additions & 2 deletions src/nvim/ex_docmd.c
Expand Up @@ -5334,6 +5334,10 @@ static void ex_resize(exarg_T *eap)
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
if (!check_can_set_curbuf_forceit(eap->forceit)) {
return;
}

char *file_to_find = NULL;
char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
Expand Down Expand Up @@ -5364,6 +5368,14 @@ static void ex_find(exarg_T *eap)
/// ":edit", ":badd", ":balt", ":visual".
static void ex_edit(exarg_T *eap)
{
// Exclude commands which keep the window's current buffer
if (eap->cmdidx != CMD_badd
&& eap->cmdidx != CMD_balt
// All other commands must obey 'winfixbuf' / ! rules
&& !check_can_set_curbuf_forceit(eap->forceit)) {
return;
}

do_exedit(eap, NULL);
}

Expand Down Expand Up @@ -6670,7 +6682,7 @@ static void ex_checkpath(exarg_T *eap)
{
find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
1, (linenr_T)MAXLNUM);
1, (linenr_T)MAXLNUM, eap->forceit);
}

/// ":psearch"
Expand Down Expand Up @@ -6729,7 +6741,7 @@ static void ex_findpat(exarg_T *eap)
if (!eap->skip) {
find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit,
*eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
n, action, eap->line1, eap->line2, eap->forceit);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/nvim/globals.h
Expand Up @@ -971,6 +971,9 @@ EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));

EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
INIT(= N_("E1513: Cannot edit buffer. 'winfixbuf' is enabled"));

EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));

EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
Expand Down
2 changes: 1 addition & 1 deletion src/nvim/insexpand.c
Expand Up @@ -3027,7 +3027,7 @@ static void get_next_include_file_completion(int compl_type)
((compl_type == CTRL_X_PATH_DEFINES
&& !(compl_cont_status & CONT_SOL))
? FIND_DEFINE : FIND_ANY),
1, ACTION_EXPAND, 1, MAXLNUM);
1, ACTION_EXPAND, 1, MAXLNUM, false);
}

/// Get the next set of words matching "compl_pattern" in dictionary or
Expand Down
7 changes: 6 additions & 1 deletion src/nvim/normal.c
Expand Up @@ -3896,6 +3896,10 @@ static void nv_gotofile(cmdarg_T *cap)
return;
}

if (!check_can_set_curbuf_disabled()) {
return;
}

char *ptr = grab_file_name(cap->count1, &lnum);

if (ptr != NULL) {
Expand Down Expand Up @@ -4232,7 +4236,8 @@ static void nv_brackets(cmdarg_T *cap)
(cap->cmdchar == ']'
? curwin->w_cursor.lnum + 1
: 1),
MAXLNUM);
MAXLNUM,
false);
xfree(ptr);
curwin->w_set_curswant = true;
}
Expand Down
2 changes: 2 additions & 0 deletions src/nvim/option.c
Expand Up @@ -4629,6 +4629,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return &(win->w_p_rnu);
case PV_NUW:
return &(win->w_p_nuw);
case PV_WFB:
return &(win->w_p_wfb);
case PV_WFH:
return &(win->w_p_wfh);
case PV_WFW:
Expand Down
19 changes: 19 additions & 0 deletions src/nvim/options.lua
Expand Up @@ -8406,6 +8406,8 @@ return {
"split" when both are present.
uselast If included, jump to the previously used window when
jumping to errors with |quickfix| commands.
If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
applied to the split window.
]=],
expand_cb = 'expand_set_switchbuf',
full_name = 'switchbuf',
Expand Down Expand Up @@ -9816,6 +9818,23 @@ return {
type = 'number',
varname = 'p_window',
},
{
abbreviation = 'wfb',
defaults = { if_true = false },
desc = [=[
If enabled, the buffer and any window that displays it are paired.
For example, attempting to change the buffer with |:edit| will fail.
Other commands which change a window's buffer such as |:cnext| will
also skip any window with 'winfixbuf' enabled. However if a command
has an "!" option, a window can be forced to switch buffers.
]=],
full_name = 'winfixbuf',
pv_name = 'p_wfb',
redraw = { 'current_window' },
scope = { 'window' },
short_desc = N_('pin a window to a specific buffer'),
type = 'boolean',
},
{
abbreviation = 'wfh',
defaults = { if_true = false },
Expand Down

0 comments on commit 141182d

Please sign in to comment.