Merge #9728 from justinmk/autocmd-once

This commit is contained in:
Justin M. Keyes
2019-03-15 12:38:53 +01:00
committed by GitHub
8 changed files with 79 additions and 51 deletions

View File

@@ -40,7 +40,7 @@ effects. Be careful not to destroy your text.
2. Defining autocommands *autocmd-define* 2. Defining autocommands *autocmd-define*
*:au* *:autocmd* *:au* *:autocmd*
:au[tocmd] [group] {event} {pat} [-once] [-nested] {cmd} :au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}
Add {cmd} to the list of commands that Vim will Add {cmd} to the list of commands that Vim will
execute automatically on {event} for a file matching execute automatically on {event} for a file matching
{pat} |autocmd-patterns|. {pat} |autocmd-patterns|.
@@ -48,9 +48,9 @@ effects. Be careful not to destroy your text.
:autocmd and won't start a comment. :autocmd and won't start a comment.
Nvim always adds {cmd} after existing autocommands so Nvim always adds {cmd} after existing autocommands so
they execute in the order in which they were defined. they execute in the order in which they were defined.
See |autocmd-nested| for [-nested]. See |autocmd-nested| for [++nested].
*autocmd-once* *autocmd-once*
If [-once] is supplied the command is executed once, If [++once] is supplied the command is executed once,
then removed ("one shot"). then removed ("one shot").
The special pattern <buffer> or <buffer=N> defines a buffer-local autocommand. The special pattern <buffer> or <buffer=N> defines a buffer-local autocommand.
@@ -119,11 +119,11 @@ prompt. When one command outputs two messages this can happen anyway.
============================================================================== ==============================================================================
3. Removing autocommands *autocmd-remove* 3. Removing autocommands *autocmd-remove*
:au[tocmd]! [group] {event} {pat} [-once] [-nested] {cmd} :au[tocmd]! [group] {event} {pat} [++once] [++nested] {cmd}
Remove all autocommands associated with {event} and Remove all autocommands associated with {event} and
{pat}, and add the command {cmd}. {pat}, and add the command {cmd}.
See |autocmd-once| for [-once]. See |autocmd-once| for [++once].
See |autocmd-nested| for [-nested]. See |autocmd-nested| for [++nested].
:au[tocmd]! [group] {event} {pat} :au[tocmd]! [group] {event} {pat}
Remove all autocommands associated with {event} and Remove all autocommands associated with {event} and
@@ -1446,9 +1446,9 @@ instead of ":q!".
*autocmd-nested* *E218* *autocmd-nested* *E218*
By default, autocommands do not nest. If you use ":e" or ":w" in an By default, autocommands do not nest. If you use ":e" or ":w" in an
autocommand, Vim does not execute the BufRead and BufWrite autocommands for autocommand, Vim does not execute the BufRead and BufWrite autocommands for
those commands. If you do want this, use the "-nested" flag for those those commands. If you do want this, use the "++nested" flag for those
commands in which you want nesting. For example: > commands in which you want nesting. For example: >
:autocmd FileChangedShell *.c -nested e! :autocmd FileChangedShell *.c ++nested e!
The nesting is limited to 10 levels to get out of recursive loops. The nesting is limited to 10 levels to get out of recursive loops.
It's possible to use the ":au" command in an autocommand. This can be a It's possible to use the ":au" command in an autocommand. This can be a

View File

@@ -35,7 +35,7 @@ There are 3 ways to create a terminal buffer:
< <
Note: The "term://" pattern is handled by a BufReadCmd handler, so the Note: The "term://" pattern is handled by a BufReadCmd handler, so the
|autocmd-nested| modifier is required to use it in an autocmd. > |autocmd-nested| modifier is required to use it in an autocmd. >
autocmd VimEnter * -nested split term://sh autocmd VimEnter * ++nested split term://sh
< This is only mentioned for reference; use |:terminal| instead. < This is only mentioned for reference; use |:terminal| instead.
When the terminal starts, the buffer contents are updated and the buffer is When the terminal starts, the buffer contents are updated and the buffer is

View File

@@ -453,14 +453,14 @@ matching BufWritePre autocommands and executes them, and then it
performs the ":write". performs the ":write".
The general form of the :autocmd command is as follows: > The general form of the :autocmd command is as follows: >
:autocmd [group] {events} {file_pattern} [-nested] {command} :autocmd [group] {events} {file_pattern} [++nested] {command}
The [group] name is optional. It is used in managing and calling the commands The [group] name is optional. It is used in managing and calling the commands
(more on this later). The {events} parameter is a list of events (comma (more on this later). The {events} parameter is a list of events (comma
separated) that trigger the command. separated) that trigger the command.
{file_pattern} is a filename, usually with wildcards. For example, using {file_pattern} is a filename, usually with wildcards. For example, using
"*.txt" makes the autocommand be used for all files whose name end in ".txt". "*.txt" makes the autocommand be used for all files whose name end in ".txt".
The optional [-nested] flag allows for nesting of autocommands (see below), The optional [++nested] flag allows for nesting of autocommands (see below),
and finally, {command} is the command to be executed. and finally, {command} is the command to be executed.
@@ -576,9 +576,9 @@ NESTING
Generally, commands executed as the result of an autocommand event will not Generally, commands executed as the result of an autocommand event will not
trigger any new events. If you read a file in response to a FileChangedShell trigger any new events. If you read a file in response to a FileChangedShell
event, it will not trigger the autocommands that would set the syntax, for event, it will not trigger the autocommands that would set the syntax, for
example. To make the events triggered, add the "-nested" flag: > example. To make the events triggered, add the "++nested" flag: >
:autocmd FileChangedShell * -nested edit :autocmd FileChangedShell * ++nested edit
EXECUTING AUTOCOMMANDS EXECUTING AUTOCOMMANDS

View File

@@ -133,7 +133,7 @@ Command-line highlighting:
removed in the future). removed in the future).
Commands: Commands:
|:autocmd| accepts the `once` flag |:autocmd| accepts the `++once` flag
|:checkhealth| |:checkhealth|
|:cquit| can use [count] to set the exit code |:cquit| can use [count] to set the exit code
|:drop| is always available |:drop| is always available

View File

@@ -880,15 +880,15 @@ CTRL-W g } *CTRL-W_g}*
cursor. This is less clever than using |:ptag|, but you don't cursor. This is less clever than using |:ptag|, but you don't
need a tags file and it will also find matches in system need a tags file and it will also find matches in system
include files. Example: > include files. Example: >
:au! CursorHold *.[ch] -nested exe "silent! psearch " . expand("<cword>") :au! CursorHold *.[ch] ++nested exe "silent! psearch " . expand("<cword>")
< Warning: This can be slow. < Warning: This can be slow.
Example *CursorHold-example* > Example *CursorHold-example* >
:au! CursorHold *.[ch] -nested exe "silent! ptag " . expand("<cword>") :au! CursorHold *.[ch] ++nested exe "silent! ptag " . expand("<cword>")
This will cause a ":ptag" to be executed for the keyword under the cursor, This will cause a ":ptag" to be executed for the keyword under the cursor,
when the cursor hasn't moved for the time set with 'updatetime'. "-nested" when the cursor hasn't moved for the time set with 'updatetime'. "++nested"
makes other autocommands be executed, so that syntax highlighting works in the makes other autocommands be executed, so that syntax highlighting works in the
preview window. The "silent!" avoids an error message when the tag could not preview window. The "silent!" avoids an error message when the tag could not
be found. Also see |CursorHold|. To disable this again: > be found. Also see |CursorHold|. To disable this again: >
@@ -898,7 +898,7 @@ be found. Also see |CursorHold|. To disable this again: >
A nice addition is to highlight the found tag, avoid the ":ptag" when there A nice addition is to highlight the found tag, avoid the ":ptag" when there
is no word under the cursor, and a few other things: > is no word under the cursor, and a few other things: >
:au! CursorHold *.[ch] -nested call PreviewWord() :au! CursorHold *.[ch] ++nested call PreviewWord()
:func PreviewWord() :func PreviewWord()
: if &previewwindow " don't do this in the preview window : if &previewwindow " don't do this in the preview window
: return : return

View File

@@ -5591,39 +5591,48 @@ static void au_del_cmd(AutoCmd *ac)
au_need_clean = true; au_need_clean = true;
} }
/* /// Cleanup autocommands and patterns that have been deleted.
* Cleanup autocommands and patterns that have been deleted. /// This is only done when not executing autocommands.
* This is only done when not executing autocommands.
*/
static void au_cleanup(void) static void au_cleanup(void)
{ {
AutoPat *ap, **prev_ap; AutoPat *ap, **prev_ap;
AutoCmd *ac, **prev_ac; AutoCmd *ac, **prev_ac;
event_T event; event_T event;
if (autocmd_busy || !au_need_clean) if (autocmd_busy || !au_need_clean) {
return; return;
}
/* loop over all events */ // Loop over all events.
for (event = (event_T)0; (int)event < (int)NUM_EVENTS; for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
event = (event_T)((int)event + 1)) { event = (event_T)((int)event + 1)) {
/* loop over all autocommand patterns */ // Loop over all autocommand patterns.
prev_ap = &(first_autopat[(int)event]); prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
/* loop over all commands for this pattern */ // Loop over all commands for this pattern.
prev_ac = &(ap->cmds); prev_ac = &(ap->cmds);
bool has_cmd = false;
for (ac = *prev_ac; ac != NULL; ac = *prev_ac) { for (ac = *prev_ac; ac != NULL; ac = *prev_ac) {
/* remove the command if the pattern is to be deleted or when // Remove the command if the pattern is to be deleted or when
* the command has been marked for deletion */ // the command has been marked for deletion.
if (ap->pat == NULL || ac->cmd == NULL) { if (ap->pat == NULL || ac->cmd == NULL) {
*prev_ac = ac->next; *prev_ac = ac->next;
xfree(ac->cmd); xfree(ac->cmd);
xfree(ac); xfree(ac);
} else } else {
has_cmd = true;
prev_ac = &(ac->next); prev_ac = &(ac->next);
}
} }
/* remove the pattern if it has been marked for deletion */ if (ap->pat != NULL && !has_cmd) {
// Pattern was not marked for deletion, but all of its commands were.
// So mark the pattern for deletion.
au_remove_pat(ap);
}
// Remove the pattern if it has been marked for deletion.
if (ap->pat == NULL) { if (ap->pat == NULL) {
if (ap->next == NULL) { if (ap->next == NULL) {
if (prev_ap == &(first_autopat[(int)event])) { if (prev_ap == &(first_autopat[(int)event])) {
@@ -5637,12 +5646,13 @@ static void au_cleanup(void)
*prev_ap = ap->next; *prev_ap = ap->next;
vim_regfree(ap->reg_prog); vim_regfree(ap->reg_prog);
xfree(ap); xfree(ap);
} else } else {
prev_ap = &(ap->next); prev_ap = &(ap->next);
}
} }
} }
au_need_clean = FALSE; au_need_clean = false;
} }
/* /*
@@ -6050,18 +6060,18 @@ void do_autocmd(char_u *arg_in, int forceit)
cmd = skipwhite(cmd); cmd = skipwhite(cmd);
for (size_t i = 0; i < 2; i++) { for (size_t i = 0; i < 2; i++) {
if (*cmd != NUL) { if (*cmd != NUL) {
// Check for "-once" flag. // Check for "++once" flag.
if (!once && STRNCMP(cmd, "-once", 5) == 0 && ascii_iswhite(cmd[5])) { if (!once && STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) {
once = true; once = true;
cmd = skipwhite(cmd + 5); cmd = skipwhite(cmd + 6);
} }
// Check for "-nested" flag. // Check for "++nested" flag.
if (!nested if (!nested
&& ((STRNCMP(cmd, "-nested", 7) == 0 && ascii_iswhite(cmd[7])) && ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))
// Deprecated form (without "-"). // Deprecated form (without "++").
|| (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])))) { || (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])))) {
nested = true; nested = true;
cmd = skipwhite(cmd + ('-' == cmd[0] ? 7 : 6)); cmd = skipwhite(cmd + ('+' == cmd[0] ? 8 : 6));
} }
} }
} }

View File

@@ -363,8 +363,8 @@ static void set_bg_deferred(void **argv)
if (starting) { if (starting) {
// Wait until after startup, so OptionSet is triggered. // Wait until after startup, so OptionSet is triggered.
do_cmdline_cmd((bgvalue[0] == 'l') do_cmdline_cmd((bgvalue[0] == 'l')
? "autocmd VimEnter * once nested set background=light" ? "autocmd VimEnter * ++once ++nested set bg=light"
: "autocmd VimEnter * once nested set background=dark"); : "autocmd VimEnter * ++once ++nested set bg=dark");
} else { } else {
set_option_value("bg", 0L, bgvalue, 0); set_option_value("bg", 0L, bgvalue, 0);
reset_option_was_set("bg"); reset_option_was_set("bg");

View File

@@ -59,9 +59,9 @@ describe('autocmd', function()
end) end)
end) end)
it('-once', function() -- :help autocmd-once it('++once', function() -- :help autocmd-once
-- --
-- ":autocmd ... -once" executes its handler once, then removes the handler. -- ":autocmd ... ++once" executes its handler once, then removes the handler.
-- --
local expected = { local expected = {
'Many1', 'Many1',
@@ -76,10 +76,10 @@ describe('autocmd', function()
} }
command('let g:foo = []') command('let g:foo = []')
command('autocmd TabNew * :call add(g:foo, "Many1")') command('autocmd TabNew * :call add(g:foo, "Many1")')
command('autocmd TabNew * -once :call add(g:foo, "Once1")') command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
command('autocmd TabNew * -once :call add(g:foo, "Once2")') command('autocmd TabNew * ++once :call add(g:foo, "Once2")')
command('autocmd TabNew * :call add(g:foo, "Many2")') command('autocmd TabNew * :call add(g:foo, "Many2")')
command('autocmd TabNew * -once :call add(g:foo, "Once3")') command('autocmd TabNew * ++once :call add(g:foo, "Once3")')
eq(dedent([[ eq(dedent([[
--- Autocommands --- --- Autocommands ---
@@ -103,27 +103,45 @@ describe('autocmd', function()
funcs.execute('autocmd Tabnew')) funcs.execute('autocmd Tabnew'))
-- --
-- ":autocmd ... -once" handlers can be deleted. -- ":autocmd ... ++once" handlers can be deleted.
-- --
expected = {} expected = {}
command('let g:foo = []') command('let g:foo = []')
command('autocmd TabNew * -once :call add(g:foo, "Once1")') command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
command('autocmd! TabNew') command('autocmd! TabNew')
command('tabnew') command('tabnew')
eq(expected, eval('g:foo')) eq(expected, eval('g:foo'))
-- --
-- ":autocmd ... <buffer> -once -nested" -- ":autocmd ... <buffer> ++once ++nested"
-- --
expected = { expected = {
'OptionSet-Once', 'OptionSet-Once',
'CursorMoved-Once', 'CursorMoved-Once',
} }
command('let g:foo = []') command('let g:foo = []')
command('autocmd OptionSet binary -nested -once :call add(g:foo, "OptionSet-Once")') command('autocmd OptionSet binary ++nested ++once :call add(g:foo, "OptionSet-Once")')
command('autocmd CursorMoved <buffer> -once -nested setlocal binary|:call add(g:foo, "CursorMoved-Once")') command('autocmd CursorMoved <buffer> ++once ++nested setlocal binary|:call add(g:foo, "CursorMoved-Once")')
command("put ='foo bar baz'") command("put ='foo bar baz'")
feed('0llhlh') feed('0llhlh')
eq(expected, eval('g:foo')) eq(expected, eval('g:foo'))
--
-- :autocmd should not show empty section after ++once handlers expire.
--
expected = {
'Once1',
'Once2',
}
command('let g:foo = []')
command('autocmd! TabNew') -- Clear all TabNew handlers.
command('autocmd TabNew * ++once :call add(g:foo, "Once1")')
command('autocmd TabNew * ++once :call add(g:foo, "Once2")')
command('tabnew')
eq(expected, eval('g:foo'))
eq(dedent([[
--- Autocommands ---]]),
funcs.execute('autocmd Tabnew'))
end) end)
end) end)