]> granicus.if.org Git - vim/commitdiff
patch 8.1.1113: making an autocommand trigger once is not so easy v8.1.1113
authorBram Moolenaar <Bram@vim.org>
Thu, 4 Apr 2019 13:04:56 +0000 (15:04 +0200)
committerBram Moolenaar <Bram@vim.org>
Thu, 4 Apr 2019 13:04:56 +0000 (15:04 +0200)
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)

runtime/doc/autocmd.txt
src/autocmd.c
src/globals.h
src/testdir/test_autocmd.vim
src/version.c

index 6480d72da0ff7bfa290ee8dc8f7ff635034ee427..5eb1936c48005b6feaf35d7f06a9102008a3ee1b 100644 (file)
@@ -1,4 +1,4 @@
-*autocmd.txt*   For Vim version 8.1.  Last change: 2019 Mar 13
+*autocmd.txt*   For Vim version 8.1.  Last change: 2019 Apr 04
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -52,7 +52,7 @@ effects.  Be careful not to destroy your text.
 2. Defining autocommands                               *autocmd-define*
 
                                                        *:au* *:autocmd*
-:au[tocmd] [group] {event} {pat} [nested] {cmd}
+:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd}
                        Add {cmd} to the list of commands that Vim will
                        execute automatically on {event} for a file matching
                        {pat} |autocmd-patterns|.
@@ -60,7 +60,13 @@ effects.  Be careful not to destroy your text.
                        :autocmd and won't start a comment.
                        Vim always adds the {cmd} after existing autocommands,
                        so that the autocommands execute in the order in which
-                       they were given.  See |autocmd-nested| for [nested].
+                       they were given.
+                       See |autocmd-nested| for [++nested].  "nested"
+                       (without the ++) can also be used, for backwards
+                       compatibility.
+                                                       *autocmd-once*
+                       If [++once] is supplied the command is executed once,
+                       then removed ("one shot").
 
 The special pattern <buffer> or <buffer=N> defines a buffer-local autocommand.
 See |autocmd-buflocal|.
@@ -128,10 +134,11 @@ prompt.  When one command outputs two messages this can happen anyway.
 ==============================================================================
 3. Removing autocommands                               *autocmd-remove*
 
-:au[tocmd]! [group] {event} {pat} [nested] {cmd}
+:au[tocmd]! [group] {event} {pat} [++once] [++nested] {cmd}
                        Remove all autocommands associated with {event} and
-                       {pat}, and add the command {cmd}.  See
-                       |autocmd-nested| for [nested].
+                       {pat}, and add the command {cmd}.
+                       See |autocmd-once| for [++once].
+                       See |autocmd-nested| for [++nested].
 
 :au[tocmd]! [group] {event} {pat}
                        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
 an autocommand, Vim does not execute the BufRead and BufWrite autocommands for
 those commands.  If you do want this, use the "nested" flag for those 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.
 
 It's possible to use the ":au" command in an autocommand.  This can be a
index d81615ba8fdca354a28e372c062663970a61d4af..9a758c8a4545b9d7a3d6f1cc73ab9f0f358c8539 100644 (file)
@@ -52,6 +52,7 @@ typedef struct AutoCmd
 {
     char_u         *cmd;               // The command to be executed (NULL
                                        // when command has been removed).
+    char           once;               // "One shot": removed after execution
     char           nested;             // If autocommands nest here.
     char           last;               // last command in list
 #ifdef FEAT_EVAL
@@ -256,7 +257,7 @@ static int au_need_clean = FALSE;   /* need to delete marked patterns */
 
 static char_u *event_nr2name(event_T event);
 static int au_get_grouparg(char_u **argp);
-static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, int forceit, int group);
+static int do_autocmd_event(event_T event, char_u *pat, int once, int nested, char_u *cmd, int forceit, int group);
 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);
 static void auto_next_pat(AutoPatCmd *apc, int stop_at_last);
 static int au_find_group(char_u *name);
@@ -361,6 +362,13 @@ au_remove_cmds(AutoPat *ap)
     au_need_clean = TRUE;
 }
 
+// Delete one command from an autocmd pattern.
+static void au_del_cmd(AutoCmd *ac)
+{
+    VIM_CLEAR(ac->cmd);
+    au_need_clean = TRUE;
+}
+
 /*
  * Cleanup autocommands and patterns that have been deleted.
  * This is only done when not executing autocommands.
@@ -385,6 +393,8 @@ au_cleanup(void)
        {
            // loop over all commands for this pattern
            prev_ac = &(ap->cmds);
+           int has_cmd = FALSE;
+
            for (ac = *prev_ac; ac != NULL; ac = *prev_ac)
            {
                // remove the command if the pattern is to be deleted or when
@@ -395,8 +405,16 @@ au_cleanup(void)
                    vim_free(ac->cmd);
                    vim_free(ac);
                }
-               else
+               else {
+                   has_cmd = TRUE;
                    prev_ac = &(ac->next);
+               }
+           }
+
+           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
@@ -815,7 +833,9 @@ do_autocmd(char_u *arg_in, int forceit)
     event_T    event;
     int                need_free = FALSE;
     int                nested = FALSE;
+    int                once = FALSE;
     int                group;
+    int                i;
 
     if (*arg == '|')
     {
@@ -874,15 +894,38 @@ do_autocmd(char_u *arg_in, int forceit)
                pat = envpat;
        }
 
-       /*
-        * Check for "nested" flag.
-        */
        cmd = skipwhite(cmd);
-       if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0
-                                                       && VIM_ISWHITE(cmd[6]))
+       for (i = 0; i < 2; i++)
        {
-           nested = TRUE;
-           cmd = skipwhite(cmd + 6);
+           if (*cmd != NUL)
+           {
+               // Check for "++once" flag.
+               if (STRNCMP(cmd, "++once", 6) == 0 && VIM_ISWHITE(cmd[6]))
+               {
+                   if (once)
+                       semsg(_(e_duparg2), "++once");
+                   once = TRUE;
+                   cmd = skipwhite(cmd + 6);
+               }
+
+               // Check for "++nested" flag.
+               if ((STRNCMP(cmd, "++nested", 8) == 0 && VIM_ISWHITE(cmd[8])))
+               {
+                   if (nested)
+                       semsg(_(e_duparg2), "++nested");
+                   nested = TRUE;
+                   cmd = skipwhite(cmd + 8);
+               }
+
+               // Check for the old "nested" flag.
+               if (STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6]))
+               {
+                   if (nested)
+                       semsg(_(e_duparg2), "nested");
+                   nested = TRUE;
+                   cmd = skipwhite(cmd + 6);
+               }
+           }
        }
 
        /*
@@ -915,14 +958,14 @@ do_autocmd(char_u *arg_in, int forceit)
        for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
                                            event = (event_T)((int)event + 1))
            if (do_autocmd_event(event, pat,
-                                        nested, cmd, forceit, group) == FAIL)
+                                once, nested, cmd, forceit, group) == FAIL)
                break;
     }
     else
     {
        while (*arg && *arg != '|' && !VIM_ISWHITE(*arg))
            if (do_autocmd_event(event_name2nr(arg, &arg), pat,
-                                       nested, cmd, forceit, group) == FAIL)
+                                once, nested,  cmd, forceit, group) == FAIL)
                break;
     }
 
@@ -973,6 +1016,7 @@ au_get_grouparg(char_u **argp)
 do_autocmd_event(
     event_T    event,
     char_u     *pat,
+    int                once,
     int                nested,
     char_u     *cmd,
     int                forceit,
@@ -1212,6 +1256,7 @@ do_autocmd_event(
            }
            ac->next = NULL;
            *prev_ac = ac;
+           ac->once = once;
            ac->nested = nested;
        }
     }
@@ -2319,6 +2364,9 @@ getnextac(int c UNUSED, void *cookie, int indent UNUSED)
        verbose_leave_scroll();
     }
     retval = vim_strsave(ac->cmd);
+    // Remove one-shot ("once") autocmd in anticipation of its execution.
+    if (ac->once)
+       au_del_cmd(ac);
     autocmd_nested = ac->nested;
 #ifdef FEAT_EVAL
     current_sctx = ac->script_ctx;
index e7ea84e70f482a5feed68b0ca0a4ab086214baba..adf98f229bec9d6d269aceeaa629d0cb981a6815 100644 (file)
@@ -1401,6 +1401,7 @@ EXTERN char e_interr[]    INIT(= N_("Interrupted"));
 EXTERN char e_invaddr[]        INIT(= N_("E14: Invalid address"));
 EXTERN char e_invarg[] INIT(= N_("E474: Invalid argument"));
 EXTERN char e_invarg2[]        INIT(= N_("E475: Invalid argument: %s"));
+EXTERN char e_duparg2[]        INIT(= N_("E983: Duplicate argument: %s"));
 EXTERN char e_invargval[]      INIT(= N_("E475: Invalid value for argument %s"));
 EXTERN char e_invargNval[]     INIT(= N_("E475: Invalid value for argument %s: %s"));
 #ifdef FEAT_EVAL
index 71df515294e3ef2d0d3fd96259f8f9f5e8097325..b89af1849f2742a265eadefb5e05b6d68048ba78 100644 (file)
@@ -1415,4 +1415,74 @@ func Test_Changed_FirstTime()
   bwipe!
 endfunc
 
+func Test_autocmd_nested()
+  let g:did_nested = 0
+  augroup Testing
+    au WinNew * edit somefile
+    au BufNew * let g:did_nested = 1
+  augroup END
+  split
+  call assert_equal(0, g:did_nested)
+  close
+  bwipe! somefile
+
+  " old nested argument still works
+  augroup Testing
+    au!
+    au WinNew * nested edit somefile
+    au BufNew * let g:did_nested = 1
+  augroup END
+  split
+  call assert_equal(1, g:did_nested)
+  close
+  bwipe! somefile
+
+  " New ++nested argument works
+  augroup Testing
+    au!
+    au WinNew * ++nested edit somefile
+    au BufNew * let g:did_nested = 1
+  augroup END
+  split
+  call assert_equal(1, g:did_nested)
+  close
+  bwipe! somefile
+
+  augroup Testing
+    au!
+  augroup END
+
+  call assert_fails('au WinNew * ++nested ++nested echo bad', 'E983:')
+  call assert_fails('au WinNew * nested nested echo bad', 'E983:')
+endfunc
+
+func Test_autocmd_once()
+  " Without ++once WinNew triggers twice
+  let g:did_split = 0
+  augroup Testing
+    au WinNew * let g:did_split += 1
+  augroup END
+  split
+  split
+  call assert_equal(2, g:did_split)
+  call assert_true(exists('#WinNew'))
+  close
+  close
+
+  " With ++once WinNew triggers once
+  let g:did_split = 0
+  augroup Testing
+    au!
+    au WinNew * ++once let g:did_split += 1
+  augroup END
+  split
+  split
+  call assert_equal(1, g:did_split)
+  call assert_false(exists('#WinNew'))
+  close
+  close
+
+  call assert_fails('au WinNew * ++once ++once echo bad', 'E983:')
+endfunc
+
 " FileChangedShell tested in test_filechanged.vim
index 22154094712e545386c85a1e6b2f3f32ac42075b..38059505e347d4b29d03ef1f5d6c12ab465f1e8b 100644 (file)
@@ -771,6 +771,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1113,
 /**/
     1112,
 /**/