diff options
-rw-r--r-- | runtime/doc/api.txt | 124 | ||||
-rw-r--r-- | src/nvim/api/autocmd.c | 221 | ||||
-rw-r--r-- | src/nvim/api/keysets.lua | 4 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 5 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.h | 16 | ||||
-rw-r--r-- | src/nvim/autocmd.c | 54 | ||||
-rw-r--r-- | src/nvim/autocmd.h | 2 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 24 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 3 | ||||
-rw-r--r-- | test/functional/api/autocmd_spec.lua | 165 |
10 files changed, 389 insertions, 229 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index cff616cf59..fe5f9eaf35 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3109,6 +3109,130 @@ nvim_tabpage_set_var({tabpage}, {name}, {value}) ============================================================================== +Autocmd Functions *api-autocmd* + +nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* + Create or get an augroup. + + To get an existing augroup ID, do: > + local id = vim.api.nvim_create_augroup(name, { + clear = false + }) +< + + Parameters: ~ + {name} String: The name of the augroup to create + {opts} Parameters + • clear (bool): Whether to clear existing commands + or not. Defaults to true. See |autocmd-groups| + + Return: ~ + opaque value to use with nvim_del_augroup_by_id + +nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* + Create an autocmd. + + Examples: + • event: "pat1,pat2,pat3", + • event: "pat1" + • event: { "pat1" } + • event: { "pat1", "pat2", "pat3" } + + Parameters: ~ + {event} The event or events to register this autocmd + Required keys: event: string | ArrayOf(string) + + Parameters: ~ + {opts} Optional Parameters: + • callback: (string|function) + • (string): The name of the viml function to + execute when triggering this autocmd + • (function): The lua function to execute when + triggering this autocmd + • NOTE: Cannot be used with {command} + + • command: (string) command + • vimscript command + • NOTE: Cannot be used with {callback} Eg. + command = "let g:value_set = v:true" + + • pattern: (string|table) + • pattern or patterns to match against + • defaults to "*". + • NOTE: Cannot be used with {buffer} + + • buffer: (bufnr) + • create a |autocmd-buflocal| autocmd. + • NOTE: Cannot be used with {pattern} + + • group: (string) The augroup name + • once: (boolean) - See |autocmd-once| + • nested: (boolean) - See |autocmd-nested| + • desc: (string) - Description of the autocmd + + Return: ~ + opaque value to use with nvim_del_autocmd + +nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()* + Delete an augroup by {id}. {id} can only be returned when + augroup was created with |nvim_create_augroup|. + + NOTE: behavior differs from augroup-delete. + + When deleting an augroup, autocmds contained by this augroup + will also be deleted and cleared. This augroup will no longer + exist + +nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()* + Delete an augroup by {name}. + + NOTE: behavior differs from augroup-delete. + + When deleting an augroup, autocmds contained by this augroup + will also be deleted and cleared. This augroup will no longer + exist + +nvim_del_autocmd({id}) *nvim_del_autocmd()* + Delete an autocmd by {id}. Autocmds only return IDs when + created via the API. Will not error if called and no autocmds + match the {id}. + + Parameters: ~ + {id} Integer The ID returned by nvim_create_autocmd + +nvim_do_autocmd({event}, {*opts}) *nvim_do_autocmd()* + Do one autocmd. + + Parameters: ~ + {event} The event or events to execute + {opts} Optional Parameters: + • buffer (number) - buffer number + • NOTE: Cannot be used with {pattern} + + • pattern (string|table) - optional, defaults to + "*". + • NOTE: Cannot be used with {buffer} + + • group (string) - autocmd group name + • modeline (boolean) - Default true, see + |<nomodeline>| + +nvim_get_autocmds({*opts}) *nvim_get_autocmds()* + Get autocmds that match the requirements passed to {opts}. + + Parameters: ~ + {opts} Optional Parameters: + • event : Name or list of name of events to match + against + • group (string): Name of group to match against + • pattern: Pattern or list of patterns to match + against + + Return: ~ + A list of autocmds that match + + +============================================================================== UI Functions *api-ui* nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index deb8ec8cf3..6b89eb1770 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -17,34 +17,7 @@ #define AUCMD_MAX_PATTERNS 256 -// Check whether every item in the array is a kObjectTypeString -#define CHECK_STRING_ARRAY(__array, k, v, goto_name) \ - for (size_t j = 0; j < __array.size; j++) { \ - Object item = __array.items[j]; \ - if (item.type != kObjectTypeString) { \ - api_set_error(err, \ - kErrorTypeValidation, \ - "All entries in '%s' must be strings", \ - k); \ - goto goto_name; \ - } \ - } - // Copy string or array of strings into an empty array. -#define UNPACK_STRING_OR_ARRAY(__array, k, v, goto_name) \ - if (v->type == kObjectTypeString) { \ - ADD(__array, copy_object(*v)); \ - } else if (v->type == kObjectTypeArray) { \ - CHECK_STRING_ARRAY(__array, k, v, goto_name); \ - __array = copy_array(v->data.array); \ - } else { \ - api_set_error(err, \ - kErrorTypeValidation, \ - "'%s' must be an array or a string.", \ - k); \ - goto goto_name; \ - } - // Get the event number, unless it is an error. Then goto `goto_name`. #define GET_ONE_EVENT(event_nr, event_str, goto_name) \ char_u *__next_ev; \ @@ -61,16 +34,18 @@ static int64_t next_autocmd_id = 1; /// Get autocmds that match the requirements passed to {opts}. -/// group -/// event -/// pattern /// -/// -- @param {string} event - event or events to match against -/// vim.api.nvim_get_autocmds({ event = "FileType" }) +/// @param opts Optional Parameters: +/// - event : Name or list of name of events to match against +/// - group (string): Name of group to match against +/// - pattern: Pattern or list of patterns to match against /// +/// @return A list of autocmds that match Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) FUNC_API_SINCE(9) { + // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) + Array autocmd_list = ARRAY_DICT_INIT; char_u *pattern_filters[AUCMD_MAX_PATTERNS]; char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; @@ -199,6 +174,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) if (aucmd_exec_is_deleted(ac->exec)) { continue; } + Dictionary autocmd_info = ARRAY_DICT_INIT; if (ap->group != AUGROUP_DEFAULT) { @@ -256,40 +232,42 @@ cleanup: return autocmd_list; } -/// Define an autocmd. -/// @param opts Dictionary +/// Create an autocmd. +/// +/// @param event The event or events to register this autocmd /// Required keys: /// event: string | ArrayOf(string) -/// event = "pat1,pat2,pat3", -/// event = "pat1" -/// event = {"pat1"} -/// event = {"pat1", "pat2", "pat3"} -/// -/// -/// -- @param {string} name - augroup name -/// -- @param {string | table} event - event or events to match against -/// -- @param {string | table} pattern - pattern or patterns to match against -/// -- @param {string | function} callback - function or string to execute on autocmd -/// -- @param {string} command - optional, vimscript command -/// Eg. command = "let g:value_set = v:true" -/// -- @param {boolean} once - optional, defaults to false /// -/// -- pattern = comma delimited list of patterns | pattern | { pattern, ... } +/// Examples: +/// - event: "pat1,pat2,pat3", +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } /// -/// pattern = "*.py,*.pyi" -/// pattern = "*.py" -/// pattern = {"*.py"} -/// pattern = { "*.py", "*.pyi" } +/// @param opts Optional Parameters: +/// - callback: (string|function) +/// - (string): The name of the viml function to execute when triggering this autocmd +/// - (function): The lua function to execute when triggering this autocmd +/// - NOTE: Cannot be used with {command} +/// - command: (string) command +/// - vimscript command +/// - NOTE: Cannot be used with {callback} +/// Eg. command = "let g:value_set = v:true" +/// - pattern: (string|table) +/// - pattern or patterns to match against +/// - defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - create a |autocmd-buflocal| autocmd. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string) The augroup name +/// - once: (boolean) - See |autocmd-once| +/// - nested: (boolean) - See |autocmd-nested| +/// - desc: (string) - Description of the autocmd /// -/// -- not supported -/// pattern = {"*.py,*.pyi"} -/// -/// -- event = string | string[] -/// event = "FileType,CursorHold" -/// event = "BufPreWrite" -/// event = {"BufPostWrite"} -/// event = {"CursorHold", "BufPreWrite", "BufPostWrite"} -Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Error *err) +/// @returns opaque value to use with nvim_del_autocmd +Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, + Error *err) FUNC_API_SINCE(9) { int64_t autocmd_id = -1; @@ -304,6 +282,11 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; Callback cb = CALLBACK_NONE; + + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { api_set_error(err, kErrorTypeValidation, "cannot pass both: 'callback' and 'command' for the same autocmd"); @@ -359,14 +342,10 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err goto cleanup; } - if (opts->event.type != kObjectTypeNil) { - UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) - } - bool is_once = api_object_to_bool(opts->once, "once", false, err); bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); - // TOOD: accept number for namespace instead + // TODO(tjdevries): accept number for namespace instead if (opts->group.type != kObjectTypeNil) { Object *v = &opts->group; if (v->type != kObjectTypeString) { @@ -402,7 +381,9 @@ Integer nvim_create_autocmd(uint64_t channel_id, Dict(create_autocmd) *opts, Err patlen = aucmd_pattern_length(pat); } } else if (v->type == kObjectTypeArray) { - CHECK_STRING_ARRAY(patterns, "pattern", v, cleanup); + if (!check_autocmd_string_array(patterns, "pattern", err)) { + goto cleanup; + } Array array = v->data.array; for (size_t i = 0; i < array.size; i++) { @@ -503,8 +484,9 @@ cleanup: return autocmd_id; } -/// Delete an autocmd by ID. Autocmds only return IDs when created -/// via the API. +/// Delete an autocmd by {id}. Autocmds only return IDs when created +/// via the API. Will not error if called and no autocmds match +/// the {id}. /// /// @param id Integer The ID returned by nvim_create_autocmd void nvim_del_autocmd(Integer id) @@ -517,28 +499,28 @@ void nvim_del_autocmd(Integer id) /// /// To get an existing augroup ID, do: /// <pre> -/// local id = vim.api.nvim_create_augroup({ name = name, clear = false }); +/// local id = vim.api.nvim_create_augroup(name, { +/// clear = false +/// }) /// </pre> /// +/// @param name String: The name of the augroup to create /// @param opts Parameters -/// - name (string): The name of the augroup /// - clear (bool): Whether to clear existing commands or not. -// Defaults to true. +/// Defaults to true. /// See |autocmd-groups| -Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Error *err) +/// +/// @returns opaque value to use with nvim_del_augroup_by_id +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 = api_object_to_bool(opts->clear, "clear", true, err); - if (opts->name.type != kObjectTypeString) { - api_set_error(err, kErrorTypeValidation, "'name' is required and must be a string"); - return -1; - } - char *name = opts->name.data.string.data; - int augroup = -1; WITH_SCRIPT_CONTEXT(channel_id, { - augroup = augroup_add(name); + augroup = augroup_add(augroup_name); if (augroup == AUGROUP_ERROR) { api_set_error(err, kErrorTypeException, "Failed to set augroup"); return -1; @@ -554,7 +536,11 @@ Integer nvim_create_augroup(uint64_t channel_id, Dict(create_augroup) *opts, Err return augroup; } +/// Delete an augroup by {id}. {id} can only be returned when augroup was +/// created with |nvim_create_augroup|. +/// /// NOTE: behavior differs from augroup-delete. +/// /// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. /// This augroup will no longer exist void nvim_del_augroup_by_id(Integer id) @@ -564,7 +550,10 @@ void nvim_del_augroup_by_id(Integer id) augroup_del(name, false); } +/// Delete an augroup by {name}. +/// /// NOTE: behavior differs from augroup-delete. +/// /// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. /// This augroup will no longer exist void nvim_del_augroup_by_name(String name) @@ -573,12 +562,17 @@ void nvim_del_augroup_by_name(String name) augroup_del(name.data, false); } -/// -- @param {string} group - autocmd group name -/// -- @param {number} buffer - buffer number -/// -- @param {string | table} event - event or events to match against -/// -- @param {string | table} pattern - optional, defaults to "*". -/// vim.api.nvim_do_autcmd({ group, buffer, pattern, event, modeline }) -void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) +/// Do one autocmd. +/// +/// @param event The event or events to execute +/// @param opts Optional Parameters: +/// - buffer (number) - buffer number +/// - NOTE: Cannot be used with {pattern} +/// - pattern (string|table) - optional, defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - group (string) - autocmd group name +/// - modeline (boolean) - Default true, see |<nomodeline>| +void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) FUNC_API_SINCE(9) { int au_group = AUGROUP_ALL; @@ -592,6 +586,10 @@ void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) Array event_array = ARRAY_DICT_INIT; + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + if (opts->group.type != kObjectTypeNil) { if (opts->group.type != kObjectTypeString) { api_set_error(err, kErrorTypeValidation, "'group' must be a string"); @@ -634,13 +632,7 @@ void nvim_do_autocmd(Dict(do_autocmd) *opts, Error *err) set_pattern = true; } - if (opts->event.type != kObjectTypeNil) { - UNPACK_STRING_OR_ARRAY(event_array, "event", (&opts->event), cleanup) - } - - if (opts->modeline.type != kObjectTypeNil) { - modeline = api_object_to_bool(opts->modeline, "modeline", true, err); - } + modeline = api_object_to_bool(opts->modeline, "modeline", true, err); if (set_pattern && set_buf) { api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); @@ -663,7 +655,46 @@ cleanup: XFREE_CLEAR(pattern); } +static bool check_autocmd_string_array(Array arr, char *k, Error *err) +{ + for (size_t i = 0; i < arr.size; i++) { + if (arr.items[i].type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "All entries in '%s' must be strings", + k); + return false; + } + + // Disallow newlines in the middle of the line. + const String l = arr.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, kErrorTypeValidation, + "String cannot contain newlines"); + return false; + } + } + return true; +} + +static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) +{ + if (v->type == kObjectTypeString) { + ADD(*array, copy_object(*v)); + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(v->data.array, k, err)) { + return false; + } + *array = copy_array(v->data.array); + } else { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + + return true; +} -#undef UNPACK_STRING_OR_ARRAY -#undef CHECK_STRING_ARRAY #undef GET_ONE_EVENT diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index be71c446b1..b05816f8ac 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -116,7 +116,6 @@ return { "callback"; "command"; "desc"; - "event"; "group"; "once"; "nested"; @@ -124,7 +123,6 @@ return { }; do_autocmd = { "buffer"; - "event"; "group"; "modeline"; "pattern"; @@ -132,12 +130,10 @@ return { get_autocmds = { "event"; "group"; - "id"; "pattern"; }; create_augroup = { "clear"; - "name"; }; } diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 3d4a04f096..c2106855d3 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -406,6 +406,7 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object }); } + buf_T *find_buffer_by_handle(Buffer buffer, Error *err) { if (buffer == 0) { @@ -1614,8 +1615,8 @@ int find_sid(uint64_t channel_id) { switch (channel_id) { case VIML_INTERNAL_CALL: - // TODO(autocmd): Figure out what this should be - // return SID_API_CLIENT; + // TODO(autocmd): Figure out what this should be + // return SID_API_CLIENT; case LUA_INTERNAL_CALL: return SID_LUA; default: diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6969994c3b..bc7c2e6a60 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -152,13 +152,15 @@ typedef struct { #endif #define WITH_SCRIPT_CONTEXT(channel_id, code) \ - const sctx_T save_current_sctx = current_sctx; \ - current_sctx.sc_sid = \ - (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ - current_sctx.sc_lnum = 0; \ - current_channel_id = channel_id; \ - code; \ - current_sctx = save_current_sctx; + do { \ + const sctx_T save_current_sctx = current_sctx; \ + current_sctx.sc_sid = \ + (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ + current_sctx.sc_lnum = 0; \ + current_channel_id = channel_id; \ + code; \ + current_sctx = save_current_sctx; \ + } while (0); #endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index a9e1978ac0..5d53ac6b17 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -4,13 +4,10 @@ // autocmd.c: Autocommand related functions #include <signal.h> -#include "nvim/autocmd.h" - -// #include "nvim/api/private/handle.h" - #include "lauxlib.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/charset.h" #include "nvim/cursor.h" @@ -108,17 +105,6 @@ static char_u *old_termresponse = NULL; #define FOR_ALL_AUPATS_IN_EVENT(event, ap) \ for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT -/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested -#define ARG_GET_FLAG(errored, cmd, flag, pattern, len) \ - if (STRNCMP(cmd, pattern, len) == 0 && ascii_iswhite((cmd)[len])) { \ - if (flag) { \ - semsg(_(e_duparg2), pattern); \ - (errored) = true; \ - } \ - (flag) = true; \ - (cmd) = skipwhite((cmd) + (len)); \ - } - // Map of autocmd group names. // name -> ID static Map(String, int) augroup_map = MAP_INIT; @@ -408,6 +394,16 @@ int augroup_add(char *name) } /// Delete the augroup that matches name. +/// @param stupid_legacy_mode bool: This paremeter determines whether to run the augroup +/// deletion in the same fashion as `:augroup! {name}` where if there are any remaining +/// autocmds left in the augroup, it will change the name of the augroup to `--- DELETED ---` +/// but leave the autocmds existing. These are _separate_ augroups, so if you do this for +/// multiple augroups, you will have a bunch of `--- DELETED ---` augroups at the same time. +/// There is no way, as far as I could tell, how to actually delete them at this point as a user +/// +/// I did not consider this good behavior, so now when NOT in stupid_legacy_mode, we actually +/// delete these groups and their commands, like you would expect (and don't leave hanging +/// `--- DELETED ---` groups around) void augroup_del(char *name, bool stupid_legacy_mode) { int i = augroup_find(name); @@ -723,7 +719,7 @@ void do_autocmd(char_u *arg_in, int forceit) char_u *envpat = NULL; char_u *cmd; int need_free = false; - int nested = false; + bool nested = false; bool once = false; int group; @@ -778,11 +774,11 @@ void do_autocmd(char_u *arg_in, int forceit) bool invalid_flags = false; for (size_t i = 0; i < 2; i++) { if (*cmd != NUL) { - ARG_GET_FLAG(invalid_flags, cmd, once, "++once", 6); - ARG_GET_FLAG(invalid_flags, cmd, nested, "++nested", 8); + invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6); + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8); // Check the deprecated "nested" flag. - ARG_GET_FLAG(invalid_flags, cmd, nested, "nested", 6); + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6); } } @@ -2050,6 +2046,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) typval_T rettv = TV_INITIAL_VALUE; callback_call(&ac->exec.callable.cb, 0, &argsin, &rettv); + // TODO(tjdevries): + // // Major Hack Alert: // We just return "not-null" and continue going. // This would be a good candidate for a refactor. You would need to refactor: @@ -2057,7 +2055,7 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) // OR // 2. make where we call do_cmdline for autocmds not have to return anything, // and instead we loop over all the matches and just execute one-by-one. - // However, my expectation woudl be that could be expensive. + // However, my expectation would be that could be expensive. retval = vim_strsave((char_u *)""); } else { retval = vim_strsave(ac->exec.callable.cmd); @@ -2544,6 +2542,22 @@ static int arg_augroup_get(char_u **argp) return group; } +/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested +static bool arg_autocmd_flag_get(bool *flag, char_u **cmd_ptr, char *pattern, int len) +{ + if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { + if (*flag) { + semsg(_(e_duparg2), pattern); + return true; + } + + *flag = true; + *cmd_ptr = skipwhite((*cmd_ptr) + len); + } + + return false; +} + // UI Enter void do_autocmd_uienter(uint64_t chanid, bool attached) diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index 0a1f036183..53ec7f2bb7 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -27,7 +27,7 @@ typedef struct AutoCmd { bool once; // "One shot": removed after execution bool nested; // If autocommands nest here bool last; // last command in list - int64_t id; // TODO(tjdevries): Explain + int64_t id; // ID used for uniquely tracking an autocmd. sctx_T script_ctx; // script context where defined char *desc; // Description for the autocmd. struct AutoCmd *next; // Next AutoCmd in list diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index fbda7fbc3c..44b003d106 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include <string.h> +#include "lauxlib.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" @@ -1157,7 +1158,22 @@ void callback_free(Callback *callback) /// Check if callback is freed bool callback_is_freed(Callback callback) { - return false; + switch (callback.type) { + case kCallbackFuncref: + return false; + break; + case kCallbackPartial: + return false; + break; + case kCallbackLua: + return callback.data.luaref == LUA_NOREF; + break; + case kCallbackNone: + return true; + break; + } + + return true; } /// Copy a callback into a typval_T. @@ -1176,8 +1192,10 @@ void callback_put(Callback *cb, typval_T *tv) func_ref(cb->data.funcref); break; case kCallbackLua: - // TODO(tjdevries): I'm not even sure if this is strictly necessary? - abort(); + // TODO(tjdevries): Unified Callback. + // At this point this isn't possible, but it'd be nice to put + // these handled more neatly in one place. + // So instead, we just do the default and put nil default: tv->v_type = VAR_SPECIAL; tv->vval.v_special = kSpecialVarNull; diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 18abf04ff6..07071d4e1e 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1354,6 +1354,9 @@ bool nlua_ref_is_function(LuaRef ref) { lua_State *const lstate = global_lstate; nlua_pushref(lstate, ref); + + // TODO(tjdevries): This should probably check for callable tables as well. + // We should put some work maybe into simplifying how all of that works bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); lua_pop(lstate, 1); diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 2b48ffa6f0..32a3db34a3 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -14,8 +14,7 @@ before_each(clear) describe('autocmd api', function() describe('nvim_create_autocmd', function() it('does not allow "command" and "callback" in the same autocmd', function() - local ok, _ = pcall(meths.create_autocmd, { - event = "BufReadPost", + local ok, _ = pcall(meths.create_autocmd, "BufReadPost", { pattern = "*.py,*.pyi", command = "echo 'Should Have Errored", callback = "not allowed", @@ -28,12 +27,11 @@ describe('autocmd api', function() eq(1, exec_lua([[ local count = 0 - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = function() count = count + 1 end, once = true - } + }) vim.cmd "set filetype=txt" vim.cmd "set filetype=python" @@ -45,11 +43,10 @@ describe('autocmd api', function() it('allows passing buffer by key', function() meths.set_var('called', 0) - meths.create_autocmd { - event = "Filetype", + meths.create_autocmd("FileType", { command = "let g:called = g:called + 1", buffer = 0, - } + }) meths.command "set filetype=txt" eq(1, meths.get_var('called')) @@ -62,8 +59,7 @@ describe('autocmd api', function() end) it('does not allow passing buffer and patterns', function() - local ok = pcall(meths.create_autocmd, { - event = "Filetype", + local ok = pcall(meths.create_autocmd, "Filetype", { command = "let g:called = g:called + 1", buffer = 0, pattern = "*.py", @@ -73,8 +69,7 @@ describe('autocmd api', function() end) it('does not allow passing invalid buffers', function() - local ok, msg = pcall(meths.create_autocmd, { - event = "Filetype", + local ok, msg = pcall(meths.create_autocmd, "Filetype", { command = "let g:called = g:called + 1", buffer = -1, }) @@ -85,17 +80,15 @@ describe('autocmd api', function() it('errors on non-functions for cb', function() eq(false, pcall(exec_lua, [[ - vim.api.nvim_create_autocmd { - event = "BufReadPost", + vim.api.nvim_create_autocmd("BufReadPost", { pattern = "*.py,*.pyi", callback = 5, - } + }) ]])) end) it('allow passing pattern and <buffer> in same pattern', function() - local ok = pcall(meths.create_autocmd, { - event = "BufReadPost", + local ok = pcall(meths.create_autocmd, "BufReadPost", { pattern = "*.py,<buffer>", command = "echo 'Should Not Error'" }) @@ -104,22 +97,20 @@ describe('autocmd api', function() end) it('should handle multiple values as comma separated list', function() - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = "*.py,*.pyi", command = "echo 'Should Not Have Errored'" - } + }) -- We should have one autocmd for *.py and one for *.pyi eq(2, #meths.get_autocmds { event = "BufReadPost" }) end) it('should handle multiple values as array', function() - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = { "*.py", "*.pyi", }, command = "echo 'Should Not Have Errored'" - } + }) -- We should have one autocmd for *.py and one for *.pyi eq(2, #meths.get_autocmds { event = "BufReadPost" }) @@ -127,23 +118,21 @@ describe('autocmd api', function() describe('desc', function() it('can add description to one autocmd', function() - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = "*.py", command = "echo 'Should Not Have Errored'", desc = "Can show description", - } + }) eq("Can show description", meths.get_autocmds { event = "BufReadPost" }[1].desc) end) it('can add description to multiple autocmd', function() - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = {"*.py", "*.pyi"}, command = "echo 'Should Not Have Errored'", desc = "Can show description", - } + }) local aus = meths.get_autocmds { event = "BufReadPost" } eq(2, #aus) @@ -154,12 +143,11 @@ describe('autocmd api', function() pending('script and verbose settings', function() it('marks API client', function() - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = "*.py", command = "echo 'Should Not Have Errored'", desc = "Can show description", - } + }) local aus = meths.get_autocmds { event = "BufReadPost" } eq(1, #aus, aus) @@ -211,8 +199,7 @@ describe('autocmd api', function() command [[au! InsertLeave]] command [[au InsertEnter * :echo "1"]] source [[ - call nvim_create_autocmd(#{ - \ event: "InsertLeave", + call nvim_create_autocmd("InsertLeave", #{ \ command: ":echo 2", \ }) ]] @@ -283,12 +270,11 @@ describe('autocmd api', function() it('raises error for undefined augroup', function() local success, code = unpack(meths.exec_lua([[ return {pcall(function() - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", group = "NotDefined", command = "echo 'hello'", - } + }) end)} ]], {})) @@ -358,14 +344,13 @@ describe('autocmd api', function() it("can trigger builtin autocmds", function() meths.set_var("autocmd_executed", false) - meths.create_autocmd { - event = "BufReadPost", + meths.create_autocmd("BufReadPost", { pattern = "*", command = "let g:autocmd_executed = v:true", - } + }) eq(false, meths.get_var("autocmd_executed")) - meths.do_autocmd { event = "BufReadPost" } + meths.do_autocmd("BufReadPost", {}) eq(true, meths.get_var("autocmd_executed")) end) @@ -373,17 +358,16 @@ describe('autocmd api', function() meths.set_var("buffer_executed", -1) eq(-1, meths.get_var("buffer_executed")) - meths.create_autocmd { - event = "BufLeave", + meths.create_autocmd("BufLeave", { pattern = "*", command = 'let g:buffer_executed = +expand("<abuf>")', - } + }) -- Doesn't execute for other non-matching events - meths.do_autocmd { event = "CursorHold", buffer = 1 } + meths.do_autocmd("CursorHold", { buffer = 1 }) eq(-1, meths.get_var("buffer_executed")) - meths.do_autocmd { event = "BufLeave", buffer = 1 } + meths.do_autocmd("BufLeave", { buffer = 1 }) eq(1, meths.get_var("buffer_executed")) end) @@ -391,14 +375,13 @@ describe('autocmd api', function() meths.set_var("filename_executed", 'none') eq('none', meths.get_var("filename_executed")) - meths.create_autocmd { - event = "BufEnter", + meths.create_autocmd("BufEnter", { pattern = "*.py", command = 'let g:filename_executed = expand("<afile>")', - } + }) -- Doesn't execute for other non-matching events - meths.do_autocmd { event = "CursorHold", buffer = 1 } + meths.do_autocmd("CursorHold", { buffer = 1 }) eq('none', meths.get_var("filename_executed")) meths.command('edit __init__.py') @@ -406,7 +389,7 @@ describe('autocmd api', function() end) it('cannot pass buf and fname', function() - local ok = pcall(meths.do_autocmd, { pattern = "literally_cannot_error.rs", buffer = 1 }) + local ok = pcall(meths.do_autocmd, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) eq(false, ok) end) @@ -418,38 +401,36 @@ describe('autocmd api', function() meths.command('edit __init__.py') eq('none', meths.get_var("filename_executed")) - meths.create_autocmd { - event = "CursorHoldI", + meths.create_autocmd("CursorHoldI", { pattern = "__init__.py", command = 'let g:filename_executed = expand("<afile>")', - } + }) -- Doesn't execute for other non-matching events - meths.do_autocmd { event = "CursorHoldI", buffer = 1 } + meths.do_autocmd("CursorHoldI", { buffer = 1 }) eq('none', meths.get_var("filename_executed")) - meths.do_autocmd { event = "CursorHoldI", buffer = tonumber(meths.get_current_buf()) } + meths.do_autocmd("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) eq('__init__.py', meths.get_var("filename_executed")) -- Reset filename meths.set_var("filename_executed", 'none') - meths.do_autocmd { event = "CursorHoldI", pattern = '__init__.py' } + meths.do_autocmd("CursorHoldI", { pattern = '__init__.py' }) eq('__init__.py', meths.get_var("filename_executed")) end) it("works with user autocmds", function() meths.set_var("matched", 'none') - meths.create_autocmd { - event = "User", + meths.create_autocmd("User", { pattern = "TestCommand", command = 'let g:matched = "matched"' - } + }) - meths.do_autocmd { event = "User", pattern = "OtherCommand" } + meths.do_autocmd("User", { pattern = "OtherCommand" }) eq('none', meths.get_var('matched')) - meths.do_autocmd { event = "User", pattern = "TestCommand" } + meths.do_autocmd("User", { pattern = "TestCommand" }) eq('matched', meths.get_var('matched')) end) end) @@ -465,7 +446,6 @@ describe('autocmd api', function() opts = opts or {} local resulting = { - event = "FileType", pattern = "*", command = "let g:executed = g:executed + 1", } @@ -473,7 +453,7 @@ describe('autocmd api', function() resulting.group = opts.group resulting.once = opts.once - meths.create_autocmd(resulting) + meths.create_autocmd("FileType", resulting) end local set_ft = function(ft) @@ -487,7 +467,7 @@ describe('autocmd api', function() it('can be added in a group', function() local augroup = "TestGroup" - meths.create_augroup({ name = augroup, clear = true }) + meths.create_augroup(augroup, { clear = true }) make_counting_autocmd { group = augroup } set_ft("txt") @@ -516,8 +496,7 @@ describe('autocmd api', function() end) it('errors on unexpected keys', function() - local success, code = pcall(meths.create_autocmd, { - event = "FileType", + local success, code = pcall(meths.create_autocmd, "FileType", { pattern = "*", not_a_valid_key = "NotDefined", }) @@ -530,11 +509,10 @@ describe('autocmd api', function() exec_lua([[ vim.g.executed = false - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = function() vim.g.executed = true end, - } + }) ]], {}) @@ -551,17 +529,15 @@ describe('autocmd api', function() count = count + 1 end - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = counter, - } + }) - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = counter, - } + }) vim.cmd "set filetype=txt" vim.cmd "set filetype=txt" @@ -579,15 +555,14 @@ describe('autocmd api', function() MyVal = {} WeakTable[MyVal] = true - vim.api.nvim_create_autocmd { - event = "FileType", + vim.api.nvim_create_autocmd("FileType", { pattern = "*", callback = function() OnceCount = OnceCount + 1 MyVal = {} end, once = true - } + }) ]]) command [[set filetype=txt]] @@ -610,10 +585,9 @@ describe('autocmd api', function() it('groups can be cleared', function() local augroup = "TestGroup" - meths.create_augroup({ name = augroup, clear = true }) - meths.create_autocmd({ + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("FileType", { group = augroup, - event = "FileType", command = "let g:executed = g:executed + 1" }) @@ -621,7 +595,7 @@ describe('autocmd api', function() set_ft("txt") eq(2, get_executed_count(), "should only count twice") - meths.create_augroup({ name = augroup, clear = true }) + meths.create_augroup(augroup, { clear = true }) eq({}, meths.get_autocmds { group = augroup }) set_ft("txt") @@ -632,7 +606,7 @@ describe('autocmd api', function() it('groups work with once', function() local augroup = "TestGroup" - meths.create_augroup({ name = augroup, clear = true }) + meths.create_augroup(augroup, { clear = true }) make_counting_autocmd { group = augroup, once = true } set_ft("txt") @@ -644,7 +618,7 @@ describe('autocmd api', function() it('autocmds can be registered multiple times.', function() local augroup = "TestGroup" - meths.create_augroup({ name = augroup, clear = true }) + meths.create_augroup(augroup, { clear = true }) make_counting_autocmd { group = augroup, once = false } make_counting_autocmd { group = augroup, once = false } make_counting_autocmd { group = augroup, once = false } @@ -658,15 +632,14 @@ describe('autocmd api', function() it('can be deleted', function() local augroup = "WillBeDeleted" - meths.create_augroup({ name = augroup, clear = true }) - meths.create_autocmd { - event = {"Filetype"}, + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd({"Filetype"}, { pattern = "*", command = "echo 'does not matter'", - } + }) -- Clears the augroup from before, which erases the autocmd - meths.create_augroup({ name = augroup, clear = true }) + meths.create_augroup(augroup, { clear = true }) local result = #meths.get_autocmds { group = augroup } @@ -678,12 +651,11 @@ describe('autocmd api', function() meths.set_var("value_set", false) - meths.create_augroup({ name = augroup, clear = true }) - meths.create_autocmd { - event = "Filetype", + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("Filetype", { pattern = "<buffer>", command = "let g:value_set = v:true", - } + }) command "new" command "set filetype=python" @@ -699,8 +671,7 @@ describe('autocmd api', function() let g:vimscript_executed = g:vimscript_executed + 1 endfunction - call nvim_create_autocmd(#{ - \ event: "Filetype", + call nvim_create_autocmd("FileType", #{ \ pattern: ["python", "javascript"], \ callback: "MyVimscriptFunction", \ }) @@ -773,7 +744,7 @@ describe('autocmd api', function() command('autocmd! BufReadPost *.py :echo "Hello"') command('augroup END') - local augroup_id = meths.create_augroup { name = "TEMP_ABCD", clear = false } + local augroup_id = meths.create_augroup("TEMP_ABCD", { clear = false }) meths.del_augroup_by_id(augroup_id) -- For good reason, we kill all the autocmds from del_augroup, |