Files
neovim/src/nvim/api/autocmd.c
Justin M. Keyes f1fad32e2e docs: misc, custom text-object #39877
text-object-define is a pattern I found in tpope's plugins (e.g.
https://github.com/tpope/vim-jdaddy) which shows an elegant way to
define a text-object. (Any mistakes in the example are my fault.)
2026-05-20 05:21:27 -04:00

882 lines
28 KiB
C

#include <assert.h>
#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/autocmd.h"
#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
#include "nvim/memory_defs.h"
#include "nvim/option.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
#include "nvim/vim_defs.h"
#include "api/autocmd.c.generated.h"
#define AUCMD_MAX_PATTERNS 256
// Copy string or array of strings into an empty array.
// Get the event number, unless it is an error. Then do `or_else`.
#define GET_ONE_EVENT(event_nr, event_str, or_else) \
event_T event_nr = \
event_name2nr_str(event_str.data.string); \
VALIDATE_S((event_nr < NUM_EVENTS), "event", event_str.data.string.data, { \
or_else; \
});
// ID for associating autocmds created via nvim_create_autocmd
// Used to delete autocmds from nvim_del_autocmd
static int64_t next_autocmd_id = 1;
/// Gets all autocommands matching ALL criteria in the {opts} query.
///
/// Note: When multiple patterns or events are provided they have "OR" semantics (any combination
/// is matched).
///
/// Examples:
///
/// ```lua
/// -- Matches all criteria
/// autocommands = vim.api.nvim_get_autocmds({
/// group = 'MyGroup',
/// event = {'BufEnter', 'BufWinEnter'},
/// pattern = {'*.c', '*.h'}
/// })
///
/// -- All commands from one group
/// autocommands = vim.api.nvim_get_autocmds({
/// group = 'MyGroup',
/// })
/// ```
///
/// @param opts Dict with at least one of these keys:
/// - buf: (`integer[]|integer?`) Buffer id or list of buffer ids, for buffer-local autocommands
/// |autocmd-buflocal|. Not allowed with {pattern}.
/// - event: (`vim.api.keyset.events|vim.api.keyset.events[]?`) Event(s) to match |autocmd-events|.
/// - group: (`string|table?`) Group name or id to match.
/// - id: (`integer?`) Autocommand ID to match.
/// - pattern: (`string|table?`) Pattern(s) to match |autocmd-pattern|. Not allowed with {buf}.
/// @return Array of matching autocommands, where each item has:
/// - buf (`integer?`): Buffer id (only for |autocmd-buffer-local|).
/// - buflocal (`boolean?`): true if the autocommand is buffer-local |autocmd-buffer-local|.
/// - callback: (`function|string?`): Event handler: a Lua function or Vimscript function name.
/// - command: (`string`) Event handler: an Ex-command. Empty if a `callback` is set.
/// - desc: (`string`) Description.
/// - event: (`vim.api.keyset.events`) Event name(s).
/// - group: (`integer`) Group id.
/// - group_name: (`string`) Group name.
/// - id: (`integer`) Autocommand id (only when defined with the API).
/// - once: (`boolean`) true if |autocmd-once| was set.
/// - pattern: (`string`) Autocommand pattern.
ArrayOf(DictAs(get_autocmds__ret)) nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena,
Error *err)
FUNC_API_SINCE(9)
{
ArrayBuilder autocmd_list = KV_INITIAL_VALUE;
kvi_init(autocmd_list);
char *pattern_filters[AUCMD_MAX_PATTERNS];
Array buffers = ARRAY_DICT_INIT;
bool event_set[NUM_EVENTS] = { false };
bool check_event = false;
int group = 0;
switch (opts->group.type) {
case kObjectTypeNil:
break;
case kObjectTypeString:
group = augroup_find(opts->group.data.string.data);
VALIDATE_S((group >= 0), "group", opts->group.data.string.data, {
goto cleanup;
});
break;
case kObjectTypeInteger:
group = (int)opts->group.data.integer;
char *name = group == 0 ? NULL : augroup_name(group);
VALIDATE_INT(augroup_exists(name), "group", opts->group.data.integer, {
goto cleanup;
});
break;
default:
VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
goto cleanup;
});
}
int id = (HAS_KEY(opts, get_autocmds, id)) ? (int)opts->id : -1;
if (HAS_KEY(opts, get_autocmds, event)) {
check_event = true;
Object v = opts->event;
if (v.type == kObjectTypeString) {
GET_ONE_EVENT(event_nr, v, goto cleanup);
event_set[event_nr] = true;
} else if (v.type == kObjectTypeArray) {
FOREACH_ITEM(v.data.array, event_v, {
VALIDATE_T("event item", kObjectTypeString, event_v.type, {
goto cleanup;
});
GET_ONE_EVENT(event_nr, event_v, goto cleanup);
event_set[event_nr] = true;
})
} else {
VALIDATE_EXP(false, "event", "String or Array", NULL, {
goto cleanup;
});
}
}
bool has_buf = HAS_KEY(opts, get_autocmds, buf) || HAS_KEY(opts, get_autocmds, buffer);
Object buf = HAS_KEY(opts, get_autocmds, buf) ? opts->buf : opts->buffer;
VALIDATE_CON((!HAS_KEY(opts, get_autocmds, buf) || !HAS_KEY(opts, get_autocmds, buffer)),
"buf", "buffer", {
goto cleanup;
});
VALIDATE_CON((!HAS_KEY(opts, get_autocmds, pattern) || !has_buf),
"pattern", "buf", {
goto cleanup;
});
int pattern_filter_count = 0;
if (HAS_KEY(opts, get_autocmds, pattern)) {
Object v = opts->pattern;
if (v.type == kObjectTypeString) {
pattern_filters[pattern_filter_count] = v.data.string.data;
pattern_filter_count += 1;
} else if (v.type == kObjectTypeArray) {
VALIDATE((v.data.array.size <= AUCMD_MAX_PATTERNS),
"Too many patterns (maximum of %d)", AUCMD_MAX_PATTERNS, {
goto cleanup;
});
FOREACH_ITEM(v.data.array, item, {
VALIDATE_T("pattern", kObjectTypeString, item.type, {
goto cleanup;
});
pattern_filters[pattern_filter_count] = item.data.string.data;
pattern_filter_count += 1;
});
} else {
VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
goto cleanup;
});
}
}
if (buf.type == kObjectTypeInteger || buf.type == kObjectTypeBuffer) {
buf_T *b = find_buffer_by_handle((Buffer)buf.data.integer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
String pat = arena_printf(arena, "<buffer=%d>", (int)b->handle);
buffers = arena_array(arena, 1);
ADD_C(buffers, STRING_OBJ(pat));
} else if (buf.type == kObjectTypeArray) {
VALIDATE((buf.data.array.size <= AUCMD_MAX_PATTERNS),
"Too many buffers (maximum of %d)", AUCMD_MAX_PATTERNS, {
goto cleanup;
});
buffers = arena_array(arena, kv_size(buf.data.array));
FOREACH_ITEM(buf.data.array, bufnr, {
VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
"buffer", "Integer", api_typename(bufnr.type), {
goto cleanup;
});
buf_T *b = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
ADD_C(buffers, STRING_OBJ(arena_printf(arena, "<buffer=%d>", (int)b->handle)));
});
} else if (has_buf) {
VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(buf.type), {
goto cleanup;
});
}
FOREACH_ITEM(buffers, bufnr, {
pattern_filters[pattern_filter_count] = bufnr.data.string.data;
pattern_filter_count += 1;
});
FOR_ALL_AUEVENTS(event) {
if (check_event && !event_set[event]) {
continue;
}
AutoCmdVec *acs = au_get_autocmds_for_event(event);
for (size_t i = 0; i < kv_size(*acs); i++) {
AutoCmd *const ac = &kv_A(*acs, i);
AutoPat *const ap = ac->pat;
if (ap == NULL) {
continue;
}
if (id != -1 && ac->id != id) {
continue;
}
// Skip autocmds from invalid groups if passed.
if (group != 0 && ap->group != group) {
continue;
}
// Skip 'pattern' from invalid patterns if passed.
if (pattern_filter_count > 0) {
bool passed = false;
for (int j = 0; j < pattern_filter_count; j++) {
assert(j < AUCMD_MAX_PATTERNS);
assert(pattern_filters[j]);
char *pat = pattern_filters[j];
int patlen = (int)strlen(pat);
char pattern_buflocal[BUFLOCAL_PAT_LEN];
if (aupat_is_buflocal(pat, patlen)) {
aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
aupat_get_buflocal_nr(pat, patlen));
pat = pattern_buflocal;
}
if (strequal(ap->pat, pat)) {
passed = true;
break;
}
}
if (!passed) {
continue;
}
}
Dict autocmd_info = arena_dict(arena, 12);
if (ap->group != AUGROUP_DEFAULT) {
PUT_C(autocmd_info, "group", INTEGER_OBJ(ap->group));
PUT_C(autocmd_info, "group_name", CSTR_AS_OBJ(augroup_name(ap->group)));
}
if (ac->id > 0) {
PUT_C(autocmd_info, "id", INTEGER_OBJ(ac->id));
}
if (ac->desc != NULL) {
PUT_C(autocmd_info, "desc", CSTR_AS_OBJ(ac->desc));
}
if (ac->handler_cmd) {
PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->handler_cmd));
} else {
PUT_C(autocmd_info, "command", STRING_OBJ(STRING_INIT));
Callback *cb = &ac->handler_fn;
switch (cb->type) {
case kCallbackLua:
if (nlua_ref_is_function(cb->data.luaref)) {
PUT_C(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
}
break;
case kCallbackFuncref:
case kCallbackPartial:
PUT_C(autocmd_info, "callback", CSTR_AS_OBJ(callback_to_string(cb, arena)));
break;
case kCallbackNone:
abort();
}
}
PUT_C(autocmd_info, "pattern", CSTR_AS_OBJ(ap->pat));
PUT_C(autocmd_info, "event", CSTR_AS_OBJ(event_nr2name(event)));
PUT_C(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
if (ap->buflocal_nr) {
PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
PUT_C(autocmd_info, "buf", INTEGER_OBJ(ap->buflocal_nr));
PUT_C(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
} else {
PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
}
kvi_push(autocmd_list, DICT_OBJ(autocmd_info));
}
}
cleanup:
return arena_take_arraybuilder(arena, &autocmd_list);
}
/// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript
/// function _name_ string) or `command` (Ex command string).
///
/// Example using Lua callback:
///
/// ```lua
/// vim.api.nvim_create_autocmd({'BufEnter', 'BufWinEnter'}, {
/// pattern = {'*.c', '*.h'},
/// callback = function(ev)
/// print(string.format('event fired: %s', vim.inspect(ev)))
/// end
/// })
/// ```
///
/// Example using an Ex command as the handler:
///
/// ```lua
/// vim.api.nvim_create_autocmd({'BufEnter', 'BufWinEnter'}, {
/// pattern = {'*.c', '*.h'},
/// command = "echo 'Entering a C or C++ file'",
/// })
/// ```
///
/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
/// and "~" must be expanded explicitly:
///
/// ```lua
/// pattern = vim.fn.expand('~') .. '/some/path/*.py'
/// ```
///
/// @param event Event(s) that will trigger the handler (`callback` or `command`).
/// @param opts Options dict:
/// - buf (`integer?`) Buffer id for buffer-local autocommands |autocmd-buflocal|.
/// Not allowed with {pattern}.
/// - callback (`function|string?`) Lua function (or Vimscript function name, if string)
/// called when the event(s) is triggered. Lua callback can return |lua-truthy| to delete
/// the autocommand. Callback receives one argument, a table with keys: [event-args]()
/// - buf: (`number`) [<abuf>]
/// - data: (`any`) Arbitrary data passed from [nvim_exec_autocmds()] [event-data]()
/// - event: (`vim.api.keyset.events`) Name of the triggered event |autocmd-events|
/// - file: (`string`) [<afile>] (not expanded to a full path)
/// - group: (`number?`) Group id, if any
/// - id: (`number`) Autocommand id
/// - match: (`string`) [<amatch>] (expanded to a full path)
/// - command (string?) Vim command executed on event. Not allowed with {callback}.
/// - desc (`string?`) Description (for documentation and troubleshooting).
/// - group (`string|integer?`) Group name or id to match against.
/// - nested (`boolean?`, default: false) Run nested autocommands |autocmd-nested|.
/// - once (`boolean?`, default: false) Handle the event only once |autocmd-once|.
/// - pattern (`string|array?`) Pattern(s) to match literally |autocmd-pattern|.
///
/// @return Autocommand id (number)
/// @see |autocommand|
/// @see |nvim_del_autocmd()|
Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
Arena *arena, Error *err)
FUNC_API_SINCE(9)
{
int64_t autocmd_id = -1;
char *desc = NULL;
char *handler_cmd = NULL;
Callback handler_fn = CALLBACK_NONE;
Array event_array = unpack_string_or_array(event, "event", true, arena, err);
if (ERROR_SET(err)) {
goto cleanup;
}
VALIDATE_CON((!HAS_KEY(opts, create_autocmd, callback) || !HAS_KEY(opts, create_autocmd,
command)),
"callback", "command", {
goto cleanup;
});
if (HAS_KEY(opts, create_autocmd, callback)) {
// NOTE: We could accept callable tables, but that isn't common in the API.
Object *callback = &opts->callback;
switch (callback->type) {
case kObjectTypeLuaRef:
VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
goto cleanup;
});
VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
goto cleanup;
});
handler_fn.type = kCallbackLua;
handler_fn.data.luaref = callback->data.luaref;
callback->data.luaref = LUA_NOREF;
break;
case kObjectTypeString:
handler_fn.type = kCallbackFuncref;
handler_fn.data.funcref = string_to_cstr(callback->data.string);
break;
default:
VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
api_typename(callback->type), {
goto cleanup;
});
}
} else if (HAS_KEY(opts, create_autocmd, command)) {
handler_cmd = string_to_cstr(opts->command);
} else {
VALIDATE_R(false, "'command' or 'callback'", {
goto cleanup;
});
}
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
bool has_buf = HAS_KEY(opts, create_autocmd, buf) || HAS_KEY(opts, create_autocmd, buffer);
Buffer buf = HAS_KEY(opts, create_autocmd, buf) ? opts->buf : opts->buffer;
VALIDATE_CON((!HAS_KEY(opts, create_autocmd, buf) || !HAS_KEY(opts, create_autocmd, buffer)),
"buf", "buffer", {
goto cleanup;
});
VALIDATE_CON((!HAS_KEY(opts, create_autocmd, pattern) || !has_buf), "pattern", "buf", {
goto cleanup;
});
Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buf, buf, "*",
arena, err);
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;
}
VALIDATE_R((event_array.size > 0), "event", {
goto cleanup;
});
autocmd_id = next_autocmd_id++;
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, goto cleanup);
int retval;
FOREACH_ITEM(patterns, pat, {
WITH_SCRIPT_CONTEXT(channel_id, {
retval = autocmd_register(autocmd_id,
event_nr,
pat.data.string.data,
(int)pat.data.string.size,
au_group,
opts->once,
opts->nested,
desc,
handler_cmd,
&handler_fn);
});
if (retval == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to set autocmd");
goto cleanup;
}
})
});
cleanup:
if (handler_cmd) {
XFREE_CLEAR(handler_cmd);
} else {
callback_free(&handler_fn);
}
return autocmd_id;
}
/// Deletes an autocommand by id.
///
/// @param id Autocommand id returned by |nvim_create_autocmd()|
void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9)
{
VALIDATE_INT((id > 0), "autocmd id", id, {
return;
});
if (!autocmd_delete_id(id)) {
api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
}
}
/// Clears all autocommands matching the {opts} query. To delete autocmds see |nvim_del_autocmd()|.
///
/// @param opts Optional parameters:
/// - buf: (`integer?`) Select |autocmd-buflocal| autocommands. Not allowed with {pattern}.
/// - event: (`vim.api.keyset.events|vim.api.keyset.events[]?`)
/// Examples:
/// - event: "pat1"
/// - event: { "pat1" }
/// - event: { "pat1", "pat2", "pat3" }
/// - group: (`string|int?`) Group name or id.
/// - NOTE: If not given, matches autocmds *not* in any group.
/// - pattern: (`string|table?`) Filter by patterns (exact match). Not allowed with {buf}.
/// - Example: if you have `*.py` as that pattern for the autocmd, you must pass `*.py`
/// exactly to clear it. `test.py` will not match the pattern.
///
void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(9)
{
// TODO(tjdevries): Future improvements:
// - once: (boolean) - Only clear autocmds with once. See |autocmd-once|
// - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested|
// - group: Allow passing "*" or true or something like that to force doing all
// autocmds, regardless of their group.
Array event_array = unpack_string_or_array(opts->event, "event", false, arena, err);
if (ERROR_SET(err)) {
return;
}
bool has_buf = HAS_KEY(opts, clear_autocmds, buf) || HAS_KEY(opts, clear_autocmds, buffer);
int buf = HAS_KEY(opts, clear_autocmds, buf) ? opts->buf : opts->buffer;
VALIDATE_CON((!HAS_KEY(opts, clear_autocmds, buf) || !HAS_KEY(opts, clear_autocmds, buffer)),
"buf", "buffer", {
return;
});
VALIDATE_CON((!HAS_KEY(opts, clear_autocmds, pattern) || !has_buf), "pattern", "buf", {
return;
});
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
return;
}
// When we create the autocmds, we want to say that they are all matched, so that's *
// but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buf, buf, "",
arena, err);
if (ERROR_SET(err)) {
return;
}
// If we didn't pass any events, that means clear all events.
if (opts->event.type == kObjectTypeNil) {
FOR_ALL_AUEVENTS(event) {
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
if (!clear_autocmd(event, pat, au_group, err)) {
return;
}
});
}
} else {
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, return );
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
if (!clear_autocmd(event_nr, pat, au_group, err)) {
return;
}
});
});
}
}
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
///
/// ```lua
/// local id = vim.api.nvim_create_augroup('my.lsp.config', {
/// clear = false
/// })
/// ```
///
/// @param name Group name
/// @param opts Optional parameters:
/// - clear (`boolean?`, default: true) Clear existing commands in the group |autocmd-groups|.
/// @return Group id.
/// @see |autocmd-groups|
Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts,
Error *err)
FUNC_API_SINCE(9)
{
char *augroup_name = name.data;
bool clear_autocmds = GET_BOOL_OR_TRUE(opts, create_augroup, clear);
int augroup = -1;
WITH_SCRIPT_CONTEXT(channel_id, {
augroup = augroup_add(augroup_name);
if (augroup == AUGROUP_ERROR) {
api_set_error(err, kErrorTypeException, "Failed to set augroup");
return -1;
}
if (clear_autocmds) {
FOR_ALL_AUEVENTS(event) {
aucmd_del_for_event_and_group(event, augroup);
}
}
});
return augroup;
}
/// Delete an autocommand group by id.
///
/// To get a group id one can use |nvim_get_autocmds()|.
///
/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in
/// this group will also be deleted and cleared. This group will no longer exist.
/// @param id Group id.
/// @see |nvim_del_augroup_by_name()|
/// @see |nvim_create_augroup()|
void nvim_del_augroup_by_id(Integer id, Error *err)
FUNC_API_SINCE(9)
{
TRY_WRAP(err, {
char *name = id == 0 ? NULL : augroup_name((int)id);
augroup_del(name, false);
});
}
/// Delete an autocommand group by name.
///
/// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in
/// this group will also be deleted and cleared. This group will no longer exist.
/// @param name Group name.
/// @see |autocmd-groups|
void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
TRY_WRAP(err, {
augroup_del(name.data, false);
});
}
/// Executes {event} handlers matching the {opts} query, in the context of {buf} (if given). |autocmd-execute|
///
/// @param event Event(s) to execute.
/// @param opts Optional filters:
/// - buf (`integer?`) Buffer where the event is applied. |autocmd-buflocal| Not allowed with {pattern}.
/// - data (`any`): Arbitrary data passed to the callback. See |nvim_create_autocmd()|.
/// - group (`string|integer?`) Group name or id to match against. |autocmd-groups|.
/// - modeline (`boolean?`, default: true) Process the modeline after the autocommands
/// [<nomodeline>]. Ignored if `buf` is given.
/// - pattern (`string|array?`, default: current file name) |autocmd-pattern|. Not allowed with {buf}.
/// @see |:doautocmd|
void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(9)
{
int au_group = AUGROUP_ALL;
bool modeline = true;
buf_T *b = curbuf;
Object *data = NULL;
Array event_array = unpack_string_or_array(event, "event", true, arena, err);
if (ERROR_SET(err)) {
return;
}
switch (opts->group.type) {
case kObjectTypeNil:
break;
case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data);
VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
return;
});
break;
case kObjectTypeInteger:
au_group = (int)opts->group.data.integer;
char *name = au_group == 0 ? NULL : augroup_name(au_group);
VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
return;
});
break;
default:
VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
return;
});
}
bool has_buf = HAS_KEY(opts, exec_autocmds, buf) || HAS_KEY(opts, exec_autocmds, buffer);
Buffer buf = HAS_KEY(opts, exec_autocmds, buf) ? opts->buf : opts->buffer;
VALIDATE_CON((!HAS_KEY(opts, exec_autocmds, buf) || !HAS_KEY(opts, exec_autocmds, buffer)),
"buf", "buffer", {
return;
});
if (has_buf) {
VALIDATE_CON((!HAS_KEY(opts, exec_autocmds, pattern)), "pattern", "buf", {
return;
});
b = find_buffer_by_handle(buf, err);
if (ERROR_SET(err)) {
return;
}
}
Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buf, buf, "",
arena, err);
if (ERROR_SET(err)) {
return;
}
if (HAS_KEY(opts, exec_autocmds, data)) {
data = &opts->data;
}
modeline = GET_BOOL_OR_TRUE(opts, exec_autocmds, modeline);
bool did_aucmd = false;
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, return )
FOREACH_ITEM(patterns, pat, {
char *fname = !has_buf ? pat.data.string.data : NULL;
did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, b, NULL, data,
has_buf);
})
})
if (did_aucmd && modeline && !has_buf) {
do_modelines(0);
}
}
static Array unpack_string_or_array(Object v, char *k, bool required, Arena *arena, Error *err)
{
if (v.type == kObjectTypeString) {
Array arr = arena_array(arena, 1);
ADD_C(arr, v);
return arr;
} else if (v.type == kObjectTypeArray) {
if (!check_string_array(v.data.array, k, true, err)) {
return (Array)ARRAY_DICT_INIT;
}
return v.data.array;
} else {
VALIDATE_EXP(!required && v.type == kObjectTypeNil, k, "Array or String", api_typename(v.type),
{
return (Array)ARRAY_DICT_INIT;
});
}
return (Array)ARRAY_DICT_INIT;
}
// Returns AUGROUP_ERROR if there was a problem with {group}
static int get_augroup_from_object(Object group, Error *err)
{
int au_group = AUGROUP_ERROR;
switch (group.type) {
case kObjectTypeNil:
return AUGROUP_DEFAULT;
case kObjectTypeString:
au_group = augroup_find(group.data.string.data);
VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
return AUGROUP_ERROR;
});
return au_group;
case kObjectTypeInteger:
au_group = (int)group.data.integer;
char *name = au_group == 0 ? NULL : augroup_name(au_group);
VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
return AUGROUP_ERROR;
});
return au_group;
default:
VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
return AUGROUP_ERROR;
});
}
}
static Array get_patterns_from_pattern_or_buf(Object pattern, bool has_buf, Buffer buf,
char *fallback, Arena *arena, Error *err)
{
ArrayBuilder patterns = ARRAY_DICT_INIT;
kvi_init(patterns);
if (pattern.type != kObjectTypeNil) {
if (pattern.type == kObjectTypeString) {
const char *pat = pattern.data.string.data;
size_t patlen = aucmd_span_pattern(pat, &pat);
while (patlen) {
kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
patlen = aucmd_span_pattern(pat + patlen, &pat);
}
} else if (pattern.type == kObjectTypeArray) {
if (!check_string_array(pattern.data.array, "pattern", true, err)) {
return (Array)ARRAY_DICT_INIT;
}
Array array = pattern.data.array;
FOREACH_ITEM(array, entry, {
const char *pat = entry.data.string.data;
size_t patlen = aucmd_span_pattern(pat, &pat);
while (patlen) {
kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
patlen = aucmd_span_pattern(pat + patlen, &pat);
}
})
} else {
VALIDATE_EXP(false, "pattern", "String or Table", api_typename(pattern.type), {
return (Array)ARRAY_DICT_INIT;
});
}
} else if (has_buf) {
buf_T *b = find_buffer_by_handle(buf, err);
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
kvi_push(patterns, STRING_OBJ(arena_printf(arena, "<buffer=%d>", (int)b->handle)));
} else if (fallback) {
kvi_push(patterns, CSTR_AS_OBJ(fallback));
}
return arena_take_arraybuilder(arena, &patterns);
}
static bool clear_autocmd(event_T event, char *pat, int au_group, Error *err)
{
if (do_autocmd_event(event, pat, false, false, "", true, au_group) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
return false;
}
return true;
}
#undef GET_ONE_EVENT