feat(options): add 'eventignorewin' (#32152)

vim-patch:partial:9.1.1084: Unable to persistently ignore events in a window and its buffers

Problem:  Unable to persistently ignore events in a window and its buffers.
Solution: Add 'eventignorewin' option to ignore events in a window and buffer
          (Luuk van Baal)

Add the window-local 'eventignorewin' option that is analogous to
'eventignore', but applies to a certain window and its buffers. Identify
events that should be allowed in 'eventignorewin', adapt "auto_event"
and "event_tab" to encode this information. Window context is not passed
onto apply_autocmds_group(), and when to ignore an event is a bit
ambiguous when "buf" is not "curbuf", rather than a large refactor, only
ignore an event when all windows into "buf" are ignoring the event.

b7147f8236

vim-patch:9.1.1102: tests: Test_WinScrolled_Resized_eiw() uses wrong filename

Problem:  tests: Test_WinScrolled_Resized_eiw() uses wrong filename
          (Luuk van Baal, after v9.1.1084)
Solution: Rename the filename to something more unique

bfc7719e48
This commit is contained in:
luukvbaal
2025-02-12 11:01:06 +01:00
committed by GitHub
parent 6982106f8c
commit 82a215cb2d
19 changed files with 406 additions and 259 deletions

View File

@ -1676,6 +1676,9 @@ To disable autocommands for some time use the 'eventignore' option. Note that
this may cause unexpected behavior, make sure you restore 'eventignore'
afterwards, using a |:try| block with |:finally|.
To disable autocmds indefinitely in a specific window use the 'eventignorewin'
option. This can only be used to ignore window and buffer related events.
*:noautocmd* *:noa*
To disable autocommands for just one command use the ":noautocmd" command
modifier. This will set 'eventignore' to "all" for the duration of the

View File

@ -319,6 +319,7 @@ OPTIONS
• 'completeopt' flag "preinsert" highlights text to be inserted.
• 'messagesopt' configures |:messages| and |hit-enter| prompt.
• 'tabclose' controls which tab page to focus when closing a tab page.
• 'eventignorewin' to persistently ignore events in a window.
PERFORMANCE

View File

@ -2334,6 +2334,13 @@ A jump table for the options with a short description can be found at |Q_op|.
set ei=WinEnter,WinLeave
<
*'eventignorewin'* *'eiw'*
'eventignorewin' 'eiw' string (default "")
local to window
Similar to 'eventignore' but applies to a particular window and its
buffers, for which window and buffer related autocommands can be
ignored indefinitely without affecting the global 'eventignore'.
*'expandtab'* *'et'* *'noexpandtab'* *'noet'*
'expandtab' 'et' boolean (default off)
local to buffer

View File

@ -696,6 +696,7 @@ Short explanation of each option: *option-list*
'errorfile' 'ef' name of the errorfile for the QuickFix mode
'errorformat' 'efm' description of the lines in the error file
'eventignore' 'ei' autocommand events that are ignored
'eventignorewin' 'eiw' autocommand events that are ignored in a window
'expandtab' 'et' use spaces when <Tab> is inserted
'exrc' 'ex' read init files in the current directory
'fileencoding' 'fenc' file encoding for multibyte text

View File

@ -1989,6 +1989,16 @@ vim.o.ei = vim.o.eventignore
vim.go.eventignore = vim.o.eventignore
vim.go.ei = vim.go.eventignore
--- Similar to 'eventignore' but applies to a particular window and its
--- buffers, for which window and buffer related autocommands can be
--- ignored indefinitely without affecting the global 'eventignore'.
---
--- @type string
vim.o.eventignorewin = ""
vim.o.eiw = vim.o.eventignorewin
vim.wo.eventignorewin = vim.o.eventignorewin
vim.wo.eiw = vim.wo.eventignorewin
--- In Insert mode: Use the appropriate number of spaces to insert a
--- <Tab>. Spaces are used in indents with the '>' and '<' commands and
--- when 'autoindent' is on. To insert a real tab when 'expandtab' is

View File

@ -1,7 +1,7 @@
" These commands create the option window.
"
" Maintainer: The Vim Project <https://github.com/vim/vim>
" Last Change: 2024 Dec 07
" Last Change: 2025 Feb 08
" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
@ -1187,6 +1187,8 @@ call <SID>AddOption("virtualedit", gettext("when to use virtual editing: \"block
call <SID>OptionG("ve", &ve)
call <SID>AddOption("eventignore", gettext("list of autocommand events which are to be ignored"))
call <SID>OptionG("ei", &ei)
call <SID>AddOption("eventignorewin", gettext("list of autocommand events which are to be ignored in a window"))
call <SID>OptionG("eiw", &eiw)
call <SID>AddOption("loadplugins", gettext("load plugin scripts when starting up"))
call <SID>BinOptionG("lpl", &lpl)
call <SID>AddOption("exrc", gettext("enable reading .vimrc/.exrc/.gvimrc in the current directory"))

View File

@ -1,156 +1,139 @@
return {
--- @type [string, string[], boolean][] List of [eventname, aliases, window-local event] tuples.
events = {
'BufAdd', -- after adding a buffer to the buffer list
'BufDelete', -- deleting a buffer from the buffer list
'BufEnter', -- after entering a buffer
'BufFilePost', -- after renaming a buffer
'BufFilePre', -- before renaming a buffer
'BufHidden', -- just after buffer becomes hidden
'BufLeave', -- before leaving a buffer
'BufModifiedSet', -- after the 'modified' state of a buffer changes
'BufNew', -- after creating any buffer
'BufNewFile', -- when creating a buffer for a new file
'BufReadCmd', -- read buffer using command
'BufReadPost', -- after reading a buffer
'BufReadPre', -- before reading a buffer
'BufUnload', -- just before unloading a buffer
'BufWinEnter', -- after showing a buffer in a window
'BufWinLeave', -- just after buffer removed from window
'BufWipeout', -- just before really deleting a buffer
'BufWriteCmd', -- write buffer using command
'BufWritePost', -- after writing a buffer
'BufWritePre', -- before writing a buffer
'ChanInfo', -- info was received about channel
'ChanOpen', -- channel was opened
'CmdUndefined', -- command undefined
'CmdWinEnter', -- after entering the cmdline window
'CmdWinLeave', -- before leaving the cmdline window
'CmdlineChanged', -- command line was modified
'CmdlineEnter', -- after entering cmdline mode
'CmdlineLeave', -- before leaving cmdline mode
'ColorScheme', -- after loading a colorscheme
'ColorSchemePre', -- before loading a colorscheme
'CompleteChanged', -- after popup menu changed
'CompleteDone', -- after finishing insert complete
'CompleteDonePre', -- idem, before clearing info
'CursorHold', -- cursor in same position for a while
'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved
'CursorMovedC', -- cursor was moved in Cmdline mode
'CursorMovedI', -- cursor was moved in Insert mode
'DiagnosticChanged', -- diagnostics in a buffer were modified
'DiffUpdated', -- diffs have been updated
'DirChanged', -- directory changed
'DirChangedPre', -- directory is going to change
'EncodingChanged', -- after changing the 'encoding' option
'ExitPre', -- before exiting
'FileAppendCmd', -- append to a file using command
'FileAppendPost', -- after appending to a file
'FileAppendPre', -- before appending to a file
'FileChangedRO', -- before first change to read-only file
'FileChangedShell', -- after shell command that changed file
'FileChangedShellPost', -- after (not) reloading changed file
'FileReadCmd', -- read from a file using command
'FileReadPost', -- after reading a file
'FileReadPre', -- before reading a file
'FileType', -- new file type detected (user defined)
'FileWriteCmd', -- write to a file using command
'FileWritePost', -- after writing a file
'FileWritePre', -- before writing a file
'FilterReadPost', -- after reading from a filter
'FilterReadPre', -- before reading from a filter
'FilterWritePost', -- after writing to a filter
'FilterWritePre', -- before writing to a filter
'FocusGained', -- got the focus
'FocusLost', -- lost the focus to another app
'FuncUndefined', -- if calling a function which doesn't exist
'GUIEnter', -- after starting the GUI
'GUIFailed', -- after starting the GUI failed
'InsertChange', -- when changing Insert/Replace mode
'InsertCharPre', -- before inserting a char
'InsertEnter', -- when entering Insert mode
'InsertLeave', -- just after leaving Insert mode
'InsertLeavePre', -- just before leaving Insert mode
'LspAttach', -- after an LSP client attaches to a buffer
'LspDetach', -- after an LSP client detaches from a buffer
'LspNotify', -- after an LSP notice has been sent to the server
'LspProgress', -- after a LSP progress update
'LspRequest', -- after an LSP request is started, canceled, or completed
'LspTokenUpdate', -- after a visible LSP token is updated
'MenuPopup', -- just before popup menu is displayed
'ModeChanged', -- after changing the mode
'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc.
'QuitPre', -- before :quit
'RecordingEnter', -- when starting to record a macro
'RecordingLeave', -- just before a macro stops recording
'RemoteReply', -- upon string reception from a remote vim
'SafeState', -- going to wait for a character
'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file
'SessionWritePost', -- after writing a session file
'ShellCmdPost', -- after ":!cmd"
'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd".
'Signal', -- after nvim process received a signal
'SourceCmd', -- sourcing a Vim script using command
'SourcePost', -- after sourcing a Vim script
'SourcePre', -- before sourcing a Vim script
'SpellFileMissing', -- spell file missing
'StdinReadPost', -- after reading from stdin
'StdinReadPre', -- before reading from stdin
'SwapExists', -- found existing swap file
'Syntax', -- syntax selected
'TabClosed', -- a tab has closed
'TabEnter', -- after entering a tab page
'TabLeave', -- before leaving a tab page
'TabNew', -- when creating a new tab
'TabNewEntered', -- after entering a new tab
'TermChanged', -- after changing 'term'
'TermClose', -- after the process exits
'TermEnter', -- after entering Terminal mode
'TermLeave', -- after leaving Terminal mode
'TermOpen', -- after opening a terminal buffer
'TermRequest', -- after an unhandled OSC sequence is emitted
'TermResponse', -- after setting "v:termresponse"
'TextChanged', -- text was modified
'TextChangedI', -- text was modified in Insert mode(no popup)
'TextChangedP', -- text was modified in Insert mode(popup)
'TextChangedT', -- text was modified in Terminal mode
'TextYankPost', -- after a yank or delete was done (y, d, c)
'UIEnter', -- after UI attaches
'UILeave', -- after UI detaches
'User', -- user defined autocommand
'VimEnter', -- after starting Vim
'VimLeave', -- before exiting Vim
'VimLeavePre', -- before exiting Vim and writing ShaDa file
'VimResized', -- after Vim window was resized
'VimResume', -- after Nvim is resumed
'VimSuspend', -- before Nvim is suspended
'WinClosed', -- after closing a window
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window
'WinResized', -- after a window was resized
'WinScrolled', -- after a window was scrolled or resized
},
aliases = {
{
'BufCreate',
'BufAdd',
},
{
'BufRead',
'BufReadPost',
},
{
'BufWrite',
'BufWritePre',
},
{
'FileEncoding',
'EncodingChanged',
},
{ 'BufAdd', { 'BufCreate' }, true }, -- after adding a buffer to the buffer list
{ 'BufDelete', {}, true }, -- deleting a buffer from the buffer list
{ 'BufEnter', {}, true }, -- after entering a buffer
{ 'BufFilePost', {}, true }, -- after renaming a buffer
{ 'BufFilePre', {}, true }, -- before renaming a buffer
{ 'BufHidden', {}, true }, -- just after buffer becomes hidden
{ 'BufLeave', {}, true }, -- before leaving a buffer
{ 'BufModifiedSet', {}, true }, -- after the 'modified' state of a buffer changes
{ 'BufNew', {}, true }, -- after creating any buffer
{ 'BufNewFile', {}, true }, -- when creating a buffer for a new file
{ 'BufReadCmd', {}, true }, -- read buffer using command
{ 'BufReadPost', { 'BufRead' }, true }, -- after reading a buffer
{ 'BufReadPre', {}, true }, -- before reading a buffer
{ 'BufUnload', {}, true }, -- just before unloading a buffer
{ 'BufWinEnter', {}, true }, -- after showing a buffer in a window
{ 'BufWinLeave', {}, true }, -- just after buffer removed from window
{ 'BufWipeout', {}, true }, -- just before really deleting a buffer
{ 'BufWriteCmd', {}, true }, -- write buffer using command
{ 'BufWritePost', {}, true }, -- after writing a buffer
{ 'BufWritePre', { 'BufWrite' }, true }, -- before writing a buffer
{ 'ChanInfo', {}, false }, -- info was received about channel
{ 'ChanOpen', {}, false }, -- channel was opened
{ 'CmdUndefined', {}, false }, -- command undefined
{ 'CmdWinEnter', {}, false }, -- after entering the cmdline window
{ 'CmdWinLeave', {}, false }, -- before leaving the cmdline window
{ 'CmdlineChanged', {}, false }, -- command line was modified
{ 'CmdlineEnter', {}, false }, -- after entering cmdline mode
{ 'CmdlineLeave', {}, false }, -- before leaving cmdline mode
{ 'ColorScheme', {}, false }, -- after loading a colorscheme
{ 'ColorSchemePre', {}, false }, -- before loading a colorscheme
{ 'CompleteChanged', {}, false }, -- after popup menu changed
{ 'CompleteDone', {}, false }, -- after finishing insert complete
{ 'CompleteDonePre', {}, false }, -- idem, before clearing info
{ 'CursorHold', {}, true }, -- cursor in same position for a while
{ 'CursorHoldI', {}, true }, -- idem, in Insert mode
{ 'CursorMoved', {}, true }, -- cursor was moved
{ 'CursorMovedC', {}, true }, -- cursor was moved in Cmdline mode
{ 'CursorMovedI', {}, true }, -- cursor was moved in Insert mode
{ 'DiagnosticChanged', {}, false }, -- diagnostics in a buffer were modified
{ 'DiffUpdated', {}, false }, -- diffs have been updated
{ 'DirChanged', {}, false }, -- directory changed
{ 'DirChangedPre', {}, false }, -- directory is going to change
{ 'EncodingChanged', { 'FileEncoding' }, false }, -- after changing the 'encoding' option
{ 'ExitPre', {}, false }, -- before exiting
{ 'FileAppendCmd', {}, true }, -- append to a file using command
{ 'FileAppendPost', {}, true }, -- after appending to a file
{ 'FileAppendPre', {}, true }, -- before appending to a file
{ 'FileChangedRO', {}, true }, -- before first change to read-only file
{ 'FileChangedShell', {}, true }, -- after shell command that changed file
{ 'FileChangedShellPost', {}, true }, -- after (not) reloading changed file
{ 'FileReadCmd', {}, true }, -- read from a file using command
{ 'FileReadPost', {}, true }, -- after reading a file
{ 'FileReadPre', {}, true }, -- before reading a file
{ 'FileType', {}, true }, -- new file type detected (user defined)
{ 'FileWriteCmd', {}, true }, -- write to a file using command
{ 'FileWritePost', {}, true }, -- after writing a file
{ 'FileWritePre', {}, true }, -- before writing a file
{ 'FilterReadPost', {}, true }, -- after reading from a filter
{ 'FilterReadPre', {}, true }, -- before reading from a filter
{ 'FilterWritePost', {}, true }, -- after writing to a filter
{ 'FilterWritePre', {}, true }, -- before writing to a filter
{ 'FocusGained', {}, false }, -- got the focus
{ 'FocusLost', {}, false }, -- lost the focus to another app
{ 'FuncUndefined', {}, false }, -- if calling a function which doesn't exist
{ 'GUIEnter', {}, false }, -- after starting the GUI
{ 'GUIFailed', {}, false }, -- after starting the GUI failed
{ 'InsertChange', {}, true }, -- when changing Insert/Replace mode
{ 'InsertCharPre', {}, true }, -- before inserting a char
{ 'InsertEnter', {}, true }, -- when entering Insert mode
{ 'InsertLeave', {}, true }, -- just after leaving Insert mode
{ 'InsertLeavePre', {}, true }, -- just before leaving Insert mode
{ 'LspAttach', {}, false }, -- after an LSP client attaches to a buffer
{ 'LspDetach', {}, false }, -- after an LSP client detaches from a buffer
{ 'LspNotify', {}, false }, -- after an LSP notice has been sent to the server
{ 'LspProgress', {}, false }, -- after a LSP progress update
{ 'LspRequest', {}, false }, -- after an LSP request is started, canceled, or completed
{ 'LspTokenUpdate', {}, false }, -- after a visible LSP token is updated
{ 'MenuPopup', {}, false }, -- just before popup menu is displayed
{ 'ModeChanged', {}, false }, -- after changing the mode
{ 'OptionSet', {}, false }, -- after setting any option
{ 'QuickFixCmdPost', {}, false }, -- after :make, :grep etc.
{ 'QuickFixCmdPre', {}, false }, -- before :make, :grep etc.
{ 'QuitPre', {}, false }, -- before :quit
{ 'RecordingEnter', {}, true }, -- when starting to record a macro
{ 'RecordingLeave', {}, true }, -- just before a macro stops recording
{ 'RemoteReply', {}, false }, -- upon string reception from a remote vim
{ 'SafeState', {}, false }, -- going to wait for a character
{ 'SearchWrapped', {}, true }, -- after the search wrapped around
{ 'SessionLoadPost', {}, false }, -- after loading a session file
{ 'SessionWritePost', {}, false }, -- after writing a session file
{ 'ShellCmdPost', {}, false }, -- after ":!cmd"
{ 'ShellFilterPost', {}, true }, -- after ":1,2!cmd", ":w !cmd", ":r !cmd".
{ 'Signal', {}, false }, -- after nvim process received a signal
{ 'SourceCmd', {}, false }, -- sourcing a Vim script using command
{ 'SourcePost', {}, false }, -- after sourcing a Vim script
{ 'SourcePre', {}, false }, -- before sourcing a Vim script
{ 'SpellFileMissing', {}, false }, -- spell file missing
{ 'StdinReadPost', {}, false }, -- after reading from stdin
{ 'StdinReadPre', {}, false }, -- before reading from stdin
{ 'SwapExists', {}, false }, -- found existing swap file
{ 'Syntax', {}, false }, -- syntax selected
{ 'TabClosed', {}, false }, -- a tab has closed
{ 'TabEnter', {}, false }, -- after entering a tab page
{ 'TabLeave', {}, false }, -- before leaving a tab page
{ 'TabNew', {}, false }, -- when creating a new tab
{ 'TabNewEntered', {}, false }, -- after entering a new tab
{ 'TermChanged', {}, false }, -- after changing 'term'
{ 'TermClose', {}, false }, -- after the process exits
{ 'TermEnter', {}, false }, -- after entering Terminal mode
{ 'TermLeave', {}, false }, -- after leaving Terminal mode
{ 'TermOpen', {}, false }, -- after opening a terminal buffer
{ 'TermRequest', {}, false }, -- after an unhandled OSC sequence is emitted
{ 'TermResponse', {}, false }, -- after setting "v:termresponse"
{ 'TextChanged', {}, true }, -- text was modified
{ 'TextChangedI', {}, true }, -- text was modified in Insert mode(no popup)
{ 'TextChangedP', {}, true }, -- text was modified in Insert mode(popup)
{ 'TextChangedT', {}, true }, -- text was modified in Terminal mode
{ 'TextYankPost', {}, true }, -- after a yank or delete was done (y, d, c)
{ 'UIEnter', {}, false }, -- after UI attaches
{ 'UILeave', {}, false }, -- after UI detaches
{ 'User', {}, false }, -- user defined autocommand
{ 'VimEnter', {}, false }, -- after starting Vim
{ 'VimLeave', {}, false }, -- before exiting Vim
{ 'VimLeavePre', {}, false }, -- before exiting Vim and writing ShaDa file
{ 'VimResized', {}, false }, -- after Vim window was resized
{ 'VimResume', {}, false }, -- after Nvim is resumed
{ 'VimSuspend', {}, false }, -- before Nvim is suspended
{ 'WinClosed', {}, true }, -- after closing a window
{ 'WinEnter', {}, true }, -- after entering a window
{ 'WinLeave', {}, true }, -- before leaving a window
{ 'WinNew', {}, false }, -- when entering a new window
{ 'WinResized', {}, true }, -- after a window was resized
{ 'WinScrolled', {}, true }, -- after a window was scrolled or resized
},
-- List of nvim-specific events or aliases for the purpose of generating
-- syntax file

View File

@ -634,7 +634,7 @@ event_T event_name2nr(const char *start, char **end)
if (event_names[i].name == NULL) {
return NUM_EVENTS;
}
return event_names[i].event;
return (event_T)abs(event_names[i].event);
}
/// Return the event number for event name "str".
@ -643,7 +643,7 @@ event_T event_name2nr_str(String str)
{
for (int i = 0; event_names[i].name != NULL; i++) {
if (str.size == event_names[i].len && STRNICMP(str.data, event_names[i].name, str.size) == 0) {
return event_names[i].event;
return (event_T)abs(event_names[i].event);
}
}
return NUM_EVENTS;
@ -658,25 +658,23 @@ const char *event_nr2name(event_T event)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST
{
for (int i = 0; event_names[i].name != NULL; i++) {
if (event_names[i].event == event) {
if ((event_T)abs(event_names[i].event) == event) {
return event_names[i].name;
}
}
return "Unknown";
}
/// Return true if "event" is included in 'eventignore'.
/// Return true if "event" is included in 'eventignore(win)'.
///
/// @param event event to check
static bool event_ignored(event_T event)
bool event_ignored(event_T event, char *ei)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
char *p = p_ei;
while (*p != NUL) {
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
while (*ei != NUL) {
if (STRNICMP(ei, "all", 3) == 0 && (ei[3] == NUL || ei[3] == ',')) {
return true;
} else if (event_name2nr(p, &p) == event) {
} else if (event_name2nr(ei, &ei) == event) {
return true;
}
}
@ -684,19 +682,23 @@ static bool event_ignored(event_T event)
return false;
}
// Return OK when the contents of p_ei is valid, FAIL otherwise.
int check_ei(void)
/// Return OK when the contents of 'eventignore' or 'eventignorewin' is valid,
/// FAIL otherwise.
int check_ei(char *ei)
{
char *p = p_ei;
bool win = ei != p_ei;
while (*p) {
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
p += 3;
if (*p == ',') {
p++;
while (*ei) {
if (STRNICMP(ei, "all", 3) == 0 && (ei[3] == NUL || ei[3] == ',')) {
ei += 3;
if (*ei == ',') {
ei++;
}
} else {
event_T event = event_name2nr(ei, &ei);
if (event == NUM_EVENTS || (win && event_names[event].event > 0)) {
return FAIL;
}
} else if (event_name2nr(p, &p) == NUM_EVENTS) {
return FAIL;
}
}
@ -1631,7 +1633,24 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
}
// Ignore events in 'eventignore'.
if (event_ignored(event)) {
if (event_ignored(event, p_ei)) {
goto BYPASS_AU;
}
bool win_ignore = false;
// If event is allowed in 'eventignorewin', check if curwin or all windows
// into "buf" are ignoring the event.
if (buf == curbuf && event_names[event].event <= 0) {
win_ignore = event_ignored(event, curwin->w_p_eiw);
} else if (buf != NULL && event_names[event].event <= 0) {
for (size_t i = 0; i < kv_size(buf->b_wininfo); i++) {
WinInfo *wip = kv_A(buf->b_wininfo, i);
if (wip->wi_win != NULL && wip->wi_win->w_buffer == buf) {
win_ignore = event_ignored(event, wip->wi_win->w_p_eiw);
}
}
}
if (win_ignore) {
goto BYPASS_AU;
}
@ -2279,9 +2298,21 @@ char *expand_get_event_name(expand_T *xp, int idx)
/// Function given to ExpandGeneric() to obtain the list of event names. Don't
/// include groups.
char *get_event_name_no_group(expand_T *xp FUNC_ATTR_UNUSED, int idx)
char *get_event_name_no_group(expand_T *xp FUNC_ATTR_UNUSED, int idx, bool win)
{
return event_names[idx].name;
if (!win) {
return event_names[idx].name;
}
// Need to check subset of allowed values for 'eventignorewin'.
int j = 0;
for (int i = 0; i < NUM_EVENTS; i++) {
j += event_names[i].event <= 0;
if (j == idx + 1) {
return event_names[i].name;
}
}
return NULL;
}
/// Check whether given autocommand is supported

View File

@ -96,6 +96,8 @@ typedef struct {
int wo_diff;
#define w_p_diff w_onebuf_opt.wo_diff // 'diff'
char *wo_fdc;
#define w_p_eiw w_onebuf_opt.wo_eiw // 'eventignorewin'
char *wo_eiw;
#define w_p_fdc w_onebuf_opt.wo_fdc // 'foldcolumn'
char *wo_fdc_save;
#define w_p_fdc_save w_onebuf_opt.wo_fdc_save // 'fdc' saved for diff mode

View File

@ -3,7 +3,6 @@ local names_file = arg[2]
local auevents = require('auevents')
local events = auevents.events
local aliases = auevents.aliases
local enum_tgt = io.open(fileio_enum_file, 'w')
local names_tgt = io.open(names_file, 'w')
@ -16,46 +15,28 @@ names_tgt:write([[
static const struct event_name {
size_t len;
char *name;
event_T event;
int event;
} event_names[] = {]])
local aliases = 0
for i, event in ipairs(events) do
enum_tgt:write(('\n EVENT_%s = %u,'):format(event:upper(), i - 1))
names_tgt:write(('\n {%u, "%s", EVENT_%s},'):format(#event, event, event:upper()))
enum_tgt:write(('\n EVENT_%s = %u,'):format(event[1]:upper(), i + aliases - 1))
-- Events with positive keys aren't allowed in 'eventignorewin'.
local event_int = ('%sEVENT_%s'):format(event[3] and '-' or '', event[1]:upper())
names_tgt:write(('\n {%u, "%s", %s},'):format(#event[1], event[1], event_int))
for _, alias in ipairs(event[2]) do
aliases = aliases + 1
names_tgt:write(('\n {%u, "%s", %s},'):format(#alias, alias, event_int))
enum_tgt:write(('\n EVENT_%s = %u,'):format(alias:upper(), i + aliases - 1))
end
if i == #events then -- Last item.
enum_tgt:write(('\n NUM_EVENTS = %u,'):format(i))
enum_tgt:write(('\n NUM_EVENTS = %u,'):format(i + aliases))
end
end
for _, v in ipairs(aliases) do
local alias = v[1]
local event = v[2]
names_tgt:write(('\n {%u, "%s", EVENT_%s},'):format(#alias, alias, event:upper()))
end
names_tgt:write('\n {0, NULL, (event_T)0},')
names_tgt:write('\n {0, NULL, (event_T)0},\n};\n')
names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = { 0 };\n')
names_tgt:close()
enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
do
names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = {\n ')
local line_len = 1
for _ = 1, (#events - 1) do
line_len = line_len + #' KV_INITIAL_VALUE,'
if line_len > 80 then
names_tgt:write('\n ')
line_len = 1 + #' KV_INITIAL_VALUE,'
end
names_tgt:write(' KV_INITIAL_VALUE,')
end
if line_len + #' KV_INITIAL_VALUE' > 80 then
names_tgt:write('\n KV_INITIAL_VALUE')
else
names_tgt:write(' KV_INITIAL_VALUE')
end
names_tgt:write('\n};\n')
end
enum_tgt:close()
names_tgt:close()

View File

@ -114,20 +114,20 @@ local vimau_start = 'syn keyword vimAutoEvent contained '
w('\n\n' .. vimau_start)
for _, au in ipairs(auevents.events) do
if not auevents.nvim_specific[au] then
if not auevents.nvim_specific[au[1]] then
if lld.line_length > 850 then
w('\n' .. vimau_start)
end
w(' ' .. au)
w(' ' .. au[1])
for _, alias in ipairs(au[2]) do
if lld.line_length > 850 then
w('\n' .. vimau_start)
end
-- au[1] is aliased to alias
w(' ' .. alias)
end
end
end
for _, au in pairs(auevents.aliases) do
if lld.line_length > 850 then
w('\n' .. vimau_start)
end
-- au[1] is aliased to au[2]
w(' ' .. au[1])
end
local nvimau_start = 'syn keyword nvimAutoEvent contained '
w('\n\n' .. nvimau_start)

View File

@ -4580,6 +4580,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return &(win->w_p_cc);
case kOptDiff:
return &(win->w_p_diff);
case kOptEventignorewin:
return &(win->w_p_eiw);
case kOptFoldcolumn:
return &(win->w_p_fdc);
case kOptFoldenable:
@ -4875,6 +4877,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_cc = copy_option_val(from->wo_cc);
to->wo_diff = from->wo_diff;
to->wo_diff_saved = from->wo_diff_saved;
to->wo_eiw = copy_option_val(from->wo_eiw);
to->wo_cocu = copy_option_val(from->wo_cocu);
to->wo_cole = from->wo_cole;
to->wo_fdc = copy_option_val(from->wo_fdc);
@ -4919,6 +4922,7 @@ static void check_winopt(winopt_T *wop)
check_string_option(&wop->wo_fde);
check_string_option(&wop->wo_fdt);
check_string_option(&wop->wo_fmr);
check_string_option(&wop->wo_eiw);
check_string_option(&wop->wo_scl);
check_string_option(&wop->wo_rlc);
check_string_option(&wop->wo_sbr);
@ -4946,6 +4950,7 @@ void clear_winopt(winopt_T *wop)
clear_string_option(&wop->wo_fde);
clear_string_option(&wop->wo_fdt);
clear_string_option(&wop->wo_fmr);
clear_string_option(&wop->wo_eiw);
clear_string_option(&wop->wo_scl);
clear_string_option(&wop->wo_rlc);
clear_string_option(&wop->wo_sbr);

View File

@ -2627,6 +2627,23 @@ local options = {
type = 'string',
varname = 'p_ei',
},
{
abbreviation = 'eiw',
cb = 'did_set_eventignore',
defaults = '',
deny_duplicates = true,
desc = [=[
Similar to 'eventignore' but applies to a particular window and its
buffers, for which window and buffer related autocommands can be
ignored indefinitely without affecting the global 'eventignore'.
]=],
expand_cb = 'expand_set_eventignore',
full_name = 'eventignorewin',
list = 'onecomma',
scope = { 'win' },
short_desc = N_('autocommand events that are ignored in a window'),
type = 'string',
},
{
abbreviation = 'et',
defaults = false,

View File

@ -1082,27 +1082,32 @@ int expand_set_encoding(optexpand_T *args, int *numMatches, char ***matches)
return expand_set_opt_generic(args, get_encoding_name, numMatches, matches);
}
/// The 'eventignore' option is changed.
const char *did_set_eventignore(optset_T *args FUNC_ATTR_UNUSED)
/// The 'eventignore(win)' option is changed.
const char *did_set_eventignore(optset_T *args)
{
if (check_ei() == FAIL) {
char **varp = (char **)args->os_varp;
if (check_ei(*varp) == FAIL) {
return e_invarg;
}
return NULL;
}
static bool expand_eiw = false;
static char *get_eventignore_name(expand_T *xp, int idx)
{
// 'eventignore' allows special keyword "all" in addition to
// 'eventignore(win)' allows special keyword "all" in addition to
// all event names.
if (idx == 0) {
return "all";
}
return get_event_name_no_group(xp, idx - 1);
return get_event_name_no_group(xp, idx - 1, expand_eiw);
}
int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches)
{
expand_eiw = args->oe_varp != (char *)&p_ei;
return expand_set_opt_generic(args, get_eventignore_name, numMatches, matches);
}

View File

@ -5530,31 +5530,20 @@ static dict_T *make_win_info_dict(int width, int height, int topline, int topfil
return NULL;
}
/// Return values of check_window_scroll_resize():
enum {
CWSR_SCROLLED = 1, ///< at least one window scrolled
CWSR_RESIZED = 2, ///< at least one window size changed
};
/// This function is used for three purposes:
/// 1. Goes over all windows in the current tab page and returns:
/// 0 no scrolling and no size changes found
/// CWSR_SCROLLED at least one window scrolled
/// CWSR_RESIZED at least one window changed size
/// CWSR_SCROLLED + CWSR_RESIZED both
/// "size_count" is set to the nr of windows with size changes.
/// "first_scroll_win" is set to the first window with any relevant changes.
/// "first_size_win" is set to the first window with size changes.
/// 1. Goes over all windows in the current tab page and sets:
/// "size_count" to the nr of windows with size changes.
/// "first_scroll_win" to the first window with any relevant changes.
/// "first_size_win" to the first window with size changes.
///
/// 2. When the first three arguments are NULL and "winlist" is not NULL,
/// "winlist" is set to the list of window IDs with size changes.
///
/// 3. When the first three arguments are NULL and "v_event" is not NULL,
/// information about changed windows is added to "v_event".
static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
win_T **first_size_win, list_T *winlist, dict_T *v_event)
static void check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
win_T **first_size_win, list_T *winlist, dict_T *v_event)
{
int result = 0;
// int listidx = 0;
int tot_width = 0;
int tot_height = 0;
@ -5576,10 +5565,11 @@ static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
continue;
}
const bool size_changed = wp->w_last_width != wp->w_width
|| wp->w_last_height != wp->w_height;
const bool ignore_scroll = event_ignored(EVENT_WINSCROLLED, wp->w_p_eiw);
const bool size_changed = !event_ignored(EVENT_WINRESIZED, wp->w_p_eiw)
&& (wp->w_last_width != wp->w_width
|| wp->w_last_height != wp->w_height);
if (size_changed) {
result |= CWSR_RESIZED;
if (winlist != NULL) {
// Add this window to the list of changed windows.
typval_T tv = {
@ -5597,21 +5587,19 @@ static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
}
// For WinScrolled the first window with a size change is used
// even when it didn't scroll.
if (*first_scroll_win == NULL) {
if (*first_scroll_win == NULL && !ignore_scroll) {
*first_scroll_win = wp;
}
}
}
const bool scroll_changed = wp->w_last_topline != wp->w_topline
|| wp->w_last_topfill != wp->w_topfill
|| wp->w_last_leftcol != wp->w_leftcol
|| wp->w_last_skipcol != wp->w_skipcol;
if (scroll_changed) {
result |= CWSR_SCROLLED;
if (first_scroll_win != NULL && *first_scroll_win == NULL) {
*first_scroll_win = wp;
}
const bool scroll_changed = !ignore_scroll
&& (wp->w_last_topline != wp->w_topline
|| wp->w_last_topfill != wp->w_topfill
|| wp->w_last_leftcol != wp->w_leftcol
|| wp->w_last_skipcol != wp->w_skipcol);
if (scroll_changed && first_scroll_win != NULL && *first_scroll_win == NULL) {
*first_scroll_win = wp;
}
if ((size_changed || scroll_changed) && v_event != NULL) {
@ -5655,8 +5643,6 @@ static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win,
}
}
}
return result;
}
/// Trigger WinScrolled and/or WinResized if any window in the current tab page
@ -5676,11 +5662,9 @@ void may_trigger_win_scrolled_resized(void)
int size_count = 0;
win_T *first_scroll_win = NULL;
win_T *first_size_win = NULL;
int cwsr = check_window_scroll_resize(&size_count,
&first_scroll_win, &first_size_win,
NULL, NULL);
check_window_scroll_resize(&size_count, &first_scroll_win, &first_size_win, NULL, NULL);
bool trigger_resize = do_resize && size_count > 0;
bool trigger_scroll = do_scroll && cwsr != 0;
bool trigger_scroll = do_scroll && first_scroll_win != NULL;
if (!trigger_resize && !trigger_scroll) {
return; // no relevant changes
}

View File

@ -1,5 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local clear = n.clear
local write_file = t.write_file
@ -40,3 +41,53 @@ it('no E440 in quickfix window when autocommand invalidates undo', function()
feed('G')
eq('', api.nvim_get_vvar('errmsg'))
end)
-- oldtest: Test_WinScrolled_Resized_eiw()
it('WinScrolled and WinResized events can be ignored in a window', function()
local screen = Screen.new()
n.exec([[
call setline(1, ['foo']->repeat(32))
set eventignorewin=WinScrolled,WinResized
split
let [g:afile,g:resized,g:scrolled] = ['none',0,0]
au WinScrolled * let [g:afile,g:scrolled] = [expand('<afile>'),g:scrolled+1]
au WinResized * let [g:afile,g:resized] = [expand('<afile>'),g:resized+1]
]])
feed('<C-W>-')
screen:expect([[
^foo |
foo |*4
{3:[No Name] [+] }|
foo |*6
{2:[No Name] [+] }|
|
]])
feed(':echo g:afile g:resized g:scrolled<CR>')
screen:expect({ any = 'none 0 0.*' })
feed('G')
screen:expect([[
foo |*4
^foo |
{3:[No Name] [+] }|
foo |*6
{2:[No Name] [+] }|
none 0 0 |
]])
feed('gg')
screen:expect([[
^foo |
foo |*4
{3:[No Name] [+] }|
foo |*6
{2:[No Name] [+] }|
none 0 0 |
]])
feed(':echo g:afile g:resized g:scrolled')
screen:expect({ any = ':echo g:afile g:resized g:scrolled.*' })
feed('<CR>')
screen:expect({ any = 'none 0 0.*' })
feed(':set eventignorewin=<CR><C-W>w<C-W>+')
screen:expect({ any = ':set eventignorewin=.*' })
feed(':echo win_getid() g:afile g:resized g:scrolled<CR>')
screen:expect({ any = '1000 1001 1 1.*' })
end)

View File

@ -220,6 +220,8 @@ let test_values = {
"\ 'encoding': [['latin1'], ['xxx', '']],
\ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'],
\ ['xxx']],
\ 'eventignorewin': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'],
\ ['xxx', 'WinNew']],
\ 'fileencoding': [['', 'latin1', 'xxx'], []],
\ 'fileformat': [['dos', 'unix', 'mac'], ['xxx']],
\ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']],

View File

@ -4205,4 +4205,64 @@ func Test_OptionSet_cmdheight()
set cmdheight& mouse& laststatus&
endfunc
func Test_eventignorewin()
defer CleanUpTestAuGroup()
augroup testing
au WinEnter * :call add(g:evs, ["WinEnter", expand("<afile>")])
au WinLeave * :call add(g:evs, ["WinLeave", expand("<afile>")])
au BufWinEnter * :call add(g:evs, ["BufWinEnter", expand("<afile>")])
augroup END
let g:evs = []
set eventignorewin=WinLeave,WinEnter
split foo
call assert_equal([['BufWinEnter', 'foo']], g:evs)
set eventignorewin=all
edit bar
call assert_equal([['BufWinEnter', 'foo']], g:evs)
set eventignorewin=
wincmd w
call assert_equal([['BufWinEnter', 'foo'], ['WinLeave', 'bar']], g:evs)
only!
%bwipe!
set eventignorewin&
unlet g:evs
endfunc
func Test_WinScrolled_Resized_eiw()
CheckRunVimInTerminal
let lines =<< trim END
call setline(1, ['foo']->repeat(32))
set eventignorewin=WinScrolled,WinResized
split
let [g:afile,g:resized,g:scrolled] = ['none',0,0]
au WinScrolled * let [g:afile,g:scrolled] = [expand('<afile>'),g:scrolled+1]
au WinResized * let [g:afile,g:resized] = [expand('<afile>'),g:resized+1]
END
call writefile(lines, 'Xtest_winscrolled_eiw', 'D')
let buf = RunVimInTerminal('-S Xtest_winscrolled_eiw', {'rows': 10})
" Both windows are ignoring resize events
call term_sendkeys(buf, "\<C-W>-")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:afile g:resized g:scrolled\<CR>")
call WaitForAssert({-> assert_equal('none 0 0', term_getline(buf, 10))}, 1000)
" And scroll events
call term_sendkeys(buf, "Ggg")
call TermWait(buf)
call term_sendkeys(buf, ":echo g:afile g:resized g:scrolled\<CR>")
call WaitForAssert({-> assert_equal('none 0 0', term_getline(buf, 10))}, 1000)
" Un-ignore events in second window, make first window current and resize
call term_sendkeys(buf, ":set eventignorewin=\<CR>\<C-W>w\<C-W>+")
call TermWait(buf)
call term_sendkeys(buf, ":echo win_getid() g:afile g:resized g:scrolled\<CR>")
call WaitForAssert({-> assert_equal('1000 1001 1 1', term_getline(buf, 10))}, 1000)
call StopVimInTerminal(buf)
endfunc
" vim: shiftwidth=2 sts=2 expandtab

View File

@ -589,6 +589,7 @@ func Test_set_completion_string_values()
" Other string options that queries the system rather than fixed enum names
call assert_equal(['all', 'BufAdd'], getcompletion('set eventignore=', 'cmdline')[0:1])
call assert_equal(['WinLeave', 'WinResized', 'WinScrolled'], getcompletion('set eiw=', 'cmdline')[-3:-1])
call assert_equal('latin1', getcompletion('set fileencodings=', 'cmdline')[1])
" call assert_equal('top', getcompletion('set printoptions=', 'cmdline')[0])
" call assert_equal('SpecialKey', getcompletion('set wincolor=', 'cmdline')[0])
@ -2506,6 +2507,7 @@ func Test_string_option_revert_on_failure()
\ ['eadirection', 'hor', 'a123'],
\ ['encoding', 'utf-8', 'a123'],
\ ['eventignore', 'TextYankPost', 'a123'],
\ ['eventignorewin', 'WinScrolled', 'a123'],
\ ['fileencoding', 'utf-8', 'a123,'],
\ ['fileformat', 'mac', 'a123'],
\ ['fileformats', 'mac', 'a123'],