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