Skip to content

Commit eb93f3f

Browse files
committed
patch 8.1.1113: making an autocommand trigger once is not so easy
Problem: Making an autocommand trigger once is not so easy. Solution: Add the ++once argument. Also add ++nested as an alias for "nested". (Justin M. Keyes, closes #4100)
1 parent 87f59b0 commit eb93f3f

File tree

5 files changed

+146
-18
lines changed

5 files changed

+146
-18
lines changed

runtime/doc/autocmd.txt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*autocmd.txt* For Vim version 8.1. Last change: 2019 Mar 13
1+
*autocmd.txt* For Vim version 8.1. Last change: 2019 Apr 04
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -52,15 +52,21 @@ effects. Be careful not to destroy your text.
5252
2. Defining autocommands *autocmd-define*
5353

5454
*:au* *:autocmd*
55-
:au[tocmd] [group] {event} {pat} [nested] {cmd}
55+
:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}
5656
Add {cmd} to the list of commands that Vim will
5757
execute automatically on {event} for a file matching
5858
{pat} |autocmd-patterns|.
5959
Note: A quote character is seen as argument to the
6060
:autocmd and won't start a comment.
6161
Vim always adds the {cmd} after existing autocommands,
6262
so that the autocommands execute in the order in which
63-
they were given. See |autocmd-nested| for [nested].
63+
they were given.
64+
See |autocmd-nested| for [++nested]. "nested"
65+
(without the ++) can also be used, for backwards
66+
compatibility.
67+
*autocmd-once*
68+
If [++once] is supplied the command is executed once,
69+
then removed ("one shot").
6470

6571
The special pattern <buffer> or <buffer=N> defines a buffer-local autocommand.
6672
See |autocmd-buflocal|.
@@ -128,10 +134,11 @@ prompt. When one command outputs two messages this can happen anyway.
128134
==============================================================================
129135
3. Removing autocommands *autocmd-remove*
130136

131-
:au[tocmd]! [group] {event} {pat} [nested] {cmd}
137+
:au[tocmd]! [group] {event} {pat} [++once] [++nested] {cmd}
132138
Remove all autocommands associated with {event} and
133-
{pat}, and add the command {cmd}. See
134-
|autocmd-nested| for [nested].
139+
{pat}, and add the command {cmd}.
140+
See |autocmd-once| for [++once].
141+
See |autocmd-nested| for [++nested].
135142

136143
:au[tocmd]! [group] {event} {pat}
137144
Remove all autocommands associated with {event} and
@@ -1473,7 +1480,7 @@ By default, autocommands do not nest. For example, if you use ":e" or ":w" in
14731480
an autocommand, Vim does not execute the BufRead and BufWrite autocommands for
14741481
those commands. If you do want this, use the "nested" flag for those commands
14751482
in which you want nesting. For example: >
1476-
:autocmd FileChangedShell *.c nested e!
1483+
:autocmd FileChangedShell *.c ++nested e!
14771484
The nesting is limited to 10 levels to get out of recursive loops.
14781485

14791486
It's possible to use the ":au" command in an autocommand. This can be a

src/autocmd.c

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ typedef struct AutoCmd
5252
{
5353
char_u *cmd; // The command to be executed (NULL
5454
// when command has been removed).
55+
char once; // "One shot": removed after execution
5556
char nested; // If autocommands nest here.
5657
char last; // last command in list
5758
#ifdef FEAT_EVAL
@@ -256,7 +257,7 @@ static int au_need_clean = FALSE; /* need to delete marked patterns */
256257

257258
static char_u *event_nr2name(event_T event);
258259
static int au_get_grouparg(char_u **argp);
259-
static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, int forceit, int group);
260+
static int do_autocmd_event(event_T event, char_u *pat, int once, int nested, char_u *cmd, int forceit, int group);
260261
static int apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, int force, int group, buf_T *buf, exarg_T *eap);
261262
static void auto_next_pat(AutoPatCmd *apc, int stop_at_last);
262263
static int au_find_group(char_u *name);
@@ -361,6 +362,13 @@ au_remove_cmds(AutoPat *ap)
361362
au_need_clean = TRUE;
362363
}
363364

365+
// Delete one command from an autocmd pattern.
366+
static void au_del_cmd(AutoCmd *ac)
367+
{
368+
VIM_CLEAR(ac->cmd);
369+
au_need_clean = TRUE;
370+
}
371+
364372
/*
365373
* Cleanup autocommands and patterns that have been deleted.
366374
* This is only done when not executing autocommands.
@@ -385,6 +393,8 @@ au_cleanup(void)
385393
{
386394
// loop over all commands for this pattern
387395
prev_ac = &(ap->cmds);
396+
int has_cmd = FALSE;
397+
388398
for (ac = *prev_ac; ac != NULL; ac = *prev_ac)
389399
{
390400
// remove the command if the pattern is to be deleted or when
@@ -395,8 +405,16 @@ au_cleanup(void)
395405
vim_free(ac->cmd);
396406
vim_free(ac);
397407
}
398-
else
408+
else {
409+
has_cmd = TRUE;
399410
prev_ac = &(ac->next);
411+
}
412+
}
413+
414+
if (ap->pat != NULL && !has_cmd) {
415+
// Pattern was not marked for deletion, but all of its
416+
// commands were. So mark the pattern for deletion.
417+
au_remove_pat(ap);
400418
}
401419

402420
// remove the pattern if it has been marked for deletion
@@ -815,7 +833,9 @@ do_autocmd(char_u *arg_in, int forceit)
815833
event_T event;
816834
int need_free = FALSE;
817835
int nested = FALSE;
836+
int once = FALSE;
818837
int group;
838+
int i;
819839

820840
if (*arg == '|')
821841
{
@@ -874,15 +894,38 @@ do_autocmd(char_u *arg_in, int forceit)
874894
pat = envpat;
875895
}
876896

877-
/*
878-
* Check for "nested" flag.
879-
*/
880897
cmd = skipwhite(cmd);
881-
if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0
882-
&& VIM_ISWHITE(cmd[6]))
898+
for (i = 0; i < 2; i++)
883899
{
884-
nested = TRUE;
885-
cmd = skipwhite(cmd + 6);
900+
if (*cmd != NUL)
901+
{
902+
// Check for "++once" flag.
903+
if (STRNCMP(cmd, "++once", 6) == 0 && VIM_ISWHITE(cmd[6]))
904+
{
905+
if (once)
906+
semsg(_(e_duparg2), "++once");
907+
once = TRUE;
908+
cmd = skipwhite(cmd + 6);
909+
}
910+
911+
// Check for "++nested" flag.
912+
if ((STRNCMP(cmd, "++nested", 8) == 0 && VIM_ISWHITE(cmd[8])))
913+
{
914+
if (nested)
915+
semsg(_(e_duparg2), "++nested");
916+
nested = TRUE;
917+
cmd = skipwhite(cmd + 8);
918+
}
919+
920+
// Check for the old "nested" flag.
921+
if (STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6]))
922+
{
923+
if (nested)
924+
semsg(_(e_duparg2), "nested");
925+
nested = TRUE;
926+
cmd = skipwhite(cmd + 6);
927+
}
928+
}
886929
}
887930

888931
/*
@@ -915,14 +958,14 @@ do_autocmd(char_u *arg_in, int forceit)
915958
for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
916959
event = (event_T)((int)event + 1))
917960
if (do_autocmd_event(event, pat,
918-
nested, cmd, forceit, group) == FAIL)
961+
once, nested, cmd, forceit, group) == FAIL)
919962
break;
920963
}
921964
else
922965
{
923966
while (*arg && *arg != '|' && !VIM_ISWHITE(*arg))
924967
if (do_autocmd_event(event_name2nr(arg, &arg), pat,
925-
nested, cmd, forceit, group) == FAIL)
968+
once, nested, cmd, forceit, group) == FAIL)
926969
break;
927970
}
928971

@@ -973,6 +1016,7 @@ au_get_grouparg(char_u **argp)
9731016
do_autocmd_event(
9741017
event_T event,
9751018
char_u *pat,
1019+
int once,
9761020
int nested,
9771021
char_u *cmd,
9781022
int forceit,
@@ -1212,6 +1256,7 @@ do_autocmd_event(
12121256
}
12131257
ac->next = NULL;
12141258
*prev_ac = ac;
1259+
ac->once = once;
12151260
ac->nested = nested;
12161261
}
12171262
}
@@ -2319,6 +2364,9 @@ getnextac(int c UNUSED, void *cookie, int indent UNUSED)
23192364
verbose_leave_scroll();
23202365
}
23212366
retval = vim_strsave(ac->cmd);
2367+
// Remove one-shot ("once") autocmd in anticipation of its execution.
2368+
if (ac->once)
2369+
au_del_cmd(ac);
23222370
autocmd_nested = ac->nested;
23232371
#ifdef FEAT_EVAL
23242372
current_sctx = ac->script_ctx;

src/globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,7 @@ EXTERN char e_interr[] INIT(= N_("Interrupted"));
14011401
EXTERN char e_invaddr[] INIT(= N_("E14: Invalid address"));
14021402
EXTERN char e_invarg[] INIT(= N_("E474: Invalid argument"));
14031403
EXTERN char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
1404+
EXTERN char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
14041405
EXTERN char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
14051406
EXTERN char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
14061407
#ifdef FEAT_EVAL

src/testdir/test_autocmd.vim

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,4 +1415,74 @@ func Test_Changed_FirstTime()
14151415
bwipe!
14161416
endfunc
14171417

1418+
func Test_autocmd_nested()
1419+
let g:did_nested = 0
1420+
augroup Testing
1421+
au WinNew * edit somefile
1422+
au BufNew * let g:did_nested = 1
1423+
augroup END
1424+
split
1425+
call assert_equal(0, g:did_nested)
1426+
close
1427+
bwipe! somefile
1428+
1429+
" old nested argument still works
1430+
augroup Testing
1431+
au!
1432+
au WinNew * nested edit somefile
1433+
au BufNew * let g:did_nested = 1
1434+
augroup END
1435+
split
1436+
call assert_equal(1, g:did_nested)
1437+
close
1438+
bwipe! somefile
1439+
1440+
" New ++nested argument works
1441+
augroup Testing
1442+
au!
1443+
au WinNew * ++nested edit somefile
1444+
au BufNew * let g:did_nested = 1
1445+
augroup END
1446+
split
1447+
call assert_equal(1, g:did_nested)
1448+
close
1449+
bwipe! somefile
1450+
1451+
augroup Testing
1452+
au!
1453+
augroup END
1454+
1455+
call assert_fails('au WinNew * ++nested ++nested echo bad', 'E983:')
1456+
call assert_fails('au WinNew * nested nested echo bad', 'E983:')
1457+
endfunc
1458+
1459+
func Test_autocmd_once()
1460+
" Without ++once WinNew triggers twice
1461+
let g:did_split = 0
1462+
augroup Testing
1463+
au WinNew * let g:did_split += 1
1464+
augroup END
1465+
split
1466+
split
1467+
call assert_equal(2, g:did_split)
1468+
call assert_true(exists('#WinNew'))
1469+
close
1470+
close
1471+
1472+
" With ++once WinNew triggers once
1473+
let g:did_split = 0
1474+
augroup Testing
1475+
au!
1476+
au WinNew * ++once let g:did_split += 1
1477+
augroup END
1478+
split
1479+
split
1480+
call assert_equal(1, g:did_split)
1481+
call assert_false(exists('#WinNew'))
1482+
close
1483+
close
1484+
1485+
call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
1486+
endfunc
1487+
14181488
" FileChangedShell tested in test_filechanged.vim

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,8 @@ static char *(features[]) =
771771

772772
static int included_patches[] =
773773
{ /* Add new patch number below this line */
774+
/**/
775+
1113,
774776
/**/
775777
1112,
776778
/**/

0 commit comments

Comments
 (0)