diff src/autocmd.c @ 16217:81e6940504e8 v8.1.1113

patch 8.1.1113: making an autocommand trigger once is not so easy commit https://github.com/vim/vim/commit/eb93f3f0e2b2ae65c5c3f55be3e62d64e3066f35 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Apr 4 15:04:56 2019 +0200 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)
author Bram Moolenaar <Bram@vim.org>
date Thu, 04 Apr 2019 15:15:05 +0200
parents cd5c83115ec6
children 331dc836f866
line wrap: on
line diff
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -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;   /* n
 
 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, in
 	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;