# HG changeset patch # User Bram Moolenaar # Date 1653671703 -7200 # Node ID aadeddf38d9b808def5a897fc03840f87f60d8e4 # Parent 0210166c162ebf44d1a25cb98a53f0a3971c31bf patch 8.2.5030: autocmd_add() can only handle one event and pattern Commit: https://github.com/vim/vim/commit/e0ff3a7de6030b73d94121626deb8229e66e82b2 Author: Yegappan Lakshmanan Date: Fri May 27 18:05:33 2022 +0100 patch 8.2.5030: autocmd_add() can only handle one event and pattern Problem: autocmd_add() can only handle one event and pattern. Solution: Support a list of events and patterns. (Yegappan Lakshmanan, closes #10483) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -938,7 +938,8 @@ autocmd_add({acmds}) *autocmd_add()* item is ignored. cmd Ex command to execute for this autocmd event event autocmd event name. Refer to |autocmd-events|. - TODO: currently only accepts one event. + This can be either a String with a single + event name or a List of event names. 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 @@ -950,7 +951,9 @@ autocmd_add({acmds}) *autocmd_add()* |autocmd-once|. pattern autocmd pattern string. Refer to |autocmd-patterns|. If "bufnr" item is - present, then this item is ignored. + present, then this item is ignored. This can + be a String with a single pattern or a List of + patterns. replace boolean flag, set to v:true to remove all the commands associated with the specified autocmd event and group and add the {cmd}. This is diff --git a/src/autocmd.c b/src/autocmd.c --- a/src/autocmd.c +++ b/src/autocmd.c @@ -2754,16 +2754,22 @@ theend: static void autocmd_add_or_delete(typval_T *argvars, typval_T *rettv, int delete) { - list_T *event_list; + list_T *aucmd_list; listitem_T *li; dict_T *event_dict; + dictitem_T *di; char_u *event_name = NULL; + list_T *event_list; + listitem_T *eli; event_T event; char_u *group_name = NULL; int group; char_u *pat = NULL; + list_T *pat_list; + listitem_T *pli; char_u *cmd = NULL; char_u *end; + char_u *p; int once; int nested; int replace; // replace the cmd for a group/event @@ -2776,16 +2782,18 @@ autocmd_add_or_delete(typval_T *argvars, if (check_for_list_arg(argvars, 0) == FAIL) return; - event_list = argvars[0].vval.v_list; - if (event_list == NULL) + aucmd_list = argvars[0].vval.v_list; + if (aucmd_list == NULL) return; - FOR_ALL_LIST_ITEMS(event_list, li) + FOR_ALL_LIST_ITEMS(aucmd_list, li) { - VIM_CLEAR(event_name); VIM_CLEAR(group_name); - VIM_CLEAR(pat); VIM_CLEAR(cmd); + event_name = NULL; + event_list = NULL; + pat = NULL; + pat_list = NULL; if (li->li_tv.v_type != VAR_DICT) continue; @@ -2794,29 +2802,31 @@ autocmd_add_or_delete(typval_T *argvars, if (event_dict == NULL) continue; - event_name = dict_get_string(event_dict, (char_u *)"event", TRUE); - if (event_name == NULL) + di = dict_find(event_dict, (char_u *)"event", -1); + if (di != 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; + if (di->di_tv.v_type == VAR_STRING) + { + event_name = di->di_tv.vval.v_string; + if (event_name == NULL) + { + emsg(_(e_string_required)); + continue; + } + } + else if (di->di_tv.v_type == VAR_LIST) + { + event_list = di->di_tv.vval.v_list; + if (event_list == NULL) + { + emsg(_(e_list_required)); + continue; + } + } else { - event = event_name2nr(event_name, &end); - if (event == NUM_EVENTS) - { - semsg(_(e_no_such_event_str), event_name); - retval = VVAL_FALSE; - break; - } + emsg(_(e_string_or_list_expected)); + continue; } } @@ -2859,21 +2869,40 @@ autocmd_add_or_delete(typval_T *argvars, if (bnum == -1) continue; - pat = alloc(128 + 1); - if (pat == NULL) - continue; - vim_snprintf((char *)pat, 128, "", (int)bnum); + vim_snprintf((char *)IObuff, IOSIZE, "", (int)bnum); + pat = IObuff; } else { - pat = dict_get_string(event_dict, (char_u *)"pattern", TRUE); - if (pat == NULL) + di = dict_find(event_dict, (char_u *)"pattern", -1); + if (di != NULL) { - if (delete) - pat = vim_strsave((char_u *)""); + if (di->di_tv.v_type == VAR_STRING) + { + pat = di->di_tv.vval.v_string; + if (pat == NULL) + { + emsg(_(e_string_required)); + continue; + } + } + else if (di->di_tv.v_type == VAR_LIST) + { + pat_list = di->di_tv.vval.v_list; + if (pat_list == NULL) + { + emsg(_(e_list_required)); + continue; + } + } else + { + emsg(_(e_string_or_list_expected)); continue; + } } + else if (delete) + pat = (char_u *)""; } once = dict_get_bool(event_dict, (char_u *)"once", FALSE); @@ -2891,9 +2920,10 @@ autocmd_add_or_delete(typval_T *argvars, continue; } - if (event == NUM_EVENTS) + if (delete && (event_name == NULL + || (event_name[0] == '*' && event_name[1] == NUL))) { - // event is '*', apply for all the events + // if the event name is not specified or '*', delete all the events for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) { @@ -2907,11 +2937,76 @@ autocmd_add_or_delete(typval_T *argvars, } else { - if (do_autocmd_event(event, pat, once, nested, cmd, - delete | replace, group, 0) == FAIL) + eli = NULL; + end = NULL; + while (TRUE) { - retval = VVAL_FALSE; - break; + if (event_list != NULL) + { + if (eli == NULL) + eli = event_list->lv_first; + else + eli = eli->li_next; + if (eli == NULL) + break; + if (eli->li_tv.v_type != VAR_STRING + || eli->li_tv.vval.v_string == NULL) + { + emsg(_(e_string_required)); + continue; + } + p = eli->li_tv.vval.v_string; + } + else + { + if (end == NULL) + p = end = event_name; + if (end == NULL || *end == NUL) + break; + } + if (p == NULL) + continue; + + event = event_name2nr(p, &end); + if (event == NUM_EVENTS || *end != NUL) + { + semsg(_(e_no_such_event_str), p); + retval = VVAL_FALSE; + break; + } + if (pat != NULL) + { + if (do_autocmd_event(event, pat, once, nested, cmd, + delete | replace, group, 0) == FAIL) + { + retval = VVAL_FALSE; + break; + } + } + else if (pat_list != NULL) + { + FOR_ALL_LIST_ITEMS(pat_list, pli) + { + if (pli->li_tv.v_type != VAR_STRING + || pli->li_tv.vval.v_string == NULL) + { + emsg(_(e_string_required)); + continue; + } + if (do_autocmd_event(event, + pli->li_tv.vval.v_string, once, nested, + cmd, delete | replace, group, 0) == + FAIL) + { + retval = VVAL_FALSE; + break; + } + } + if (retval == VVAL_FALSE) + break; + } + if (event_name != NULL) + p = end; } } @@ -2925,9 +3020,7 @@ autocmd_add_or_delete(typval_T *argvars, au_del_group(group_name); } - VIM_CLEAR(event_name); VIM_CLEAR(group_name); - VIM_CLEAR(pat); VIM_CLEAR(cmd); current_augroup = save_augroup; diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -1184,7 +1184,7 @@ EXTERN char e_invalid_argument_str[] INIT(= N_("E475: Invalid argument: %s")); EXTERN char e_invalid_value_for_argument_str[] INIT(= N_("E475: Invalid value for argument %s")); -#if defined(FEAT_JOB_CHANNEL) || defined(FEAT_PROP_POPUP) +#if defined(FEAT_JOB_CHANNEL) || defined(FEAT_PROP_POPUP) || defined(FEAT_EVAL) EXTERN char e_invalid_value_for_argument_str_str[] INIT(= N_("E475: Invalid value for argument %s: %s")); #endif @@ -3280,7 +3280,7 @@ EXTERN char e_atom_engine_must_be_at_sta INIT(= N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern")); #ifdef FEAT_EVAL EXTERN char e_bitshift_ops_must_be_number[] - INIT(= N_("E1282: bitshift operands must be numbers")); + INIT(= N_("E1282: Bitshift operands must be numbers")); EXTERN char e_bitshift_ops_must_be_postive[] - INIT(= N_("E1283: bitshift amount must be a positive number")); + INIT(= N_("E1283: Bitshift amount must be a positive number")); #endif 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 @@ -3429,6 +3429,83 @@ func Test_autocmd_add() \ cmd: 'echo "bufadd"'}] call assert_fails('call autocmd_add(l)', 'E216:') + " Test for using a list of events and patterns + call autocmd_delete([#{group: 'TestAcSet'}]) + let l = [#{group: 'TestAcSet', event: ['BufEnter', 'BufLeave'], + \ pattern: ['*.py', '*.sh'], cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + call assert_equal([ + \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.py', + \ nested: v:false, once: v:false, event: 'BufEnter'}, + \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.sh', + \ nested: v:false, once: v:false, event: 'BufEnter'}, + \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.py', + \ nested: v:false, once: v:false, event: 'BufLeave'}, + \ #{cmd: 'echo "bufcmds"', group: 'TestAcSet', pattern: '*.sh', + \ nested: v:false, once: v:false, event: 'BufLeave'}], + \ autocmd_get(#{group: 'TestAcSet'})) + + " Test for invalid values for 'event' item + call autocmd_delete([#{group: 'TestAcSet'}]) + let l = [#{group: 'TestAcSet', event: test_null_string(), + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E928:') + let l = [#{group: 'TestAcSet', event: test_null_list(), + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E714:') + let l = [#{group: 'TestAcSet', event: {}, + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E777:') + let l = [#{group: 'TestAcSet', event: [{}], + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E928:') + let l = [#{group: 'TestAcSet', event: [test_null_string()], + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E928:') + let l = [#{group: 'TestAcSet', event: 'BufEnter,BufLeave', + \ pattern: '*.py', cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E216:') + let l = [#{group: 'TestAcSet', event: [], + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + let l = [#{group: 'TestAcSet', event: [""], + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E216:') + let l = [#{group: 'TestAcSet', event: "", + \ pattern: "*.py", cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + call assert_equal([], autocmd_get(#{group: 'TestAcSet'})) + + " Test for invalid values for 'pattern' item + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: test_null_string(), cmd: 'echo "bufcmds"'}] + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: test_null_list(), cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E714:') + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: {}, cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E777:') + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: [{}], cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E928:') + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: [test_null_string()], cmd: 'echo "bufcmds"'}] + call assert_fails('call autocmd_add(l)', 'E928:') + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: [], cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: [""], cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + let l = [#{group: 'TestAcSet', event: "BufEnter", + \ pattern: "", cmd: 'echo "bufcmds"'}] + call autocmd_add(l) + call assert_equal([], autocmd_get(#{group: 'TestAcSet'})) + + let l = [#{group: 'TestAcSet', event: 'BufEnter,abc,BufLeave', + \ pattern: '*.py', cmd: 'echo "bufcmds"'}] + 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([[]])) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -735,6 +735,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 5030, +/**/ 5029, /**/ 5028,