fix(autocmd): skip empty comma-separated patterns properly

Problem: empty comma-separated patterns in an aupat aren't skipped correctly.
Solution: skip consecutive commas in an aupat.

Also simplify the logic.
This commit is contained in:
Sean Dewar
2025-12-20 14:00:10 +00:00
parent 6d2330f50d
commit 1cde71233f
3 changed files with 71 additions and 48 deletions

View File

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

View File

@@ -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]".

View File

@@ -800,4 +800,51 @@ describe('autocmd', function()
fn.execute('autocmd BufEnter <buffer=1>,bar,bar,foo,foo,<buffer>,<buffer=6>,<buffer>')
)
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)