# HG changeset patch # User Bram Moolenaar # Date 1652953505 -7200 # Node ID c5862dfaf0bdd72dcc0c88b56024eed3c020abf3 # Parent 40a5b3396eb4dc7f69fef76f8acaf591e0c09854 patch 8.2.4981: it is not possible to manipulate autocommands Commit: https://github.com/vim/vim/commit/1755a91851f7022fdd3eecfbd2cc0b508a2f2a8f Author: Yegappan Lakshmanan 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) diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -82,6 +82,9 @@ triggered. / :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 or . 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: > diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- 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 "". + 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. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt --- 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 diff --git a/src/autocmd.c b/src/autocmd.c --- 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 + // ". 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, "", (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 --- 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 --- 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 : */ diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim --- 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 echo "textyankpost" + augroup END + + let expected = [ + \ {'cmd': 'echo "textyankpost"', 'group': 'TestAutoCmdFns', + \ 'pattern': '', '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: '', 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 --- 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', 'E1174: String required for argument 1']) enddef +def Test_autocmd_add() + v9.CheckDefAndScriptFailure(['autocmd_add({})'], ['E1013: Argument 1: type mismatch, expected list but got dict', 'E1211: List required for argument 1']) +enddef + +def Test_autocmd_delete() + v9.CheckDefAndScriptFailure(['autocmd_delete({})'], ['E1013: Argument 1: type mismatch, expected list but got dict', 'E1211: List required for argument 1']) +enddef + +def Test_autocmd_get() + v9.CheckDefAndScriptFailure(['autocmd_get(10)'], ['E1013: Argument 1: type mismatch, expected dict 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 --- 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,