diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index 2d480e94b7..ecbb8fd74c 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -823,12 +823,10 @@ static Array get_patterns_from_pattern_or_buf(Object pattern, bool has_buffer, B if (pattern.type != kObjectTypeNil) { if (pattern.type == kObjectTypeString) { const char *pat = pattern.data.string.data; - size_t patlen = aucmd_pattern_length(pat); + size_t patlen = aucmd_span_pattern(pat, &pat); while (patlen) { kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen)); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); + patlen = aucmd_span_pattern(pat + patlen, &pat); } } else if (pattern.type == kObjectTypeArray) { if (!check_string_array(pattern.data.array, "pattern", true, err)) { @@ -838,12 +836,10 @@ static Array get_patterns_from_pattern_or_buf(Object pattern, bool has_buffer, B Array array = pattern.data.array; FOREACH_ITEM(array, entry, { const char *pat = entry.data.string.data; - size_t patlen = aucmd_pattern_length(pat); + size_t patlen = aucmd_span_pattern(pat, &pat); while (patlen) { kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen)); - - pat = aucmd_next_pattern(pat, patlen); - patlen = aucmd_pattern_length(pat); + patlen = aucmd_span_pattern(pat + patlen, &pat); } }) } else { diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index 5210e0a5ef..0300b760a6 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -165,7 +165,7 @@ static void au_show_for_event(int group, event_T event, const char *pat) // Empty pattern shows all autocommands for this event int patlen = 0; if (*pat != NUL) { - patlen = (int)aucmd_pattern_length(pat); + patlen = (int)aucmd_span_pattern(pat, &pat); if (patlen == 0) { // Don't show if it contains only commas return; } @@ -288,8 +288,7 @@ static void au_show_for_event(int group, event_T event, const char *pat) } } - pat = aucmd_next_pattern(endpat, 0); - patlen = (int)aucmd_pattern_length(pat); + patlen = (int)aucmd_span_pattern(endpat, &pat); } while (patlen); } @@ -920,7 +919,7 @@ int do_autocmd_event(event_T event, const char *pat, bool once, int nested, cons } // Loop through all the specified patterns. - int patlen = (int)aucmd_pattern_length(pat); + int patlen = (int)aucmd_span_pattern(pat, &pat); while (patlen) { const char *endpat = pat + patlen; @@ -966,8 +965,7 @@ int do_autocmd_event(event_T event, const char *pat, bool once, int nested, cons autocmd_register(0, event, pat, patlen, group, once, nested, NULL, cmd, &handler_fn); } - pat = aucmd_next_pattern(endpat, 0); - patlen = (int)aucmd_pattern_length(pat); + patlen = (int)aucmd_span_pattern(endpat, &pat); } au_cleanup(); // may really delete removed patterns/commands now @@ -1105,46 +1103,28 @@ int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int return OK; } -size_t aucmd_pattern_length(const char *pat) - FUNC_ATTR_PURE +size_t aucmd_span_pattern(const char *pat, const char **start) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (*pat == NUL) { - return 0; + // Skip leading commas. + while (*pat == ',') { + pat++; } - const char *endpat; - - for (; *pat; pat = endpat + 1) { - // Find end of the pattern. - // Watch out for a comma in braces, like "*.\{obj,o\}". - endpat = pat; - // ignore single comma - if (*endpat == ',') { - continue; + // Find end of the pattern. + // Watch out for a comma in braces, like "*.\{obj,o\}". + const char *p = pat; + int brace_level = 0; + for (; *p && (*p != ',' || brace_level || (p > pat && p[-1] == '\\')); p++) { + if (*p == '{') { + brace_level++; + } else if (*p == '}') { + brace_level--; } - int brace_level = 0; - for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); endpat++) { - if (*endpat == '{') { - brace_level++; - } else if (*endpat == '}') { - brace_level--; - } - } - - return (size_t)(endpat - pat); } - return strlen(pat); -} - -const char *aucmd_next_pattern(const char *pat, size_t patlen) - FUNC_ATTR_PURE -{ - pat = pat + patlen; - if (*pat == ',') { - pat = pat + 1; - } - return pat; + *start = pat; + return (size_t)(p - pat); } /// Implementation of ":doautocmd [group] event [fname]". diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 84dcdd5c23..df00753a57 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -800,4 +800,51 @@ describe('autocmd', function() fn.execute('autocmd BufEnter ,bar,bar,foo,foo,,,') ) end) + + it('parses empty comma-delimited patterns correctly', function() + exec [[ + autocmd User , " + autocmd User ,, " + autocmd User ,,according,to,,all,known,,,laws,, " + ]] + api.nvim_create_autocmd('User', { pattern = ',,of,,,aviation,,,,there,,', command = '' }) + api.nvim_create_autocmd('User', { + pattern = { ',,,,is,,no', ',,way,,,', 'a,,bee{,should be, able to,},fly' }, + command = '', + }) + eq( + { + 'according', + 'to', + 'all', + 'known', + 'laws', + 'of', + 'aviation', + 'there', + 'is', + 'no', + 'way', + 'a', + 'bee{,should be, able to,}', + 'fly', + }, + exec_lua(function() + return vim.tbl_map(function(v) + return v.pattern + end, vim.api.nvim_get_autocmds({ event = 'User' })) + end) + ) + eq( + dedent([[ + + --- Autocommands --- + User + there + is + a + fly]]), + fn.execute('autocmd User ,,,there,is,,a,fly,,') + ) + end) end)