summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorYegappan Lakshmanan <yegappan@yahoo.com>2022-05-19 10:31:47 +0100
committerBram Moolenaar <Bram@vim.org>2022-05-19 10:31:47 +0100
commit1755a91851f7022fdd3eecfbd2cc0b508a2f2a8f (patch)
treed0b3e630953275b71ee1a97a9bdfa55912b19415 /src
parentaaadb5b6f76ea03e5eb460121f3dbf46ad04ce50 (diff)
patch 8.2.4981: it is not possible to manipulate autocommandsv8.2.4981
Problem: It is not possible to manipulate autocommands. Solution: Add functions to add, get and set autocommands. (Yegappan Lakshmanan, closes #10291)
Diffstat (limited to 'src')
-rw-r--r--src/autocmd.c353
-rw-r--r--src/evalfunc.c6
-rw-r--r--src/proto/autocmd.pro3
-rw-r--r--src/testdir/test_autocmd.vim270
-rw-r--r--src/testdir/test_vim9_builtin.vim12
-rw-r--r--src/version.c2
6 files changed, 645 insertions, 1 deletions
diff --git a/src/autocmd.c b/src/autocmd.c
index 4b30c1f530..5bcf3ab561 100644
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -2562,7 +2562,7 @@ get_augroup_name(expand_T *xp UNUSED, int idx)
{
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
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 2c012d047e..35b8984c3c 100644
--- 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
diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro
index ddf391516a..713ae245eb 100644
--- a/src/proto/autocmd.pro
+++ b/src/proto/autocmd.pro
@@ -38,4 +38,7 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd);
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 : */
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index 466b6ec2e4..c6c5f2172c 100644
--- 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
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index 318968b9c6..4f39ebb51e 100644
--- 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
diff --git a/src/version.c b/src/version.c
index 90d9618b9c..01c1f2efd0 100644
--- 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,