neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

autocmd.c (28993B)


      1 #include <assert.h>
      2 #include <lauxlib.h>
      3 #include <stdbool.h>
      4 #include <stdint.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 
      8 #include "klib/kvec.h"
      9 #include "nvim/api/autocmd.h"
     10 #include "nvim/api/keysets_defs.h"
     11 #include "nvim/api/private/defs.h"
     12 #include "nvim/api/private/dispatch.h"
     13 #include "nvim/api/private/helpers.h"
     14 #include "nvim/api/private/validate.h"
     15 #include "nvim/autocmd.h"
     16 #include "nvim/autocmd_defs.h"
     17 #include "nvim/buffer.h"
     18 #include "nvim/buffer_defs.h"
     19 #include "nvim/eval/typval.h"
     20 #include "nvim/eval/typval_defs.h"
     21 #include "nvim/ex_cmds_defs.h"
     22 #include "nvim/globals.h"
     23 #include "nvim/lua/executor.h"
     24 #include "nvim/memory.h"
     25 #include "nvim/memory_defs.h"
     26 #include "nvim/strings.h"
     27 #include "nvim/types_defs.h"
     28 #include "nvim/vim_defs.h"
     29 
     30 #include "api/autocmd.c.generated.h"
     31 
     32 #define AUCMD_MAX_PATTERNS 256
     33 
     34 // Copy string or array of strings into an empty array.
     35 // Get the event number, unless it is an error. Then do `or_else`.
     36 #define GET_ONE_EVENT(event_nr, event_str, or_else) \
     37  event_T event_nr = \
     38    event_name2nr_str(event_str.data.string); \
     39  VALIDATE_S((event_nr < NUM_EVENTS), "event", event_str.data.string.data, { \
     40    or_else; \
     41  });
     42 
     43 // ID for associating autocmds created via nvim_create_autocmd
     44 // Used to delete autocmds from nvim_del_autocmd
     45 static int64_t next_autocmd_id = 1;
     46 
     47 /// Get all autocommands that match the corresponding {opts}.
     48 ///
     49 /// These examples will get autocommands matching ALL the given criteria:
     50 ///
     51 /// ```lua
     52 /// -- Matches all criteria
     53 /// autocommands = vim.api.nvim_get_autocmds({
     54 ///   group = 'MyGroup',
     55 ///   event = {'BufEnter', 'BufWinEnter'},
     56 ///   pattern = {'*.c', '*.h'}
     57 /// })
     58 ///
     59 /// -- All commands from one group
     60 /// autocommands = vim.api.nvim_get_autocmds({
     61 ///   group = 'MyGroup',
     62 /// })
     63 /// ```
     64 ///
     65 /// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
     66 /// match any combination of them.
     67 ///
     68 /// @param opts Dict with at least one of the following:
     69 ///             - buffer: (integer) Buffer number or list of buffer numbers for buffer local autocommands
     70 ///             |autocmd-buflocal|. Cannot be used with {pattern}
     71 ///             - event: (vim.api.keyset.events|vim.api.keyset.events[])
     72 ///               event or events to match against |autocmd-events|.
     73 ///             - id: (integer) Autocommand ID to match.
     74 ///             - group: (string|table) the autocommand group name or id to match against.
     75 ///             - pattern: (string|table) pattern or patterns to match against |autocmd-pattern|.
     76 ///             Cannot be used with {buffer}
     77 /// @return Array of autocommands matching the criteria, with each item
     78 ///             containing the following fields:
     79 ///             - buffer: (integer) the buffer number.
     80 ///             - buflocal: (boolean) true if the autocommand is buffer local.
     81 ///             - command: (string) the autocommand command. Note: this will be empty if a callback is set.
     82 ///             - callback: (function|string|nil): Lua function or name of a Vim script function
     83 ///               which is executed when this autocommand is triggered.
     84 ///             - desc: (string) the autocommand description.
     85 ///             - event: (vim.api.keyset.events) the autocommand event.
     86 ///             - id: (integer) the autocommand id (only when defined with the API).
     87 ///             - group: (integer) the autocommand group id.
     88 ///             - group_name: (string) the autocommand group name.
     89 ///             - once: (boolean) whether the autocommand is only run once.
     90 ///             - pattern: (string) the autocommand pattern.
     91 ///               If the autocommand is buffer local |autocmd-buffer-local|:
     92 ArrayOf(DictAs(get_autocmds__ret)) nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena,
     93                                                     Error *err)
     94  FUNC_API_SINCE(9)
     95 {
     96  ArrayBuilder autocmd_list = KV_INITIAL_VALUE;
     97  kvi_init(autocmd_list);
     98  char *pattern_filters[AUCMD_MAX_PATTERNS];
     99 
    100  Array buffers = ARRAY_DICT_INIT;
    101 
    102  bool event_set[NUM_EVENTS] = { false };
    103  bool check_event = false;
    104 
    105  int group = 0;
    106 
    107  switch (opts->group.type) {
    108  case kObjectTypeNil:
    109    break;
    110  case kObjectTypeString:
    111    group = augroup_find(opts->group.data.string.data);
    112    VALIDATE_S((group >= 0), "group", opts->group.data.string.data, {
    113      goto cleanup;
    114    });
    115    break;
    116  case kObjectTypeInteger:
    117    group = (int)opts->group.data.integer;
    118    char *name = group == 0 ? NULL : augroup_name(group);
    119    VALIDATE_INT(augroup_exists(name), "group", opts->group.data.integer, {
    120      goto cleanup;
    121    });
    122    break;
    123  default:
    124    VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
    125      goto cleanup;
    126    });
    127  }
    128 
    129  int id = (HAS_KEY(opts, get_autocmds, id)) ? (int)opts->id : -1;
    130 
    131  if (HAS_KEY(opts, get_autocmds, event)) {
    132    check_event = true;
    133 
    134    Object v = opts->event;
    135    if (v.type == kObjectTypeString) {
    136      GET_ONE_EVENT(event_nr, v, goto cleanup);
    137      event_set[event_nr] = true;
    138    } else if (v.type == kObjectTypeArray) {
    139      FOREACH_ITEM(v.data.array, event_v, {
    140        VALIDATE_T("event item", kObjectTypeString, event_v.type, {
    141          goto cleanup;
    142        });
    143 
    144        GET_ONE_EVENT(event_nr, event_v, goto cleanup);
    145        event_set[event_nr] = true;
    146      })
    147    } else {
    148      VALIDATE_EXP(false, "event", "String or Array", NULL, {
    149        goto cleanup;
    150      });
    151    }
    152  }
    153 
    154  VALIDATE((!HAS_KEY(opts, get_autocmds, pattern) || !HAS_KEY(opts, get_autocmds, buffer)),
    155           "%s", "Cannot use both 'pattern' and 'buffer'", {
    156    goto cleanup;
    157  });
    158 
    159  int pattern_filter_count = 0;
    160  if (HAS_KEY(opts, get_autocmds, pattern)) {
    161    Object v = opts->pattern;
    162    if (v.type == kObjectTypeString) {
    163      pattern_filters[pattern_filter_count] = v.data.string.data;
    164      pattern_filter_count += 1;
    165    } else if (v.type == kObjectTypeArray) {
    166      VALIDATE((v.data.array.size <= AUCMD_MAX_PATTERNS),
    167               "Too many patterns (maximum of %d)", AUCMD_MAX_PATTERNS, {
    168        goto cleanup;
    169      });
    170 
    171      FOREACH_ITEM(v.data.array, item, {
    172        VALIDATE_T("pattern", kObjectTypeString, item.type, {
    173          goto cleanup;
    174        });
    175 
    176        pattern_filters[pattern_filter_count] = item.data.string.data;
    177        pattern_filter_count += 1;
    178      });
    179    } else {
    180      VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
    181        goto cleanup;
    182      });
    183    }
    184  }
    185 
    186  if (opts->buffer.type == kObjectTypeInteger || opts->buffer.type == kObjectTypeBuffer) {
    187    buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err);
    188    if (ERROR_SET(err)) {
    189      goto cleanup;
    190    }
    191 
    192    String pat = arena_printf(arena, "<buffer=%d>", (int)buf->handle);
    193    buffers = arena_array(arena, 1);
    194    ADD_C(buffers, STRING_OBJ(pat));
    195  } else if (opts->buffer.type == kObjectTypeArray) {
    196    if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
    197      api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
    198                    AUCMD_MAX_PATTERNS);
    199      goto cleanup;
    200    }
    201 
    202    buffers = arena_array(arena, kv_size(opts->buffer.data.array));
    203    FOREACH_ITEM(opts->buffer.data.array, bufnr, {
    204      VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
    205                   "buffer", "Integer", api_typename(bufnr.type), {
    206        goto cleanup;
    207      });
    208 
    209      buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
    210      if (ERROR_SET(err)) {
    211        goto cleanup;
    212      }
    213 
    214      ADD_C(buffers, STRING_OBJ(arena_printf(arena, "<buffer=%d>", (int)buf->handle)));
    215    });
    216  } else if (HAS_KEY(opts, get_autocmds, buffer)) {
    217    VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
    218      goto cleanup;
    219    });
    220  }
    221 
    222  FOREACH_ITEM(buffers, bufnr, {
    223    pattern_filters[pattern_filter_count] = bufnr.data.string.data;
    224    pattern_filter_count += 1;
    225  });
    226 
    227  FOR_ALL_AUEVENTS(event) {
    228    if (check_event && !event_set[event]) {
    229      continue;
    230    }
    231 
    232    AutoCmdVec *acs = au_get_autocmds_for_event(event);
    233    for (size_t i = 0; i < kv_size(*acs); i++) {
    234      AutoCmd *const ac = &kv_A(*acs, i);
    235      AutoPat *const ap = ac->pat;
    236 
    237      if (ap == NULL) {
    238        continue;
    239      }
    240 
    241      if (id != -1 && ac->id != id) {
    242        continue;
    243      }
    244 
    245      // Skip autocmds from invalid groups if passed.
    246      if (group != 0 && ap->group != group) {
    247        continue;
    248      }
    249 
    250      // Skip 'pattern' from invalid patterns if passed.
    251      if (pattern_filter_count > 0) {
    252        bool passed = false;
    253        for (int j = 0; j < pattern_filter_count; j++) {
    254          assert(j < AUCMD_MAX_PATTERNS);
    255          assert(pattern_filters[j]);
    256 
    257          char *pat = pattern_filters[j];
    258          int patlen = (int)strlen(pat);
    259 
    260          char pattern_buflocal[BUFLOCAL_PAT_LEN];
    261          if (aupat_is_buflocal(pat, patlen)) {
    262            aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
    263                                         aupat_get_buflocal_nr(pat, patlen));
    264            pat = pattern_buflocal;
    265          }
    266 
    267          if (strequal(ap->pat, pat)) {
    268            passed = true;
    269            break;
    270          }
    271        }
    272 
    273        if (!passed) {
    274          continue;
    275        }
    276      }
    277 
    278      Dict autocmd_info = arena_dict(arena, 11);
    279 
    280      if (ap->group != AUGROUP_DEFAULT) {
    281        PUT_C(autocmd_info, "group", INTEGER_OBJ(ap->group));
    282        PUT_C(autocmd_info, "group_name", CSTR_AS_OBJ(augroup_name(ap->group)));
    283      }
    284 
    285      if (ac->id > 0) {
    286        PUT_C(autocmd_info, "id", INTEGER_OBJ(ac->id));
    287      }
    288 
    289      if (ac->desc != NULL) {
    290        PUT_C(autocmd_info, "desc", CSTR_AS_OBJ(ac->desc));
    291      }
    292 
    293      if (ac->handler_cmd) {
    294        PUT_C(autocmd_info, "command", CSTR_AS_OBJ(ac->handler_cmd));
    295      } else {
    296        PUT_C(autocmd_info, "command", STRING_OBJ(STRING_INIT));
    297 
    298        Callback *cb = &ac->handler_fn;
    299        switch (cb->type) {
    300        case kCallbackLua:
    301          if (nlua_ref_is_function(cb->data.luaref)) {
    302            PUT_C(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
    303          }
    304          break;
    305        case kCallbackFuncref:
    306        case kCallbackPartial:
    307          PUT_C(autocmd_info, "callback", CSTR_AS_OBJ(callback_to_string(cb, arena)));
    308          break;
    309        case kCallbackNone:
    310          abort();
    311        }
    312      }
    313 
    314      PUT_C(autocmd_info, "pattern", CSTR_AS_OBJ(ap->pat));
    315      PUT_C(autocmd_info, "event", CSTR_AS_OBJ(event_nr2name(event)));
    316      PUT_C(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
    317 
    318      if (ap->buflocal_nr) {
    319        PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
    320        PUT_C(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
    321      } else {
    322        PUT_C(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
    323      }
    324 
    325      kvi_push(autocmd_list, DICT_OBJ(autocmd_info));
    326    }
    327  }
    328 
    329 cleanup:
    330  return arena_take_arraybuilder(arena, &autocmd_list);
    331 }
    332 
    333 /// Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript
    334 /// function _name_ string) or `command` (Ex command string).
    335 ///
    336 /// Example using Lua callback:
    337 ///
    338 /// ```lua
    339 /// vim.api.nvim_create_autocmd({'BufEnter', 'BufWinEnter'}, {
    340 ///   pattern = {'*.c', '*.h'},
    341 ///   callback = function(ev)
    342 ///     print(string.format('event fired: %s', vim.inspect(ev)))
    343 ///   end
    344 /// })
    345 /// ```
    346 ///
    347 /// Example using an Ex command as the handler:
    348 ///
    349 /// ```lua
    350 /// vim.api.nvim_create_autocmd({'BufEnter', 'BufWinEnter'}, {
    351 ///   pattern = {'*.c', '*.h'},
    352 ///   command = "echo 'Entering a C or C++ file'",
    353 /// })
    354 /// ```
    355 ///
    356 /// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
    357 /// and "~" must be expanded explicitly:
    358 ///
    359 /// ```lua
    360 /// pattern = vim.fn.expand('~') .. '/some/path/*.py'
    361 /// ```
    362 ///
    363 /// @param event Event(s) that will trigger the handler (`callback` or `command`).
    364 /// @param opts Options dict:
    365 ///             - group (string|integer) optional: autocommand group name or id to match against.
    366 ///             - pattern (string|array) optional: pattern(s) to match literally |autocmd-pattern|.
    367 ///             - buffer (integer) optional: buffer number for buffer-local autocommands
    368 ///             |autocmd-buflocal|. Cannot be used with {pattern}.
    369 ///             - desc (string) optional: description (for documentation and troubleshooting).
    370 ///             - callback (function|string) optional: Lua function (or Vimscript function name, if
    371 ///             string) called when the event(s) is triggered. Lua callback can return a truthy
    372 ///             value (not `false` or `nil`) to delete the autocommand, and receives one argument, a
    373 ///             table with these keys: [event-args]()
    374 ///                 - id: (number) autocommand id
    375 ///                 - event: (vim.api.keyset.events) name of the triggered event |autocmd-events|
    376 ///                 - group: (number|nil) autocommand group id, if any
    377 ///                 - file: (string) [<afile>] (not expanded to a full path)
    378 ///                 - match: (string) [<amatch>] (expanded to a full path)
    379 ///                 - buf: (number) [<abuf>]
    380 ///                 - data: (any) arbitrary data passed from [nvim_exec_autocmds()] [event-data]()
    381 ///             - command (string) optional: Vim command to execute on event. Cannot be used with
    382 ///             {callback}
    383 ///             - once (boolean) optional: defaults to false. Run the autocommand
    384 ///             only once |autocmd-once|.
    385 ///             - nested (boolean) optional: defaults to false. Run nested
    386 ///             autocommands |autocmd-nested|.
    387 ///
    388 /// @return Autocommand id (number)
    389 /// @see |autocommand|
    390 /// @see |nvim_del_autocmd()|
    391 Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts,
    392                            Arena *arena, Error *err)
    393  FUNC_API_SINCE(9)
    394 {
    395  int64_t autocmd_id = -1;
    396  char *desc = NULL;
    397  char *handler_cmd = NULL;
    398  Callback handler_fn = CALLBACK_NONE;
    399 
    400  Array event_array = unpack_string_or_array(event, "event", true, arena, err);
    401  if (ERROR_SET(err)) {
    402    goto cleanup;
    403  }
    404 
    405  VALIDATE((!HAS_KEY(opts, create_autocmd, callback) || !HAS_KEY(opts, create_autocmd, command)),
    406           "%s", "Cannot use both 'callback' and 'command'", {
    407    goto cleanup;
    408  });
    409 
    410  if (HAS_KEY(opts, create_autocmd, callback)) {
    411    // NOTE: We could accept callable tables, but that isn't common in the API.
    412 
    413    Object *callback = &opts->callback;
    414    switch (callback->type) {
    415    case kObjectTypeLuaRef:
    416      VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
    417        goto cleanup;
    418      });
    419      VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
    420        goto cleanup;
    421      });
    422 
    423      handler_fn.type = kCallbackLua;
    424      handler_fn.data.luaref = callback->data.luaref;
    425      callback->data.luaref = LUA_NOREF;
    426      break;
    427    case kObjectTypeString:
    428      handler_fn.type = kCallbackFuncref;
    429      handler_fn.data.funcref = string_to_cstr(callback->data.string);
    430      break;
    431    default:
    432      VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
    433                   api_typename(callback->type), {
    434        goto cleanup;
    435      });
    436    }
    437  } else if (HAS_KEY(opts, create_autocmd, command)) {
    438    handler_cmd = string_to_cstr(opts->command);
    439  } else {
    440    VALIDATE(false, "%s", "Required: 'command' or 'callback'", {
    441      goto cleanup;
    442    });
    443  }
    444 
    445  int au_group = get_augroup_from_object(opts->group, err);
    446  if (au_group == AUGROUP_ERROR) {
    447    goto cleanup;
    448  }
    449 
    450  bool has_buffer = HAS_KEY(opts, create_autocmd, buffer);
    451 
    452  VALIDATE((!HAS_KEY(opts, create_autocmd, pattern) || !has_buffer),
    453           "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
    454    goto cleanup;
    455  });
    456 
    457  Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buffer, opts->buffer, "*",
    458                                                    arena, err);
    459  if (ERROR_SET(err)) {
    460    goto cleanup;
    461  }
    462 
    463  if (HAS_KEY(opts, create_autocmd, desc)) {
    464    desc = opts->desc.data;
    465  }
    466 
    467  VALIDATE_R((event_array.size > 0), "event", {
    468    goto cleanup;
    469  });
    470 
    471  autocmd_id = next_autocmd_id++;
    472  FOREACH_ITEM(event_array, event_str, {
    473    GET_ONE_EVENT(event_nr, event_str, goto cleanup);
    474 
    475    int retval;
    476 
    477    FOREACH_ITEM(patterns, pat, {
    478      WITH_SCRIPT_CONTEXT(channel_id, {
    479        retval = autocmd_register(autocmd_id,
    480                                  event_nr,
    481                                  pat.data.string.data,
    482                                  (int)pat.data.string.size,
    483                                  au_group,
    484                                  opts->once,
    485                                  opts->nested,
    486                                  desc,
    487                                  handler_cmd,
    488                                  &handler_fn);
    489      });
    490 
    491      if (retval == FAIL) {
    492        api_set_error(err, kErrorTypeException, "Failed to set autocmd");
    493        goto cleanup;
    494      }
    495    })
    496  });
    497 
    498 cleanup:
    499  if (handler_cmd) {
    500    XFREE_CLEAR(handler_cmd);
    501  } else {
    502    callback_free(&handler_fn);
    503  }
    504 
    505  return autocmd_id;
    506 }
    507 
    508 /// Deletes an autocommand by id.
    509 ///
    510 /// @param id Integer Autocommand id returned by |nvim_create_autocmd()|
    511 void nvim_del_autocmd(Integer id, Error *err)
    512  FUNC_API_SINCE(9)
    513 {
    514  VALIDATE_INT((id > 0), "autocmd id", id, {
    515    return;
    516  });
    517  if (!autocmd_delete_id(id)) {
    518    api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
    519  }
    520 }
    521 
    522 /// Clears all autocommands selected by {opts}. To delete autocmds see |nvim_del_autocmd()|.
    523 ///
    524 /// @param opts Parameters
    525 ///         - event: (vim.api.keyset.events|vim.api.keyset.events[])
    526 ///              Examples:
    527 ///              - event: "pat1"
    528 ///              - event: { "pat1" }
    529 ///              - event: { "pat1", "pat2", "pat3" }
    530 ///         - pattern: (string|table)
    531 ///             - pattern or patterns to match exactly.
    532 ///                 - For example, if you have `*.py` as that pattern for the autocmd,
    533 ///                   you must pass `*.py` exactly to clear it. `test.py` will not
    534 ///                   match the pattern.
    535 ///             - defaults to clearing all patterns.
    536 ///             - NOTE: Cannot be used with {buffer}
    537 ///         - buffer: (bufnr)
    538 ///             - clear only |autocmd-buflocal| autocommands.
    539 ///             - NOTE: Cannot be used with {pattern}
    540 ///         - group: (string|int) The augroup name or id.
    541 ///             - NOTE: If not passed, will only delete autocmds *not* in any group.
    542 ///
    543 void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Arena *arena, Error *err)
    544  FUNC_API_SINCE(9)
    545 {
    546  // TODO(tjdevries): Future improvements:
    547  //        - once: (boolean) - Only clear autocmds with once. See |autocmd-once|
    548  //        - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested|
    549  //        - group: Allow passing "*" or true or something like that to force doing all
    550  //        autocmds, regardless of their group.
    551 
    552  Array event_array = unpack_string_or_array(opts->event, "event", false, arena, err);
    553  if (ERROR_SET(err)) {
    554    return;
    555  }
    556 
    557  bool has_buffer = HAS_KEY(opts, clear_autocmds, buffer);
    558 
    559  VALIDATE((!HAS_KEY(opts, clear_autocmds, pattern) || !has_buffer),
    560           "%s", "Cannot use both 'pattern' and 'buffer'", {
    561    return;
    562  });
    563 
    564  int au_group = get_augroup_from_object(opts->group, err);
    565  if (au_group == AUGROUP_ERROR) {
    566    return;
    567  }
    568 
    569  // When we create the autocmds, we want to say that they are all matched, so that's *
    570  // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
    571  Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buffer, opts->buffer, "",
    572                                                    arena, err);
    573  if (ERROR_SET(err)) {
    574    return;
    575  }
    576 
    577  // If we didn't pass any events, that means clear all events.
    578  if (event_array.size == 0) {
    579    FOR_ALL_AUEVENTS(event) {
    580      FOREACH_ITEM(patterns, pat_object, {
    581        char *pat = pat_object.data.string.data;
    582        if (!clear_autocmd(event, pat, au_group, err)) {
    583          return;
    584        }
    585      });
    586    }
    587  } else {
    588    FOREACH_ITEM(event_array, event_str, {
    589      GET_ONE_EVENT(event_nr, event_str, return );
    590 
    591      FOREACH_ITEM(patterns, pat_object, {
    592        char *pat = pat_object.data.string.data;
    593        if (!clear_autocmd(event_nr, pat, au_group, err)) {
    594          return;
    595        }
    596      });
    597    });
    598  }
    599 }
    600 
    601 /// Create or get an autocommand group |autocmd-groups|.
    602 ///
    603 /// To get an existing group id, do:
    604 ///
    605 /// ```lua
    606 /// local id = vim.api.nvim_create_augroup('my.lsp.config', {
    607 ///     clear = false
    608 /// })
    609 /// ```
    610 ///
    611 /// @param name String: The name of the group
    612 /// @param opts Dict Parameters
    613 ///                 - clear (bool) optional: defaults to true. Clear existing
    614 ///                 commands if the group already exists |autocmd-groups|.
    615 /// @return Integer id of the created group.
    616 /// @see |autocmd-groups|
    617 Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts,
    618                            Error *err)
    619  FUNC_API_SINCE(9)
    620 {
    621  char *augroup_name = name.data;
    622  bool clear_autocmds = GET_BOOL_OR_TRUE(opts, create_augroup, clear);
    623 
    624  int augroup = -1;
    625  WITH_SCRIPT_CONTEXT(channel_id, {
    626    augroup = augroup_add(augroup_name);
    627    if (augroup == AUGROUP_ERROR) {
    628      api_set_error(err, kErrorTypeException, "Failed to set augroup");
    629      return -1;
    630    }
    631 
    632    if (clear_autocmds) {
    633      FOR_ALL_AUEVENTS(event) {
    634        aucmd_del_for_event_and_group(event, augroup);
    635      }
    636    }
    637  });
    638 
    639  return augroup;
    640 }
    641 
    642 /// Delete an autocommand group by id.
    643 ///
    644 /// To get a group id one can use |nvim_get_autocmds()|.
    645 ///
    646 /// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in
    647 /// this group will also be deleted and cleared. This group will no longer exist.
    648 /// @param id Integer The id of the group.
    649 /// @see |nvim_del_augroup_by_name()|
    650 /// @see |nvim_create_augroup()|
    651 void nvim_del_augroup_by_id(Integer id, Error *err)
    652  FUNC_API_SINCE(9)
    653 {
    654  TRY_WRAP(err, {
    655    char *name = id == 0 ? NULL : augroup_name((int)id);
    656    augroup_del(name, false);
    657  });
    658 }
    659 
    660 /// Delete an autocommand group by name.
    661 ///
    662 /// NOTE: behavior differs from |:augroup-delete|. When deleting a group, autocommands contained in
    663 /// this group will also be deleted and cleared. This group will no longer exist.
    664 /// @param name String The name of the group.
    665 /// @see |autocmd-groups|
    666 void nvim_del_augroup_by_name(String name, Error *err)
    667  FUNC_API_SINCE(9)
    668 {
    669  TRY_WRAP(err, {
    670    augroup_del(name.data, false);
    671  });
    672 }
    673 
    674 /// Execute all autocommands for {event} that match the corresponding
    675 ///  {opts} |autocmd-execute|.
    676 /// @param event The event or events to execute
    677 /// @param opts Dict of autocommand options:
    678 ///             - group (string|integer) optional: the autocommand group name or
    679 ///             id to match against. |autocmd-groups|.
    680 ///             - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used
    681 ///             with {buffer}.
    682 ///             - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with
    683 ///             {pattern}.
    684 ///             - modeline (bool) optional: defaults to true. Process the
    685 ///             modeline after the autocommands [<nomodeline>].
    686 ///             - data (any): arbitrary data to send to the autocommand callback. See
    687 ///             |nvim_create_autocmd()| for details.
    688 /// @see |:doautocmd|
    689 void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Arena *arena, Error *err)
    690  FUNC_API_SINCE(9)
    691 {
    692  int au_group = AUGROUP_ALL;
    693  bool modeline = true;
    694 
    695  buf_T *buf = curbuf;
    696 
    697  Object *data = NULL;
    698 
    699  Array event_array = unpack_string_or_array(event, "event", true, arena, err);
    700  if (ERROR_SET(err)) {
    701    return;
    702  }
    703 
    704  switch (opts->group.type) {
    705  case kObjectTypeNil:
    706    break;
    707  case kObjectTypeString:
    708    au_group = augroup_find(opts->group.data.string.data);
    709    VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
    710      return;
    711    });
    712    break;
    713  case kObjectTypeInteger:
    714    au_group = (int)opts->group.data.integer;
    715    char *name = au_group == 0 ? NULL : augroup_name(au_group);
    716    VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
    717      return;
    718    });
    719    break;
    720  default:
    721    VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
    722      return;
    723    });
    724  }
    725 
    726  bool has_buffer = false;
    727  if (HAS_KEY(opts, exec_autocmds, buffer)) {
    728    VALIDATE((!HAS_KEY(opts, exec_autocmds, pattern)),
    729             "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
    730      return;
    731    });
    732 
    733    has_buffer = true;
    734    buf = find_buffer_by_handle(opts->buffer, err);
    735 
    736    if (ERROR_SET(err)) {
    737      return;
    738    }
    739  }
    740 
    741  Array patterns = get_patterns_from_pattern_or_buf(opts->pattern, has_buffer, opts->buffer, "",
    742                                                    arena, err);
    743  if (ERROR_SET(err)) {
    744    return;
    745  }
    746 
    747  if (HAS_KEY(opts, exec_autocmds, data)) {
    748    data = &opts->data;
    749  }
    750 
    751  modeline = GET_BOOL_OR_TRUE(opts, exec_autocmds, modeline);
    752 
    753  bool did_aucmd = false;
    754  FOREACH_ITEM(event_array, event_str, {
    755    GET_ONE_EVENT(event_nr, event_str, return )
    756 
    757    FOREACH_ITEM(patterns, pat, {
    758      char *fname = !has_buffer ? pat.data.string.data : NULL;
    759      did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
    760    })
    761  })
    762 
    763  if (did_aucmd && modeline) {
    764    do_modelines(0);
    765  }
    766 }
    767 
    768 static Array unpack_string_or_array(Object v, char *k, bool required, Arena *arena, Error *err)
    769 {
    770  if (v.type == kObjectTypeString) {
    771    Array arr = arena_array(arena, 1);
    772    ADD_C(arr, v);
    773    return arr;
    774  } else if (v.type == kObjectTypeArray) {
    775    if (!check_string_array(v.data.array, k, true, err)) {
    776      return (Array)ARRAY_DICT_INIT;
    777    }
    778    return v.data.array;
    779  } else {
    780    VALIDATE_EXP(!required, k, "Array or String", api_typename(v.type), {
    781      return (Array)ARRAY_DICT_INIT;
    782    });
    783  }
    784 
    785  return (Array)ARRAY_DICT_INIT;
    786 }
    787 
    788 // Returns AUGROUP_ERROR if there was a problem with {group}
    789 static int get_augroup_from_object(Object group, Error *err)
    790 {
    791  int au_group = AUGROUP_ERROR;
    792 
    793  switch (group.type) {
    794  case kObjectTypeNil:
    795    return AUGROUP_DEFAULT;
    796  case kObjectTypeString:
    797    au_group = augroup_find(group.data.string.data);
    798    VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
    799      return AUGROUP_ERROR;
    800    });
    801 
    802    return au_group;
    803  case kObjectTypeInteger:
    804    au_group = (int)group.data.integer;
    805    char *name = au_group == 0 ? NULL : augroup_name(au_group);
    806    VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
    807      return AUGROUP_ERROR;
    808    });
    809    return au_group;
    810  default:
    811    VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
    812      return AUGROUP_ERROR;
    813    });
    814  }
    815 }
    816 
    817 static Array get_patterns_from_pattern_or_buf(Object pattern, bool has_buffer, Buffer buffer,
    818                                              char *fallback, Arena *arena, Error *err)
    819 {
    820  ArrayBuilder patterns = ARRAY_DICT_INIT;
    821  kvi_init(patterns);
    822 
    823  if (pattern.type != kObjectTypeNil) {
    824    if (pattern.type == kObjectTypeString) {
    825      const char *pat = pattern.data.string.data;
    826      size_t patlen = aucmd_span_pattern(pat, &pat);
    827      while (patlen) {
    828        kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
    829        patlen = aucmd_span_pattern(pat + patlen, &pat);
    830      }
    831    } else if (pattern.type == kObjectTypeArray) {
    832      if (!check_string_array(pattern.data.array, "pattern", true, err)) {
    833        return (Array)ARRAY_DICT_INIT;
    834      }
    835 
    836      Array array = pattern.data.array;
    837      FOREACH_ITEM(array, entry, {
    838        const char *pat = entry.data.string.data;
    839        size_t patlen = aucmd_span_pattern(pat, &pat);
    840        while (patlen) {
    841          kvi_push(patterns, CBUF_TO_ARENA_OBJ(arena, pat, patlen));
    842          patlen = aucmd_span_pattern(pat + patlen, &pat);
    843        }
    844      })
    845    } else {
    846      VALIDATE_EXP(false, "pattern", "String or Table", api_typename(pattern.type), {
    847        return (Array)ARRAY_DICT_INIT;
    848      });
    849    }
    850  } else if (has_buffer) {
    851    buf_T *buf = find_buffer_by_handle(buffer, err);
    852    if (ERROR_SET(err)) {
    853      return (Array)ARRAY_DICT_INIT;
    854    }
    855 
    856    kvi_push(patterns, STRING_OBJ(arena_printf(arena, "<buffer=%d>", (int)buf->handle)));
    857  }
    858 
    859  if (kv_size(patterns) == 0 && fallback) {
    860    kvi_push(patterns, CSTR_AS_OBJ(fallback));
    861  }
    862 
    863  return arena_take_arraybuilder(arena, &patterns);
    864 }
    865 
    866 static bool clear_autocmd(event_T event, char *pat, int au_group, Error *err)
    867 {
    868  if (do_autocmd_event(event, pat, false, false, "", true, au_group) == FAIL) {
    869    api_set_error(err, kErrorTypeException, "Failed to clear autocmd");
    870    return false;
    871  }
    872 
    873  return true;
    874 }
    875 
    876 #undef GET_ONE_EVENT