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.

(cherry picked from commit 1cde71233f)
This commit is contained in:
Sean Dewar
2025-12-20 14:00:10 +00:00
committed by github-actions[bot]
parent bea500dbeb
commit 648cf4e586
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 != kObjectTypeNil) {
if (pattern.type == kObjectTypeString) { if (pattern.type == kObjectTypeString) {
const char *pat = pattern.data.string.data; const char *pat = pattern.data.string.data;
size_t patlen = aucmd_pattern_length(pat); size_t patlen = aucmd_span_pattern(pat, &pat);
while (patlen) { while (patlen) {
kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen)); kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
patlen = aucmd_span_pattern(pat + patlen, &pat);
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
} }
} else if (pattern.type == kObjectTypeArray) { } else if (pattern.type == kObjectTypeArray) {
if (!check_string_array(pattern.data.array, "pattern", true, err)) { 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; Array array = pattern.data.array;
FOREACH_ITEM(array, entry, { FOREACH_ITEM(array, entry, {
const char *pat = entry.data.string.data; const char *pat = entry.data.string.data;
size_t patlen = aucmd_pattern_length(pat); size_t patlen = aucmd_span_pattern(pat, &pat);
while (patlen) { while (patlen) {
kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen)); kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
patlen = aucmd_span_pattern(pat + patlen, &pat);
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
} }
}) })
} else { } else {

View File

@@ -156,7 +156,7 @@ static void au_show_for_event(int group, event_T event, const char *pat)
// Empty pattern shows all autocommands for this event // Empty pattern shows all autocommands for this event
int patlen = 0; int patlen = 0;
if (*pat != NUL) { 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 if (patlen == 0) { // Don't show if it contains only commas
return; return;
} }
@@ -279,8 +279,7 @@ static void au_show_for_event(int group, event_T event, const char *pat)
} }
} }
pat = aucmd_next_pattern(endpat, 0); patlen = (int)aucmd_span_pattern(endpat, &pat);
patlen = (int)aucmd_pattern_length(pat);
} while (patlen); } while (patlen);
} }
@@ -906,7 +905,7 @@ int do_autocmd_event(event_T event, const char *pat, bool once, int nested, cons
} }
// Loop through all the specified patterns. // Loop through all the specified patterns.
int patlen = (int)aucmd_pattern_length(pat); int patlen = (int)aucmd_span_pattern(pat, &pat);
while (patlen) { while (patlen) {
const char *endpat = pat + patlen; const char *endpat = pat + patlen;
@@ -952,8 +951,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); autocmd_register(0, event, pat, patlen, group, once, nested, NULL, cmd, &handler_fn);
} }
pat = aucmd_next_pattern(endpat, 0); patlen = (int)aucmd_span_pattern(endpat, &pat);
patlen = (int)aucmd_pattern_length(pat);
} }
au_cleanup(); // may really delete removed patterns/commands now au_cleanup(); // may really delete removed patterns/commands now
@@ -1091,46 +1089,28 @@ int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int
return OK; return OK;
} }
size_t aucmd_pattern_length(const char *pat) size_t aucmd_span_pattern(const char *pat, const char **start)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{ {
if (*pat == NUL) { // Skip leading commas.
return 0; while (*pat == ',') {
pat++;
} }
const char *endpat; // Find end of the pattern.
// Watch out for a comma in braces, like "*.\{obj,o\}".
for (; *pat; pat = endpat + 1) { const char *p = pat;
// Find end of the pattern. int brace_level = 0;
// Watch out for a comma in braces, like "*.\{obj,o\}". for (; *p && (*p != ',' || brace_level || (p > pat && p[-1] == '\\')); p++) {
endpat = pat; if (*p == '{') {
// ignore single comma brace_level++;
if (*endpat == ',') { } else if (*p == '}') {
continue; 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); *start = pat;
} return (size_t)(p - 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;
} }
/// Implementation of ":doautocmd [group] event [fname]". /// Implementation of ":doautocmd [group] event [fname]".

View File

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