diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 01f76c762e..99886020e9 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -510,9 +510,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc int retval; - for (size_t i = 0; i < patterns.size; i++) { - Object pat = patterns.items[i]; - + FOREACH_ITEM(patterns, pat, { // See: TODO(sctx) WITH_SCRIPT_CONTEXT(channel_id, { retval = autocmd_register(autocmd_id, @@ -530,7 +528,7 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc api_set_error(err, kErrorTypeException, "Failed to set autocmd"); goto cleanup; } - } + }) }); @@ -743,11 +741,8 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) bool modeline = true; buf_T *buf = curbuf; - bool set_buf = false; - - char_u *pattern = NULL; - bool set_pattern = false; + Array patterns = ARRAY_DICT_INIT; Array event_array = ARRAY_DICT_INIT; if (!unpack_string_or_array(&event_array, &event, "event", true, err)) { @@ -787,36 +782,32 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) } buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); - set_buf = true; if (ERROR_SET(err)) { goto cleanup; } } - if (opts->pattern.type != kObjectTypeNil) { - if (opts->pattern.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'pattern' must be a string"); - goto cleanup; - } + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } - pattern = vim_strsave((char_u *)opts->pattern.data.string.data); - set_pattern = true; + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); } modeline = api_object_to_bool(opts->modeline, "modeline", true, err); - if (set_pattern && set_buf) { - api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); - goto cleanup; - } - bool did_aucmd = false; FOREACH_ITEM(event_array, event_str, { GET_ONE_EVENT(event_nr, event_str, cleanup) - did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL); - }) + FOREACH_ITEM(patterns, pat, { + char_u *fname = opts->buffer.type == kObjectTypeNil ? (char_u *)pat.data.string.data : NULL; + did_aucmd |= + apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL); + }) + }) if (did_aucmd && modeline) { do_modelines(0); @@ -824,13 +815,13 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err) cleanup: api_free_array(event_array); - XFREE_CLEAR(pattern); + api_free_array(patterns); } static bool check_autocmd_string_array(Array arr, char *k, Error *err) { - for (size_t i = 0; i < arr.size; i++) { - if (arr.items[i].type != kObjectTypeString) { + FOREACH_ITEM(arr, entry, { + if (entry.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "All entries in '%s' must be strings", @@ -839,13 +830,13 @@ static bool check_autocmd_string_array(Array arr, char *k, Error *err) } // Disallow newlines in the middle of the line. - const String l = arr.items[i].data.string; + const String l = entry.data.string; if (memchr(l.data, NL, l.size)) { api_set_error(err, kErrorTypeValidation, "String cannot contain newlines"); return false; } - } + }) return true; } @@ -932,8 +923,8 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob } Array array = v->data.array; - for (size_t i = 0; i < array.size; i++) { - char_u *pat = (char_u *)array.items[i].data.string.data; + FOREACH_ITEM(array, entry, { + char_u *pat = (char_u *)entry.data.string.data; size_t patlen = aucmd_pattern_length(pat); while (patlen) { ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); @@ -941,15 +932,15 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob pat = aucmd_next_pattern(pat, patlen); patlen = aucmd_pattern_length(pat); } - } + }) } else { api_set_error(err, kErrorTypeValidation, - "'pattern' must be a string"); + "'pattern' must be a string or table"); return false; } } else if (buffer.type != kObjectTypeNil) { - if (buffer.type != kObjectTypeInteger) { + if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) { api_set_error(err, kErrorTypeValidation, "'buffer' must be an integer"); diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 50990c41f5..259ed3f961 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -610,6 +610,26 @@ describe('autocmd api', function() eq(true, meths.get_var("autocmd_executed")) end) + it("can trigger multiple patterns", function() + meths.set_var("autocmd_executed", 0) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed += 1", + }) + + meths.exec_autocmds("BufReadPost", { pattern = { "*.lua", "*.vim" } }) + eq(2, meths.get_var("autocmd_executed")) + + meths.create_autocmd("BufReadPre", { + pattern = { "bar", "foo" }, + command = "let g:autocmd_executed += 10", + }) + + meths.exec_autocmds("BufReadPre", { pattern = { "foo", "bar", "baz", "frederick" }}) + eq(22, meths.get_var("autocmd_executed")) + end) + it("can pass the buffer", function() meths.set_var("buffer_executed", -1) eq(-1, meths.get_var("buffer_executed")) @@ -666,7 +686,7 @@ describe('autocmd api', function() meths.exec_autocmds("CursorHoldI", { buffer = 1 }) eq('none', meths.get_var("filename_executed")) - meths.exec_autocmds("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) + meths.exec_autocmds("CursorHoldI", { buffer = meths.get_current_buf() }) eq('__init__.py', meths.get_var("filename_executed")) -- Reset filename