fix(api)!: empty non-nil autocmd "pattern" handling

Problem: in autocmd APIs, a non-nil "pattern" containing only empty
'sub'-patterns is silently treated as nil, causing the fallback value to be
unexpectedly used instead.

Solution: for nvim_create_autocmd(), raise a validation error (as no autocmds
would be created). For nvim_{exec,clear}_autocmds(), make it a no-op (as
matching no autocmds is not an error).
This commit is contained in:
Sean Dewar
2026-02-16 09:11:07 +00:00
parent 665ebce569
commit 446e794a9c
3 changed files with 57 additions and 4 deletions

View File

@@ -17,7 +17,8 @@ These changes may require adaptations in your config or plugins.
API
todo
|nvim_create_autocmd()|, |nvim_exec_autocmds()| and |nvim_clear_autocmds()|
no longer treat an empty non-nil pattern as nil.
DIAGNOSTICS

View File

@@ -449,6 +449,9 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
if (ERROR_SET(err)) {
goto cleanup;
}
VALIDATE(patterns.size > 0, "%s", "No non-empty patterns specified", {
goto cleanup;
});
if (HAS_KEY(opts, create_autocmd, desc)) {
desc = opts->desc.data;
@@ -830,9 +833,7 @@ static Array get_patterns_from_pattern_or_buf(Object pattern, bool has_buffer, B
}
kvi_push(patterns, STRING_OBJ(arena_printf(arena, "<buffer=%d>", (int)buf->handle)));
}
if (kv_size(patterns) == 0 && fallback) {
} else if (fallback) {
kvi_push(patterns, CSTR_AS_OBJ(fallback));
}

View File

@@ -77,6 +77,41 @@ describe('autocmd api', function()
"Invalid 'event': 'BufAdd,BufDelete'",
pcall_err(api.nvim_create_autocmd, 'BufAdd,BufDelete', { command = '' })
)
eq(
'No non-empty patterns specified',
pcall_err(api.nvim_create_autocmd, 'VimEnter', { pattern = '', command = '' })
)
eq(
'No non-empty patterns specified',
pcall_err(api.nvim_create_autocmd, 'VimEnter', { pattern = ',', command = '' })
)
eq(
'No non-empty patterns specified',
pcall_err(api.nvim_create_autocmd, 'VimEnter', { pattern = {}, command = '' })
)
eq(
'No non-empty patterns specified',
pcall_err(api.nvim_create_autocmd, 'VimEnter', { pattern = { '' }, command = '' })
)
eq(
'No non-empty patterns specified',
pcall_err(api.nvim_create_autocmd, 'VimEnter', { pattern = { ',,' }, command = '' })
)
-- OK when at least one non-empty pattern is given.
api.nvim_create_autocmd('VimEnter', { pattern = 'hi,,', command = '' })
api.nvim_create_autocmd('VimEnter', { pattern = { ',', 'bye' }, command = '' })
eq(
{ 'hi', 'bye' },
exec_lua(function()
return vim
.iter(vim.api.nvim_get_autocmds({ event = 'VimEnter' }))
:map(function(ev)
return ev.pattern
end)
:totable()
end)
)
end)
it('doesnt leak when you use ++once', function()
@@ -1053,6 +1088,13 @@ describe('autocmd api', function()
api.nvim_exec_autocmds('BufReadPre', { pattern = { 'foo', 'bar', 'baz', 'frederick' } })
eq(22, api.nvim_get_var('autocmd_executed'))
-- Non-nil, fully-empty patterns execute nothing.
api.nvim_exec_autocmds('BufReadPre', { pattern = '' })
api.nvim_exec_autocmds('BufReadPre', { pattern = ',,,,' })
api.nvim_exec_autocmds('BufReadPre', { pattern = {} })
api.nvim_exec_autocmds('BufReadPre', { pattern = { ',', '' } })
eq(22, api.nvim_get_var('autocmd_executed'))
end)
it('can pass the buffer', function()
@@ -1600,6 +1642,15 @@ describe('autocmd api', function()
local after_delete_events = api.nvim_get_autocmds { event = { 'InsertEnter', 'InsertLeave' } }
eq(2, #after_delete_events)
-- Non-nil, fully-empty patterns clear nothing.
local event_count = #api.nvim_get_autocmds {}
api.nvim_clear_autocmds({ pattern = '' })
api.nvim_clear_autocmds({ pattern = ',,,' })
api.nvim_clear_autocmds({ pattern = {} })
api.nvim_clear_autocmds({ pattern = { '', '' } })
api.nvim_clear_autocmds({ pattern = { ',', ',,' } })
eq(event_count, #api.nvim_get_autocmds {})
end)
it('should allow clearing by buffer', function()