changeset 28917:c5862dfaf0bd v8.2.4981

patch 8.2.4981: it is not possible to manipulate autocommands Commit: https://github.com/vim/vim/commit/1755a91851f7022fdd3eecfbd2cc0b508a2f2a8f Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Thu May 19 10:31:47 2022 +0100 patch 8.2.4981: it is not possible to manipulate autocommands Problem: It is not possible to manipulate autocommands. Solution: Add functions to add, get and set autocommands. (Yegappan Lakshmanan, closes #10291)
author Bram Moolenaar <Bram@vim.org>
date Thu, 19 May 2022 11:45:05 +0200
parents 40a5b3396eb4
children ec2988a3b03c
files runtime/doc/autocmd.txt runtime/doc/builtin.txt runtime/doc/usr_41.txt src/autocmd.c src/evalfunc.c src/proto/autocmd.pro src/testdir/test_autocmd.vim src/testdir/test_vim9_builtin.vim src/version.c
diffstat 9 files changed, 801 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -82,6 +82,9 @@ triggered.
 		  /<start
 		}
 
+The |autocmd_add()| function can be used to add a list of autocmds and autocmd
+groups from a Vim script.
+
 Note: The ":autocmd" command can only be followed by another command when the
 '|' appears where the pattern is expected.  This works: >
 	:augroup mine | au! BufRead | augroup END
@@ -146,6 +149,9 @@ prompt.  When one command outputs two me
 ==============================================================================
 3. Removing autocommands				*autocmd-remove*
 
+In addition to the below described commands, the |autocmd_delete()| function can
+be used to remove a list of autocmds and autocmd groups from a Vim script.
+
 :au[tocmd]! [group] {event} {aupat} [++once] [++nested] {cmd}
 			Remove all autocommands associated with {event} and
 			{aupat}, and add the command {cmd}.
@@ -198,6 +204,9 @@ argument behavior differs from that for 
 In order to list buffer-local autocommands, use a pattern in the form <buffer>
 or <buffer=N>.  See |autocmd-buflocal|.
 
+The |autocmd_get()| function can be used from a Vim script to get a list of
+autocmds.
+
 							*:autocmd-verbose*
 When 'verbose' is non-zero, listing an autocommand will also display where it
 was last defined. Example: >
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -60,6 +60,9 @@ assert_report({msg})		Number	report a te
 assert_true({actual} [, {msg}])	Number	assert {actual} is true
 atan({expr})			Float	arc tangent of {expr}
 atan2({expr1}, {expr2})		Float	arc tangent of {expr1} / {expr2}
+autocmd_add({acmds})		Bool	add a list of autocmds and groups
+autocmd_delete({acmds})		Bool	delete a list of autocmds and groups
+autocmd_get([{opts}])		List	return a list of autocmds
 balloon_gettext()		String	current text in the balloon
 balloon_show({expr})		none	show {expr} inside the balloon
 balloon_split({msg})		List	split {msg} as used for a balloon
@@ -922,6 +925,145 @@ atan2({expr1}, {expr2})					*atan2()*
 <
 		{only available when compiled with the |+float| feature}
 
+
+autocmd_add({acmds})					*autocmd_add()*
+		Adds a List of autocmds and autocmd groups.
+
+		The {acmds} argument is a List where each item is a Dict with
+		the following optional items:
+		    bufnr	buffer number to add a buffer-local autocmd.
+				If this item is specified, then the "pattern"
+				item is ignored.
+		    cmd		Ex command to execute for this autocmd event
+		    event	autocmd event name. Refer to |autocmd-events|.
+		    group	autocmd group name. Refer to |autocmd-groups|.
+				If this group doesn't exist then it is
+				created.  If not specified or empty, then the
+				default group is used.
+		    nested	set to v:true to add a nested autocmd.
+				Refer to |autocmd-nested|.
+		    once	set to v:true to add a autocmd which executes
+				only once. Refer to |autocmd-once|.
+		    pattern	autocmd pattern string. Refer to
+				|autocmd-patterns|.  If "bufnr" item is
+				present, then this item is ignored.
+
+		Returns v:true on success and v:false on failure.
+		Examples: >
+			" Create a buffer-local autocmd for buffer 5
+			let acmd = {}
+			let acmd.group = 'MyGroup'
+			let acmd.event = 'BufEnter'
+			let acmd.bufnr = 5
+			let acmd.cmd = 'call BufEnterFunc()'
+			call autocmd_add([acmd])
+
+		Can also be used as a |method|: >
+			GetAutocmdList()->autocmd_add()
+<
+autocmd_delete({acmds})					*autocmd_delete()*
+		Deletes a List of autocmds and autocmd groups.
+
+		The {acmds} argument is a List where each item is a Dict with
+		the following optional items:
+		    bufnr	buffer number to delete a buffer-local autocmd.
+				If this item is specified, then the "pattern"
+				item is ignored.
+		    cmd		Ex command for this autocmd event
+		    event	autocmd event name. Refer to |autocmd-events|.
+				If '*' then all the autocmd events in this
+				group are deleted.
+		    group	autocmd group name. Refer to |autocmd-groups|.
+				If not specified or empty, then the default
+				group is used.
+		    nested	set to v:true for a nested autocmd.
+				Refer to |autocmd-nested|.
+		    once	set to v:true for an autocmd which executes
+				only once. Refer to |autocmd-once|.
+		    pattern	autocmd pattern string. Refer to
+				|autocmd-patterns|.  If "bufnr" item is
+				present, then this item is ignored.
+
+		If only {group} is specified in a {acmds} entry and {event},
+		{pattern} and {cmd} are not specified, then that autocmd group
+		is deleted.
+
+		Returns v:true on success and v:false on failure.
+		Examples: >
+			" :autocmd! BufLeave *.vim
+			let acmd = #{event: 'BufLeave', pattern: '*.vim'}
+			call autocmd_delete([acmd]})
+			" :autocmd! MyGroup1 BufLeave
+			let acmd = #{group: 'MyGroup1', event: 'BufLeave'}
+			call autocmd_delete([acmd])
+			" :autocmd! MyGroup2 BufEnter *.c
+			let acmd = #{group: 'MyGroup2', event: 'BufEnter',
+							\ pattern: '*.c'}
+			" :autocmd! MyGroup2 * *.c
+			let acmd = #{group: 'MyGroup2', event: '*',
+							\ pattern: '*.c'}
+			call autocmd_delete([acmd])
+			" :autocmd! MyGroup3
+			let acmd = #{group: 'MyGroup3'}
+			call autocmd_delete([acmd])
+<
+		Can also be used as a |method|: >
+			GetAutocmdList()->autocmd_delete()
+
+autocmd_get([{opts}])					*autocmd_get()*
+		Returns a |List| of autocmds. If {opts} is not supplied, then
+		returns the autocmds for all the events in all the groups.
+
+		The optional {opts} Dict argument supports the following
+		items:
+		    group	Autocmd group name. If specified, returns only
+				the autocmds defined in this group. If the
+				specified group doesn't exist, results in an
+				error message.  If set to an empty string,
+				then the default autocmd group is used.
+		    event	Autocmd event name. If specified, returns only
+				the autocmds defined for this event.  If set
+				to "*", then returns autocmds for all the
+				events.  If the specified event doesn't exist,
+				results in an error message.
+		    pattern	Autocmd pattern. If specified, returns only
+				the autocmds defined for this pattern.
+		A combination of the above three times can be supplied in
+		{opts}.
+
+		Each Dict in the returned List contains the following items:
+		    bufnr	For buffer-local autocmds, buffer number where
+				the autocmd is defined.
+		    cmd		Command executed for this autocmd.
+		    event	Autocmd event name.
+		    group	Autocmd group name.
+		    nested	Set to v:true for a nested autocmd. See
+				|autocmd-nested|.
+		    once	Set to v:true, if the autocmd will be executed
+				only once. See |autocmd-once|.
+		    pattern	Autocmd pattern.  For a buffer-local
+				autocmd, this will be of the form "<buffer=n>".
+		If there are multiple commands for an autocmd event in a
+		group, then separate items are returned for each command.
+
+		Examples: >
+			" :autocmd MyGroup
+			echo autocmd_get(#{group: 'Mygroup'})
+			" :autocmd G BufUnload
+			echo autocmd_get(#{group: 'G', event: 'BufUnload'})
+			" :autocmd G * *.ts
+			let acmd = #{group: 'G', event: '*', pattern: '*.ts'}
+			echo autocmd_get(acmd)
+			" :autocmd Syntax
+			echo autocmd_get(#{event: 'Syntax'})
+			" :autocmd G BufEnter *.ts
+			let acmd = #{group: 'G', event: 'BufEnter',
+							\ pattern: '*.ts'}
+			echo autocmd_get(acmd)
+<
+		Can also be used as a |method|: >
+			Getopts()->autocmd_get()
+<
 balloon_gettext()					*balloon_gettext()*
 		Return the current text in the balloon.  Only for the string,
 		not used for the List.
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -925,6 +925,11 @@ Date and Time:				*date-functions* *time
 	reltimestr()		convert reltime() result to a string
 	reltimefloat()		convert reltime() result to a Float
 
+Autocmds:					*autocmd-functions*
+	autocmd_add()		add a list of autocmds and groups
+	autocmd_delete()	delete a list of autocmds and groups
+	autocmd_get()		return a list of autocmds
+
 			*buffer-functions* *window-functions* *arg-functions*
 Buffers, windows and the argument list:
 	argc()			number of entries in the argument list
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -2562,7 +2562,7 @@ get_augroup_name(expand_T *xp UNUSED, in
 {
     if (idx == augroups.ga_len)		// add "END" add the end
 	return (char_u *)"END";
-    if (idx >= augroups.ga_len)		// end of list
+    if (idx < 0 || idx >= augroups.ga_len)	// end of list
 	return NULL;
     if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup())
 	// skip deleted entries
@@ -2747,4 +2747,355 @@ theend:
     vim_free(arg_save);
     return retval;
 }
+
+/*
+ * autocmd_add() and autocmd_delete() functions
+ */
+    static void
+autocmd_add_or_delete(typval_T *argvars, typval_T *rettv, int delete)
+{
+    list_T	*event_list;
+    listitem_T	*li;
+    dict_T	*event_dict;
+    char_u	*event_name = NULL;
+    event_T	event;
+    char_u	*group_name = NULL;
+    int		group;
+    char_u	*pat = NULL;
+    char_u	*cmd = NULL;
+    char_u	*end;
+    int		once;
+    int		nested;
+    int		retval = VVAL_TRUE;
+    int		save_augroup = current_augroup;
+
+    rettv->v_type = VAR_BOOL;
+    rettv->vval.v_number = VVAL_FALSE;
+
+    if (check_for_list_arg(argvars, 0) == FAIL)
+	return;
+
+    event_list = argvars[0].vval.v_list;
+    if (event_list == NULL)
+	return;
+
+    FOR_ALL_LIST_ITEMS(event_list, li)
+    {
+	VIM_CLEAR(event_name);
+	VIM_CLEAR(group_name);
+	VIM_CLEAR(pat);
+	VIM_CLEAR(cmd);
+
+	if (li->li_tv.v_type != VAR_DICT)
+	    continue;
+
+	event_dict = li->li_tv.vval.v_dict;
+	if (event_dict == NULL)
+	    continue;
+
+	event_name = dict_get_string(event_dict, (char_u *)"event", TRUE);
+	if (event_name == NULL)
+	{
+	    if (delete)
+		// if the event name is not specified, delete all the events
+		event = NUM_EVENTS;
+	    else
+		continue;
+	}
+	else
+	{
+	    if (delete && event_name[0] == '*' && event_name[1] == NUL)
+		// if the event name is '*', delete all the events
+		event = NUM_EVENTS;
+	    else
+	    {
+		event = event_name2nr(event_name, &end);
+		if (event == NUM_EVENTS)
+		{
+		    semsg(_(e_no_such_event_str), event_name);
+		    retval = VVAL_FALSE;
+		    break;
+		}
+	    }
+	}
+
+	group_name = dict_get_string(event_dict, (char_u *)"group", TRUE);
+	if (group_name == NULL || *group_name == NUL)
+	    // if the autocmd group name is not specified, then use the current
+	    // autocmd group
+	    group = current_augroup;
+	else
+	{
+	    group = au_find_group(group_name);
+	    if (group == AUGROUP_ERROR)
+	    {
+		if (delete)
+		{
+		    semsg(_(e_no_such_group_str), group_name);
+		    retval = VVAL_FALSE;
+		    break;
+		}
+		// group is not found, create it now
+		group = au_new_group(group_name);
+		if (group == AUGROUP_ERROR)
+		{
+		    semsg(_(e_no_such_group_str), group_name);
+		    retval = VVAL_FALSE;
+		    break;
+		}
+
+		current_augroup = group;
+	    }
+	}
+
+	// if a buffer number is specified, then generate a pattern of the form
+	// "<buffer=n>. Otherwise, use the pattern supplied by the user.
+	if (dict_has_key(event_dict, "bufnr"))
+	{
+	    varnumber_T	bnum;
+
+	    bnum = dict_get_number_def(event_dict, (char_u *)"bufnr", -1);
+	    if (bnum == -1)
+		continue;
+
+	    pat = alloc(128 + 1);
+	    if (pat == NULL)
+		continue;
+	    vim_snprintf((char *)pat, 128, "<buffer=%d>", (int)bnum);
+	}
+	else
+	{
+	    pat = dict_get_string(event_dict, (char_u *)"pattern", TRUE);
+	    if (pat == NULL)
+	    {
+		if (delete)
+		    pat = vim_strsave((char_u *)"");
+		else
+		    continue;
+	    }
+	}
+
+	once = dict_get_bool(event_dict, (char_u *)"once", FALSE);
+	nested = dict_get_bool(event_dict, (char_u *)"nested", FALSE);
+
+	cmd = dict_get_string(event_dict, (char_u *)"cmd", TRUE);
+	if (cmd == NULL)
+	{
+	    if (delete)
+		cmd = vim_strsave((char_u *)"");
+	    else
+		continue;
+	}
+
+	if (event == NUM_EVENTS)
+	{
+	    // event is '*', apply for all the events
+	    for (event = (event_T)0; (int)event < NUM_EVENTS;
+		    event = (event_T)((int)event + 1))
+	    {
+		if (do_autocmd_event(event, pat, once, nested, cmd, delete,
+							group, 0) == FAIL)
+		{
+		    retval = VVAL_FALSE;
+		    break;
+		}
+	    }
+	}
+	else
+	{
+	    if (do_autocmd_event(event, pat, once, nested, cmd, delete, group,
+								    0) == FAIL)
+	    {
+		retval = VVAL_FALSE;
+		break;
+	    }
+	}
+
+	// if only the autocmd group name is specified for delete and the
+	// autocmd event, pattern and cmd are not specified, then delete the
+	// autocmd group.
+	if (delete && group_name != NULL &&
+		(event_name == NULL || event_name[0] == NUL)
+		&& (pat == NULL || pat[0] == NUL)
+		&& (cmd == NULL || cmd[0] == NUL))
+	    au_del_group(group_name);
+    }
+
+    VIM_CLEAR(event_name);
+    VIM_CLEAR(group_name);
+    VIM_CLEAR(pat);
+    VIM_CLEAR(cmd);
+
+    current_augroup = save_augroup;
+    rettv->vval.v_number = retval;
+}
+
+/*
+ * autocmd_add() function
+ */
+    void
+f_autocmd_add(typval_T *argvars, typval_T *rettv)
+{
+    autocmd_add_or_delete(argvars, rettv, FALSE);
+}
+
+/*
+ * autocmd_delete() function
+ */
+    void
+f_autocmd_delete(typval_T *argvars, typval_T *rettv)
+{
+    autocmd_add_or_delete(argvars, rettv, TRUE);
+}
+
+/*
+ * autocmd_get() function
+ * Returns a List of autocmds.
+ */
+    void
+f_autocmd_get(typval_T *argvars, typval_T *rettv)
+{
+    event_T	event_arg = NUM_EVENTS;
+    event_T	event;
+    AutoPat	*ap;
+    AutoCmd	*ac;
+    list_T	*event_list;
+    dict_T	*event_dict;
+    char_u	*event_name = NULL;
+    char_u	*pat = NULL;
+    char_u	*name = NULL;
+    int		group = AUGROUP_ALL;
+
+    if (check_for_opt_dict_arg(argvars, 0) == FAIL)
+	return;
+
+    if (argvars[0].v_type == VAR_DICT)
+    {
+	// return only the autocmds in the specified group
+	if (dict_has_key(argvars[0].vval.v_dict, "group"))
+	{
+	    name = dict_get_string(argvars[0].vval.v_dict,
+						      (char_u *)"group", TRUE);
+	    if (name == NULL)
+		return;
+
+	    if (*name == NUL)
+		group = AUGROUP_DEFAULT;
+	    else
+	    {
+		group = au_find_group(name);
+		if (group == AUGROUP_ERROR)
+		{
+		    semsg(_(e_no_such_group_str), name);
+		    vim_free(name);
+		    return;
+		}
+	    }
+	    vim_free(name);
+	}
+
+	// return only the autocmds for the specified event
+	if (dict_has_key(argvars[0].vval.v_dict, "event"))
+	{
+	    int		i;
+
+	    name = dict_get_string(argvars[0].vval.v_dict,
+						      (char_u *)"event", TRUE);
+	    if (name == NULL)
+		return;
+
+	    if (name[0] == '*' && name[1] == NUL)
+		event_arg = NUM_EVENTS;
+	    else
+	    {
+		for (i = 0; event_names[i].name != NULL; i++)
+		    if (STRICMP(event_names[i].name, name) == 0)
+			break;
+		if (event_names[i].name == NULL)
+		{
+		    semsg(_(e_no_such_event_str), name);
+		    vim_free(name);
+		    return;
+		}
+		event_arg = event_names[i].event;
+	    }
+	    vim_free(name);
+	}
+
+	// return only the autocmds for the specified pattern
+	if (dict_has_key(argvars[0].vval.v_dict, "pattern"))
+	{
+	    pat = dict_get_string(argvars[0].vval.v_dict,
+						    (char_u *)"pattern", TRUE);
+	    if (pat == NULL)
+		return;
+	}
+    }
+
+    if (rettv_list_alloc(rettv) == FAIL)
+	return;
+    event_list = rettv->vval.v_list;
+
+    // iterate through all the autocmd events
+    for (event = (event_T)0; (int)event < NUM_EVENTS;
+	    event = (event_T)((int)event + 1))
+    {
+	if (event_arg != NUM_EVENTS && event != event_arg)
+	    continue;
+
+	event_name = event_nr2name(event);
+
+	// iterate through all the patterns for this autocmd event
+	FOR_ALL_AUTOCMD_PATTERNS(event, ap)
+	{
+	    char_u	*group_name;
+
+	    if (group != AUGROUP_ALL && group != ap->group)
+		continue;
+
+	    if (pat != NULL && STRCMP(pat, ap->pat) != 0)
+		continue;
+
+	    group_name = get_augroup_name(NULL, ap->group);
+
+	    // iterate through all the commands for this pattern and add one
+	    // item for each cmd.
+	    for (ac = ap->cmds; ac != NULL; ac = ac->next)
+	    {
+		event_dict = dict_alloc();
+		if (event_dict == NULL)
+		    return;
+
+		if (list_append_dict(event_list, event_dict) == FAIL)
+		    return;
+
+		if (dict_add_string(event_dict, "event", event_name) == FAIL)
+		    return;
+
+		if (dict_add_string(event_dict, "group", group_name == NULL
+			    ? (char_u *)"" : group_name) == FAIL)
+		    return;
+
+		if (ap->buflocal_nr != 0)
+		    if (dict_add_number(event_dict, "bufnr", ap->buflocal_nr)
+								       == FAIL)
+			return;
+
+		if (dict_add_string(event_dict, "pattern", ap->pat) == FAIL)
+		    return;
+
+		if (dict_add_string(event_dict, "cmd", ac->cmd) == FAIL)
+		    return;
+
+		if (dict_add_bool(event_dict, "once", ac->once) == FAIL)
+		    return;
+		if (dict_add_bool(event_dict, "nested", ac->nested) == FAIL)
+		    return;
+	    }
+	}
+    }
+
+    vim_free(pat);
+}
+
 #endif
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -1587,6 +1587,12 @@ static funcentry_T global_functions[] =
 			ret_float,	    FLOAT_FUNC(f_atan)},
     {"atan2",		2, 2, FEARG_1,	    arg2_float_or_nr,
 			ret_float,	    FLOAT_FUNC(f_atan2)},
+    {"autocmd_add",	1, 1, FEARG_1,	    arg1_list_any,
+			ret_number_bool,    f_autocmd_add},
+    {"autocmd_delete",	1, 1, FEARG_1,	    arg1_list_any,
+			ret_number_bool,    f_autocmd_delete},
+    {"autocmd_get",	0, 1, FEARG_1,	    arg1_dict_any,
+			ret_list_dict_any,  f_autocmd_get},
     {"balloon_gettext",	0, 0, 0,	    NULL,
 			ret_string,
 #ifdef FEAT_BEVAL
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -38,4 +38,7 @@ char_u *set_context_in_autocmd(expand_T 
 char_u *get_event_name(expand_T *xp, int idx);
 int autocmd_supported(char_u *name);
 int au_exists(char_u *arg);
+void f_autocmd_add(typval_T *argvars, typval_T *rettv);
+void f_autocmd_delete(typval_T *argvars, typval_T *rettv);
+void f_autocmd_get(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -3210,4 +3210,274 @@ func Test_noname_autocmd()
   augroup! test_noname_autocmd_group
 endfunc
 
+" Test for the autocmd_get() function
+func Test_autocmd_get()
+  augroup TestAutoCmdFns
+    au!
+    autocmd BufAdd *.vim echo "bufadd-vim"
+    autocmd BufAdd *.py echo "bufadd-py"
+    autocmd BufHidden *.vim echo "bufhidden"
+  augroup END
+  augroup TestAutoCmdFns2
+    autocmd BufAdd *.vim echo "bufadd-vim-2"
+    autocmd BufRead *.a1b2c3 echo "bufadd-vim-2"
+  augroup END
+
+  let l = autocmd_get()
+  call assert_true(l->len() > 0)
+
+  " Test for getting all the autocmds in a group
+  let expected = [
+        \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false, once: v:false,
+        \  event: 'BufAdd'},
+        \ #{cmd: 'echo "bufadd-py"', group: 'TestAutoCmdFns',
+        \  pattern: '*.py', nested: v:false, once: v:false,
+        \  event: 'BufAdd'},
+        \ #{cmd: 'echo "bufhidden"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false,
+        \  once: v:false, event: 'BufHidden'}]
+  call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+  " Test for getting autocmds for all the patterns in a group
+  call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns',
+        \ event: '*'}))
+
+  " Test for getting autocmds for an event in a group
+  let expected = [
+        \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false, once: v:false,
+        \  event: 'BufAdd'},
+        \ #{cmd: 'echo "bufadd-py"', group: 'TestAutoCmdFns',
+        \  pattern: '*.py', nested: v:false, once: v:false,
+        \  event: 'BufAdd'}]
+  call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns',
+        \ event: 'BufAdd'}))
+
+  " Test for getting the autocmds for all the events in a group for particular
+  " pattern
+  call assert_equal([{'cmd': 'echo "bufadd-py"', 'group': 'TestAutoCmdFns',
+        \ 'pattern': '*.py', 'nested': v:false, 'once': v:false,
+        \ 'event': 'BufAdd'}],
+        \ autocmd_get(#{group: 'TestAutoCmdFns', event: '*', pattern: '*.py'}))
+
+  " Test for getting the autocmds for an events in a group for particular
+  " pattern
+  let l = autocmd_get(#{group: 'TestAutoCmdFns', event: 'BufAdd',
+        \ pattern: '*.vim'})
+  call assert_equal([
+        \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false, once: v:false,
+        \  event: 'BufAdd'}], l)
+
+  " Test for getting the autocmds for a pattern in a group
+  let l = autocmd_get(#{group: 'TestAutoCmdFns', pattern: '*.vim'})
+  call assert_equal([
+        \ #{cmd: 'echo "bufadd-vim"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false, once: v:false,
+        \  event: 'BufAdd'},
+        \ #{cmd: 'echo "bufhidden"', group: 'TestAutoCmdFns',
+        \  pattern: '*.vim', nested: v:false,
+        \  once: v:false, event: 'BufHidden'}], l)
+
+  " Test for getting the autocmds for a pattern in all the groups
+  let l = autocmd_get(#{pattern: '*.a1b2c3'})
+  call assert_equal([{'cmd': 'echo "bufadd-vim-2"', 'group': 'TestAutoCmdFns2',
+        \ 'pattern': '*.a1b2c3', 'nested': v:false, 'once': v:false,
+        \ 'event': 'BufRead'}], l)
+
+  " Test for getting autocmds for a pattern without any autocmds
+  call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+        \ pattern: '*.abc'}))
+  call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+        \ event: 'BufAdd', pattern: '*.abc'}))
+  call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns',
+        \ event: 'BufWipeout'}))
+  call assert_fails("call autocmd_get(#{group: 'abc', event: 'BufAdd'})",
+        \ 'E367:')
+  let cmd = "echo autocmd_get(#{group: 'TestAutoCmdFns', event: 'abc'})"
+  call assert_fails(cmd, 'E216:')
+  call assert_fails("call autocmd_get(#{group: 'abc'})", 'E367:')
+  call assert_fails("echo autocmd_get(#{event: 'abc'})", 'E216:')
+
+  augroup TestAutoCmdFns
+    au!
+  augroup END
+  call assert_equal([], autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+  " Test for nested and once autocmds
+  augroup TestAutoCmdFns
+    au!
+    autocmd VimSuspend * ++nested echo "suspend"
+    autocmd VimResume * ++once echo "resume"
+  augroup END
+
+  let expected = [
+        \ {'cmd': 'echo "suspend"', 'group': 'TestAutoCmdFns', 'pattern': '*',
+        \ 'nested': v:true, 'once': v:false, 'event': 'VimSuspend'},
+        \ {'cmd': 'echo "resume"', 'group': 'TestAutoCmdFns', 'pattern': '*',
+        \  'nested': v:false, 'once': v:true, 'event': 'VimResume'}]
+  call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+  " Test for buffer-local autocmd
+  augroup TestAutoCmdFns
+    au!
+    autocmd TextYankPost <buffer> echo "textyankpost"
+  augroup END
+
+  let expected = [
+        \ {'cmd': 'echo "textyankpost"', 'group': 'TestAutoCmdFns',
+        \  'pattern': '<buffer=' .. bufnr() .. '>', 'nested': v:false,
+        \  'once': v:false, 'bufnr': bufnr(), 'event': 'TextYankPost'}]
+  call assert_equal(expected, autocmd_get(#{group: 'TestAutoCmdFns'}))
+
+  augroup TestAutoCmdFns
+    au!
+  augroup END
+  augroup! TestAutoCmdFns
+  augroup TestAutoCmdFns2
+    au!
+  augroup END
+  augroup! TestAutoCmdFns2
+
+  call assert_fails("echo autocmd_get(#{group: []})", 'E730:')
+  call assert_fails("echo autocmd_get(#{event: {}})", 'E731:')
+  call assert_fails("echo autocmd_get([])", 'E1206:')
+endfunc
+
+" Test for the autocmd_add() function
+func Test_autocmd_add()
+  " Define a single autocmd in a group
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pattern: '*.sh',
+        \ cmd: 'echo "bufadd"', once: v:true, nested: v:true}])
+  call assert_equal([#{cmd: 'echo "bufadd"', group: 'TestAcSet',
+        \ pattern: '*.sh', nested: v:true, once: v:true,
+        \ event: 'BufAdd'}], autocmd_get(#{group: 'TestAcSet'}))
+
+  " Define two autocmds in the same group
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pattern: '*.sh',
+        \ cmd: 'echo "bufadd"'},
+        \ #{group: 'TestAcSet', event: 'BufEnter', pattern: '*.sh',
+        \   cmd: 'echo "bufenter"'}])
+  call assert_equal([
+        \ #{cmd: 'echo "bufadd"', group: 'TestAcSet', pattern: '*.sh',
+        \   nested: v:false, once: v:false, event: 'BufAdd'},
+        \ #{cmd: 'echo "bufenter"', group: 'TestAcSet', pattern: '*.sh',
+        \   nested: v:false, once: v:false, event: 'BufEnter'}],
+        \   autocmd_get(#{group: 'TestAcSet'}))
+
+  " Define a buffer-local autocmd
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'CursorHold',
+        \ bufnr: bufnr(), cmd: 'echo "cursorhold"'}])
+  call assert_equal([
+        \ #{cmd: 'echo "cursorhold"', group: 'TestAcSet',
+        \   pattern: '<buffer=' .. bufnr() .. '>', nested: v:false,
+        \   once: v:false, bufnr: bufnr(), event: 'CursorHold'}],
+        \   autocmd_get(#{group: 'TestAcSet'}))
+
+  " Use an invalid buffer number
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufEnter',
+        \ bufnr: -1, cmd: 'echo "bufenter"'}])
+  let l = [#{group: 'TestAcSet', event: 'BufAdd', bufnr: 9999,
+        \ cmd: 'echo "bufadd"'}]
+  call assert_fails("echo autocmd_add(l)", 'E680:')
+  let l = [#{group: 'TestAcSet', event: 'BufRead', bufnr: [],
+        \ cmd: 'echo "bufread"'}]
+  call assert_fails("echo autocmd_add(l)", 'E745:')
+  call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+  " Add two commands to the same group, event and pattern
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufUnload',
+        \ pattern: 'abc', cmd: 'echo "cmd1"'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufUnload',
+        \ pattern: 'abc', cmd: 'echo "cmd2"'}])
+  call assert_equal([
+        \ #{cmd: 'echo "cmd1"', group: 'TestAcSet', pattern: 'abc',
+        \   nested: v:false,  once: v:false, event: 'BufUnload'},
+        \ #{cmd: 'echo "cmd2"', group: 'TestAcSet', pattern: 'abc',
+        \   nested: v:false,  once: v:false, event: 'BufUnload'}],
+        \   autocmd_get(#{group: 'TestAcSet'}))
+
+  " When adding a new autocmd, if the autocmd 'group' is not specified, then
+  " the current autocmd group should be used.
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  augroup TestAcSet
+    call autocmd_add([#{event: 'BufHidden', pattern: 'abc', cmd: 'echo "abc"'}])
+  augroup END
+  call assert_equal([
+        \ #{cmd: 'echo "abc"', group: 'TestAcSet', pattern: 'abc',
+        \   nested: v:false,  once: v:false, event: 'BufHidden'}],
+        \   autocmd_get(#{group: 'TestAcSet'}))
+
+  let l = [#{group: 'TestAcSet', event: 'abc', pattern: '*.sh',
+        \ cmd: 'echo "bufadd"'}]
+  call assert_fails('call autocmd_add(l)', 'E216:')
+
+  call assert_fails("call autocmd_add({})", 'E1211:')
+  call assert_equal(v:false,  autocmd_add(test_null_list()))
+  call assert_true(autocmd_add([[]]))
+  call assert_true(autocmd_add([test_null_dict()]))
+
+  augroup TestAcSet
+    au!
+  augroup END
+
+  call autocmd_add([#{group: 'TestAcSet'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd'}])
+  call autocmd_add([#{group: 'TestAcSet', pat: '*.sh'}])
+  call autocmd_add([#{group: 'TestAcSet', cmd: 'echo "a"'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', pat: '*.sh'}])
+  call autocmd_add([#{group: 'TestAcSet', event: 'BufAdd', cmd: 'echo "a"'}])
+  call autocmd_add([#{group: 'TestAcSet', pat: '*.sh', cmd: 'echo "a"'}])
+  call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+  augroup! TestAcSet
+endfunc
+
+" Test for deleting autocmd events and groups
+func Test_autocmd_delete()
+  " Delete an event in an autocmd group
+  augroup TestAcSet
+    au!
+    au BufAdd *.sh echo "bufadd"
+    au BufEnter *.sh echo "bufenter"
+  augroup END
+  call autocmd_delete([#{group: 'TestAcSet', event: 'BufAdd'}])
+  call assert_equal([#{cmd: 'echo "bufenter"', group: 'TestAcSet',
+        \ pattern: '*.sh', nested: v:false, once: v:false,
+        \ event: 'BufEnter'}], autocmd_get(#{group: 'TestAcSet'}))
+
+  " Delete all the events in an autocmd group
+  augroup TestAcSet
+    au BufAdd *.sh echo "bufadd"
+  augroup END
+  call autocmd_delete([#{group: 'TestAcSet', event: '*'}])
+  call assert_equal([], autocmd_get(#{group: 'TestAcSet'}))
+
+  " Delete a non-existing autocmd group
+  call assert_fails("call autocmd_delete([#{group: 'abc'}])", 'E367:')
+  " Delete a non-existing autocmd event
+  let l = [#{group: 'TestAcSet', event: 'abc'}]
+  call assert_fails("call autocmd_delete(l)", 'E216:')
+  " Delete a non-existing autocmd pattern
+  let l = [#{group: 'TestAcSet', event: 'BufAdd', pat: 'abc'}]
+  call assert_true(autocmd_delete(l))
+
+  " Delete an autocmd group
+  augroup TestAcSet
+    au!
+    au BufAdd *.sh echo "bufadd"
+    au BufEnter *.sh echo "bufenter"
+  augroup END
+  call autocmd_delete([#{group: 'TestAcSet'}])
+  call assert_fails("call autocmd_get(#{group: 'TestAcSet'})", 'E367:')
+
+  call assert_true(autocmd_delete([[]]))
+  call assert_true(autocmd_delete([test_null_dict()]))
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -304,6 +304,18 @@ def Test_assert_report()
   v9.CheckDefAndScriptFailure(['assert_report([1, 2])'], ['E1013: Argument 1: type mismatch, expected string but got list<number>', 'E1174: String required for argument 1'])
 enddef
 
+def Test_autocmd_add()
+  v9.CheckDefAndScriptFailure(['autocmd_add({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_autocmd_delete()
+  v9.CheckDefAndScriptFailure(['autocmd_delete({})'], ['E1013: Argument 1: type mismatch, expected list<any> but got dict<unknown>', 'E1211: List required for argument 1'])
+enddef
+
+def Test_autocmd_get()
+  v9.CheckDefAndScriptFailure(['autocmd_get(10)'], ['E1013: Argument 1: type mismatch, expected dict<any> but got number', 'E1206: Dictionary required for argument 1'])
+enddef
+
 def Test_balloon_show()
   CheckGui
   CheckFeature balloon_eval
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    4981,
+/**/
     4980,
 /**/
     4979,