fix(autocmd): parsing of comma-separated buflocal patterns

Problem: patterns after a buffer-local pattern in a comma-separated aupat are
         ignored.
Solution: ensure pat refers to the original buffer after each pattern, not the
          buflocal_pat buffer, and when printing make sure it's normalized for
          each pattern, not just the first.

Also simplify the logic when printing all autocommands for an event, and ensure
headings aren't repeated for each event when printing autocommands.
This commit is contained in:
Sean Dewar
2025-12-20 16:30:28 +00:00
parent ce7ed53fba
commit 6d2330f50d
3 changed files with 104 additions and 30 deletions

View File

@@ -154,6 +154,7 @@ static void au_show_for_all_events(int group, const char *pat)
}
static void au_show_for_event(int group, event_T event, const char *pat)
FUNC_ATTR_NONNULL_ALL
{
AutoCmdVec *const acs = &autocmds[(int)event];
// Return early if there are no autocmds for this event
@@ -161,10 +162,23 @@ static void au_show_for_event(int group, event_T event, const char *pat)
return;
}
char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
int patlen;
// Empty pattern shows all autocommands for this event
int patlen = 0;
if (*pat != NUL) {
patlen = (int)aucmd_pattern_length(pat);
if (patlen == 0) { // Don't show if it contains only commas
return;
}
}
char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
int last_group = AUGROUP_ERROR;
const char *last_group_name = NULL;
// Loop through all the specified patterns.
do {
AutoPat *last_ap = NULL;
const char *endpat = pat + patlen;
// detect special <buffer[=X]> buffer-local patterns
if (aupat_is_buflocal(pat, patlen)) {
@@ -174,21 +188,6 @@ static void au_show_for_event(int group, event_T event, const char *pat)
patlen = (int)strlen(buflocal_pat);
}
if (patlen == 0) {
return;
}
assert(*pat != NUL);
} else {
pat = NULL;
patlen = 0;
}
// Loop through all the specified patterns.
while (true) {
AutoPat *last_ap = NULL;
int last_group = AUGROUP_ERROR;
const char *last_group_name = NULL;
for (size_t i = 0; i < kv_size(*acs); i++) {
AutoCmd *const ac = &kv_A(*acs, i);
@@ -204,7 +203,7 @@ static void au_show_for_event(int group, event_T event, const char *pat)
// For <buffer[=X]>, this condition works because we normalize
// all buffer-local patterns.
if ((group != AUGROUP_ALL && ac->pat->group != group)
|| (pat != NULL
|| (patlen
&& (ac->pat->patlen != patlen || strncmp(pat, ac->pat->pat, (size_t)patlen) != 0))) {
continue;
}
@@ -289,17 +288,9 @@ static void au_show_for_event(int group, event_T event, const char *pat)
}
}
// If a pattern is provided, find next pattern. Otherwise exit after single iteration.
if (pat != NULL) {
pat = aucmd_next_pattern(pat, (size_t)patlen);
patlen = (int)aucmd_pattern_length(pat);
if (patlen == 0) {
break;
}
} else {
break;
}
}
pat = aucmd_next_pattern(endpat, 0);
patlen = (int)aucmd_pattern_length(pat);
} while (patlen);
}
// Delete autocommand.
@@ -931,6 +922,8 @@ 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);
while (patlen) {
const char *endpat = pat + patlen;
// detect special <buffer[=X]> buffer-local patterns
bool is_buflocal = aupat_is_buflocal(pat, patlen);
if (is_buflocal) {
@@ -973,7 +966,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(pat, (size_t)patlen);
pat = aucmd_next_pattern(endpat, 0);
patlen = (int)aucmd_pattern_length(pat);
}

View File

@@ -731,4 +731,73 @@ describe('autocmd', function()
]]
eq('flarb', fn.bufname())
end)
it('does not ignore comma-separated patterns after a buffer-local pattern', function()
exec [[
edit baz " reuses buffer 1
edit bazinga
edit bar
edit boop
edit foo
edit floob
let g:events1 = []
autocmd BufEnter <buffer>,<buffer=1>,boop,bar let g:events1 += [expand('<afile>')]
let g:events2 = []
augroup flobby
autocmd BufEnter <buffer=2>,foo let g:events2 += [expand('<afile>')]
autocmd BufEnter foo,<buffer=3> let g:events2 += ['flobby ' .. expand('<afile>')]
augroup END
]]
eq(
dedent([=[
--- Autocommands ---
BufEnter
<buffer=6>
let g:events1 += [expand('<afile>')]
<buffer=1>
let g:events1 += [expand('<afile>')]
boop let g:events1 += [expand('<afile>')]
bar let g:events1 += [expand('<afile>')]
flobby BufEnter
<buffer=2>
let g:events2 += [expand('<afile>')]
foo let g:events2 += [expand('<afile>')]
let g:events2 += ['flobby ' .. expand('<afile>')]
<buffer=3>
let g:events2 += ['flobby ' .. expand('<afile>')]]=]),
fn.execute('autocmd BufEnter')
)
command('bufdo "')
eq({ 'baz', 'bar', 'boop', 'floob' }, eval('g:events1'))
eq({ 'bazinga', 'flobby bar', 'foo', 'flobby foo' }, eval('g:events2'))
-- Also make sure it doesn't repeat the group/event name or pattern for each printed event.
-- Do however repeat the pattern if the user specified it multiple times to be printed.
-- These conditions aim to make the output consistent with Vim.
eq(
dedent([=[
--- Autocommands ---
BufEnter
<buffer=1>
let g:events1 += [expand('<afile>')]
bar let g:events1 += [expand('<afile>')]
bar let g:events1 += [expand('<afile>')]
flobby BufEnter
foo let g:events2 += [expand('<afile>')]
let g:events2 += ['flobby ' .. expand('<afile>')]
foo let g:events2 += [expand('<afile>')]
let g:events2 += ['flobby ' .. expand('<afile>')]
BufEnter
<buffer=6>
let g:events1 += [expand('<afile>')]
<buffer=6>
let g:events1 += [expand('<afile>')]
<buffer=6>
let g:events1 += [expand('<afile>')]]=]),
fn.execute('autocmd BufEnter <buffer=1>,bar,bar,foo,foo,<buffer>,<buffer=6>,<buffer>')
)
end)
end)

View File

@@ -188,6 +188,18 @@ describe(':autocmd', function()
B echo "B3"]]),
fn.execute('autocmd test_3 * B')
)
eq(
dedent([[
--- Autocommands ---]]),
fn.execute('autocmd * ,')
)
eq(
dedent([[
--- Autocommands ---]]),
fn.execute('autocmd * ,,,')
)
end)
it('should skip consecutive patterns', function()