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 )
This commit is contained in:
Yegappan Lakshmanan 2022-05-19 10:31:47 +01:00 committed by Bram Moolenaar
parent aaadb5b6f7
commit 1755a91851
9 changed files with 801 additions and 1 deletions

View file

@ -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 messages this can happen anyway.
==============================================================================
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 defining and removing autocommands.
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: >

View file

@ -60,6 +60,9 @@ assert_report({msg}) Number report a test failure
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.

View file

@ -925,6 +925,11 @@ Date and Time: *date-functions* *time-functions*
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

View file

@ -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

View file

@ -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

View file

@ -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 : */

View file

@ -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

View file

@ -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

View file

@ -746,6 +746,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
4981,
/**/
4980,
/**/