aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/api/autocmd.c151
-rw-r--r--src/nvim/autocmd.c974
-rw-r--r--src/nvim/autocmd.h70
-rw-r--r--src/nvim/ex_docmd.c3
-rw-r--r--src/nvim/generators/gen_events.lua19
-rw-r--r--test/benchmark/autocmd_spec.lua175
-rw-r--r--test/functional/autocmd/autocmd_spec.lua18
-rw-r--r--test/functional/autocmd/show_spec.lua41
8 files changed, 725 insertions, 726 deletions
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index e606322f24..6ecbff2606 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -225,8 +225,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
continue;
}
- for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) {
- if (ap->cmds == NULL) {
+ AutoCmdVec *acs = au_get_autocmds_for_event(event);
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+
+ if (ap == NULL) {
continue;
}
@@ -238,19 +242,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
// Skip 'pattern' from invalid patterns if passed.
if (pattern_filter_count > 0) {
bool passed = false;
- for (int i = 0; i < pattern_filter_count; i++) {
- assert(i < AUCMD_MAX_PATTERNS);
- assert(pattern_filters[i]);
+ for (int j = 0; j < pattern_filter_count; j++) {
+ assert(j < AUCMD_MAX_PATTERNS);
+ assert(pattern_filters[j]);
- char *pat = pattern_filters[i];
+ char *pat = pattern_filters[j];
int patlen = (int)strlen(pat);
if (aupat_is_buflocal(pat, patlen)) {
- aupat_normalize_buflocal_pat(pattern_buflocal,
- pat,
- patlen,
+ aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
aupat_get_buflocal_nr(pat, patlen));
-
pat = pattern_buflocal;
}
@@ -265,85 +266,71 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
}
}
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
-
- Dictionary autocmd_info = ARRAY_DICT_INIT;
+ Dictionary autocmd_info = ARRAY_DICT_INIT;
- if (ap->group != AUGROUP_DEFAULT) {
- PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
- PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
- }
+ if (ap->group != AUGROUP_DEFAULT) {
+ PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+ PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
+ }
- if (ac->id > 0) {
- PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
- }
+ if (ac->id > 0) {
+ PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
+ }
- if (ac->desc != NULL) {
- PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
- }
+ if (ac->desc != NULL) {
+ PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
+ }
- if (ac->exec.type == CALLABLE_CB) {
- PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
+ if (ac->exec.type == CALLABLE_CB) {
+ PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
- Callback *cb = &ac->exec.callable.cb;
- switch (cb->type) {
- case kCallbackLua:
- if (nlua_ref_is_function(cb->data.luaref)) {
- PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
- }
- break;
- case kCallbackFuncref:
- case kCallbackPartial:
- PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
- break;
- default:
- abort();
+ Callback *cb = &ac->exec.callable.cb;
+ switch (cb->type) {
+ case kCallbackLua:
+ if (nlua_ref_is_function(cb->data.luaref)) {
+ PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
}
- } else {
- PUT(autocmd_info,
- "command",
- STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+ break;
+ case kCallbackFuncref:
+ case kCallbackPartial:
+ PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
+ break;
+ default:
+ abort();
}
+ } else {
+ PUT(autocmd_info, "command", STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+ }
- PUT(autocmd_info,
- "pattern",
- STRING_OBJ(cstr_to_string(ap->pat)));
-
- PUT(autocmd_info,
- "event",
- STRING_OBJ(cstr_to_string((char *)event_nr2name(event))));
-
- PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
-
- if (ap->buflocal_nr) {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
- PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
- } else {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
- }
+ PUT(autocmd_info, "pattern", STRING_OBJ(cstr_to_string(ap->pat)));
+ PUT(autocmd_info, "event", STRING_OBJ(cstr_to_string(event_nr2name(event))));
+ PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
- // TODO(sctx): It would be good to unify script_ctx to actually work with lua
- // right now it's just super weird, and never really gives you the info that
- // you would expect from this.
- //
- // I think we should be able to get the line number, filename, etc. from lua
- // when we're executing something, and it should be easy to then save that
- // info here.
- //
- // I think it's a big loss not getting line numbers of where options, autocmds,
- // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
- //
- // Once we do that, we can put these into the autocmd_info, but I don't think it's
- // useful to do that at this time.
- //
- // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
- // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
-
- ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ if (ap->buflocal_nr) {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
+ PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
+ } else {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
}
+
+ // TODO(sctx): It would be good to unify script_ctx to actually work with lua
+ // right now it's just super weird, and never really gives you the info that
+ // you would expect from this.
+ //
+ // I think we should be able to get the line number, filename, etc. from lua
+ // when we're executing something, and it should be easy to then save that
+ // info here.
+ //
+ // I think it's a big loss not getting line numbers of where options, autocmds,
+ // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
+ //
+ // Once we do that, we can put these into the autocmd_info, but I don't think it's
+ // useful to do that at this time.
+ //
+ // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
+ // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
+
+ ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
}
}
@@ -663,7 +650,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
if (clear_autocmds) {
FOR_ALL_AUEVENTS(event) {
- aupat_del_for_event_and_group(event, augroup);
+ aucmd_del_for_event_and_group(event, augroup);
}
}
});
@@ -866,7 +853,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
Object *v = &pattern;
if (v->type == kObjectTypeString) {
- char *pat = v->data.string.data;
+ const char *pat = v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
@@ -881,7 +868,7 @@ static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Ob
Array array = v->data.array;
FOREACH_ITEM(array, entry, {
- char *pat = entry.data.string.data;
+ const char *pat = entry.data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 1d09e8f6c3..7a65f11e80 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -15,16 +15,12 @@
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
-#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
-#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
@@ -34,7 +30,6 @@
#include "nvim/highlight_defs.h"
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
-#include "nvim/main.h"
#include "nvim/map.h"
#include "nvim/memline_defs.h"
#include "nvim/memory.h"
@@ -69,41 +64,13 @@
// - Groups start with augroup_
// - Events start with event_
+// The autocommands are stored in a contiguous vector for each event.
//
-// The autocommands are stored in a list for each event.
-// Autocommands for the same pattern, that are consecutive, are joined
-// together, to avoid having to match the pattern too often.
-// The result is an array of Autopat lists, which point to AutoCmd lists:
-//
-// last_autopat[0] -----------------------------+
-// V
-// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
-// Autopat.cmds Autopat.cmds
-// | |
-// V V
-// AutoCmd.next AutoCmd.next
-// | |
-// V V
-// AutoCmd.next NULL
-// |
-// V
-// NULL
-//
-// last_autopat[1] --------+
-// V
-// first_autopat[1] --> Autopat.next --> NULL
-// Autopat.cmds
-// |
-// V
-// AutoCmd.next
-// |
-// V
-// NULL
-// etc.
-//
-// The order of AutoCmds is important, this is the order in which they were
-// defined and will have to be executed.
+// The order of AutoCmds is important, this is the order in which they
+// were defined and will have to be executed.
//
+// To avoid having to match the pattern too often, patterns are reference
+// counted and reused for consecutive autocommands.
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
@@ -120,7 +87,7 @@ static int current_augroup = AUGROUP_DEFAULT;
// Whether we need to delete marked patterns.
// While deleting autocmds, they aren't actually remover, just marked.
-static int au_need_clean = false;
+static bool au_need_clean = false;
static int autocmd_blocked = 0; // block all autocmds
@@ -129,20 +96,16 @@ static bool autocmd_include_groups = false;
static char *old_termresponse = NULL;
-/// Iterates over all the AutoPats for a particular event
-#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \
- for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT
-
// Map of autocmd group names and ids.
// name -> ID
// ID -> name
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
-static void augroup_map_del(int id, char *name)
+static void augroup_map_del(int id, const char *name)
{
if (name != NULL) {
- String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string(name));
+ String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name));
map_del(String, int)(&map_augroup_name_to_id, key);
api_free_string(key);
}
@@ -161,204 +124,191 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
return deleted_augroup;
}
-// Show the autocommands for one AutoPat.
-static void aupat_show(AutoPat *ap, event_T event, int previous_group)
+static void au_show_for_all_events(int group, const char *pat)
{
- // Check for "got_int" (here and at various places below), which is set
- // when "q" has been hit for the "--more--" prompt
- if (got_int) {
- return;
+ FOR_ALL_AUEVENTS(event) {
+ au_show_for_event(group, event, pat);
}
+}
- // pattern has been removed
- if (ap->pat == NULL) {
+static void au_show_for_event(int group, event_T event, const char *pat)
+{
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ // Return early if there are no autocmds for this event
+ if (kv_size(*acs) == 0) {
return;
}
- char *name = augroup_name(ap->group);
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+ int patlen;
+ if (*pat != NUL) {
+ patlen = (int)aucmd_pattern_length(pat);
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- // When switching groups, we need to show the new group information.
- if (ap->group != previous_group) {
- // show the group name, if it's not the default group
- if (ap->group != AUGROUP_DEFAULT) {
- if (name == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(name, HL_ATTR(HLF_T));
- }
- msg_puts(" ");
+ // detect special <buffer[=X]> buffer-local patterns
+ if (aupat_is_buflocal(pat, patlen)) {
+ // normalize pat into standard "<buffer>#N" form
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
+ pat = buflocal_pat;
+ patlen = (int)strlen(buflocal_pat);
}
- // show the event name
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- msg_putchar('\n');
- if (got_int) {
+
+ if (patlen == 0) {
return;
}
+ assert(*pat != NUL);
+ } else {
+ pat = NULL;
+ patlen = 0;
}
- msg_col = 4;
- msg_outtrans(ap->pat);
-
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- // skip removed commands
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ // Loop through all the specified patterns.
+ while (true) {
+ AutoPat *last_ap = NULL;
+ int last_group = AUGROUP_ERROR;
+ const char *last_group_name = NULL;
- if (msg_col >= 14) {
- msg_putchar('\n');
- }
- msg_col = 14;
- if (got_int) {
- return;
- }
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
- char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
- if (ac->desc != NULL) {
- size_t msglen = 100;
- char *msg = xmallocz(msglen);
- if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- snprintf(msg, msglen, " [%s]", ac->desc);
- } else {
- snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
+ // Skip deleted autocommands.
+ if (ac->pat == NULL) {
+ continue;
}
- msg_outtrans(msg);
- XFREE_CLEAR(msg);
- } else if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- } else {
- msg_outtrans(exec_to_string);
- }
- XFREE_CLEAR(exec_to_string);
- if (p_verbose > 0) {
- last_set_msg(ac->script_ctx);
- }
- if (got_int) {
- return;
- }
- if (ac->next != NULL) {
- msg_putchar('\n');
- if (got_int) {
- return;
+
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if ((group != AUGROUP_ALL && ac->pat->group != group)
+ || (pat != NULL
+ && (ac->pat->patlen != patlen || strncmp(pat, ac->pat->pat, (size_t)patlen) != 0))) {
+ continue;
}
- }
- }
-}
-static void au_show_for_all_events(int group, char *pat)
-{
- FOR_ALL_AUEVENTS(event) {
- au_show_for_event(group, event, pat);
- }
-}
+ // Show event name and group only if one of them changed.
+ if (ac->pat->group != last_group) {
+ last_group = ac->pat->group;
+ last_group_name = augroup_name(ac->pat->group);
-static void au_show_for_event(int group, event_T event, char *pat)
-{
- // Return early if there are no autocmds for this event
- if (au_event_is_empty(event)) {
- return;
- }
+ if (got_int) {
+ return;
+ }
- // always need to show group information before the first pattern for the event
- int previous_group = AUGROUP_ERROR;
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- if (*pat == NUL) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (group == AUGROUP_ALL || ap->group == group) {
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ // When switching groups, we need to show the new group information.
+ // show the group name, if it's not the default group
+ if (ac->pat->group != AUGROUP_DEFAULT) {
+ if (last_group_name == NULL) {
+ msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
+ } else {
+ msg_puts_attr(last_group_name, HL_ATTR(HLF_T));
+ }
+ msg_puts(" ");
+ }
+ // show the event name
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
}
- }
- return;
- }
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
- // Loop through all the specified patterns.
- int patlen = (int)aucmd_pattern_length(pat);
- while (patlen) {
- // detect special <buffer[=X]> buffer-local patterns
- if (aupat_is_buflocal(pat, patlen)) {
- // normalize pat into standard "<buffer>#N" form
- aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
- pat = buflocal_pat;
- patlen = (int)strlen(buflocal_pat);
- }
+ // Show pattern only if it changed.
+ if (last_ap != ac->pat) {
+ last_ap = ac->pat;
- assert(*pat != NUL);
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- // Find AutoPat entries with this pattern.
- // always goes at or after the last one, so start at the end.
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if ((group == AUGROUP_ALL || ap->group == group)
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Show autocmd's for this autopat, or buflocals <buffer=X>
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ msg_col = 4;
+ msg_outtrans(ac->pat->pat);
+ }
+
+ if (got_int) {
+ return;
+ }
+
+ if (msg_col >= 14) {
+ msg_putchar('\n');
+ }
+ msg_col = 14;
+ if (got_int) {
+ return;
+ }
+
+ char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
+ if (ac->desc != NULL) {
+ size_t msglen = 100;
+ char *msg = xmallocz(msglen);
+ if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ snprintf(msg, msglen, " [%s]", ac->desc);
+ } else {
+ snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
}
+ msg_outtrans(msg);
+ XFREE_CLEAR(msg);
+ } else if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ } else {
+ msg_outtrans(exec_to_string);
+ }
+ XFREE_CLEAR(exec_to_string);
+ if (p_verbose > 0) {
+ last_set_msg(ac->script_ctx);
+ }
+
+ if (got_int) {
+ return;
}
}
- pat = aucmd_next_pattern(pat, (size_t)patlen);
- patlen = (int)aucmd_pattern_length(pat);
+ // If a pattern is provided, find next pattern. Otherwise exit after single iteration.
+ if (pat != NULL) {
+ pat = aucmd_next_pattern(pat, (size_t)patlen);
+ patlen = (int)aucmd_pattern_length(pat);
+ if (patlen == 0) {
+ break;
+ }
+ } else {
+ break;
+ }
}
}
-// Mark an autocommand handler for deletion.
-static void aupat_del(AutoPat *ap)
-{
- XFREE_CLEAR(ap->pat);
- ap->buflocal_nr = -1;
- au_need_clean = true;
-}
-
-void aupat_del_for_event_and_group(event_T event, int group)
+// Delete autocommand.
+static void aucmd_del(AutoCmd *ac)
{
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == group) {
- aupat_del(ap);
- }
+ if (ac->pat != NULL && --ac->pat->refcount == 0) {
+ XFREE_CLEAR(ac->pat->pat);
+ vim_regfree(ac->pat->reg_prog);
+ xfree(ac->pat);
}
+ ac->pat = NULL;
+ aucmd_exec_free(&ac->exec);
+ XFREE_CLEAR(ac->desc);
- au_cleanup();
+ au_need_clean = true;
}
-// Mark all commands for a pattern for deletion.
-static void aupat_remove_cmds(AutoPat *ap)
+void aucmd_del_for_event_and_group(event_T event, int group)
{
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- aucmd_exec_free(&ac->exec);
-
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat->group == group) {
+ aucmd_del(ac);
}
}
- au_need_clean = true;
-}
-// Delete one command from an autocmd pattern.
-static void aucmd_del(AutoCmd *ac)
-{
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
- au_need_clean = true;
+ au_cleanup();
}
-/// Cleanup autocommands and patterns that have been deleted.
+/// Cleanup autocommands that have been deleted.
/// This is only done when not executing autocommands.
static void au_cleanup(void)
{
@@ -368,72 +318,39 @@ static void au_cleanup(void)
// Loop over all events.
FOR_ALL_AUEVENTS(event) {
- // Loop over all autocommand patterns.
- AutoPat **prev_ap = &(first_autopat[(int)event]);
- for (AutoPat *ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- bool has_cmd = false;
-
- // Loop over all commands for this pattern.
- AutoCmd **prev_ac = &(ap->cmds);
- for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
- // Remove the command if the pattern is to be deleted or when
- // the command has been marked for deletion.
- if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) {
- *prev_ac = ac->next;
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
-
- xfree(ac);
- } else {
- has_cmd = true;
- prev_ac = &(ac->next);
- }
+ // Loop over all autocommands.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ size_t nsize = 0;
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (nsize != i) {
+ kv_A(*acs, nsize) = *ac;
}
-
- if (ap->pat != NULL && !has_cmd) {
- // Pattern was not marked for deletion, but all of its commands were.
- // So mark the pattern for deletion.
- aupat_del(ap);
- }
-
- // Remove the pattern if it has been marked for deletion.
- if (ap->pat == NULL) {
- if (ap->next == NULL) {
- if (prev_ap == &(first_autopat[(int)event])) {
- last_autopat[(int)event] = NULL;
- } else {
- // this depends on the "next" field being the first in
- // the struct
- last_autopat[(int)event] = (AutoPat *)prev_ap;
- }
- }
- *prev_ap = ap->next;
- vim_regfree(ap->reg_prog);
- xfree(ap);
- } else {
- prev_ap = &(ap->next);
+ if (ac->pat != NULL) {
+ nsize++;
}
}
+ if (nsize == 0) {
+ kv_destroy(*acs);
+ } else {
+ acs->size = nsize;
+ }
}
au_need_clean = false;
}
-// Get the first AutoPat for a particular event.
-AutoPat *au_get_autopat_for_event(event_T event)
+AutoCmdVec *au_get_autocmds_for_event(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[(int)event];
+ return &autocmds[(int)event];
}
-// Called when buffer is freed, to remove/invalidate related buffer-local
-// autocmds.
+// Called when buffer is freed, to remove/invalidate related buffer-local autocmds.
void aubuflocal_remove(buf_T *buf)
{
// invalidate currently executing autocommands
- for (AutoPatCmd *apc = active_apc_list; apc; apc = apc->next) {
+ for (AutoPatCmd *apc = active_apc_list; apc != NULL; apc = apc->next) {
if (buf->b_fnum == apc->arg_bufnr) {
apc->arg_bufnr = 0;
}
@@ -441,16 +358,19 @@ void aubuflocal_remove(buf_T *buf)
// invalidate buflocals looping through events
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->buflocal_nr == buf->b_fnum) {
- aupat_del(ap);
-
- if (p_verbose >= 6) {
- verbose_enter();
- smsg(_("auto-removing autocommand: %s <buffer=%d>"),
- event_nr2name(event), buf->b_fnum);
- verbose_leave();
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat == NULL || ac->pat->buflocal_nr != buf->b_fnum) {
+ continue;
+ }
+
+ aucmd_del(ac);
+
+ if (p_verbose >= 6) {
+ verbose_enter();
+ smsg(_("auto-removing autocommand: %s <buffer=%d>"), event_nr2name(event), buf->b_fnum);
+ verbose_leave();
}
}
}
@@ -459,7 +379,7 @@ void aubuflocal_remove(buf_T *buf)
// Add an autocmd group name or return existing group matching name.
// Return its ID.
-int augroup_add(char *name)
+int augroup_add(const char *name)
{
assert(STRICMP(name, "end") != 0);
@@ -495,20 +415,21 @@ int augroup_add(char *name)
/// `--- DELETED ---` groups around)
void augroup_del(char *name, bool stupid_legacy_mode)
{
- int i = augroup_find(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist
+ int group = augroup_find(name);
+ if (group == AUGROUP_ERROR) { // the group doesn't exist
semsg(_("E367: No such group: \"%s\""), name);
return;
- }
- if (i == current_augroup) {
+ } else if (group == current_augroup) {
emsg(_("E936: Cannot delete the current group"));
return;
}
if (stupid_legacy_mode) {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i && ap->pat != NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL && ap->group == group) {
give_warning(_("W19: Deleting augroup that is still in use"), true);
map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED);
augroup_map_del(ap->group, NULL);
@@ -518,16 +439,18 @@ void augroup_del(char *name, bool stupid_legacy_mode)
}
} else {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat != NULL && ac->pat->group == group) {
+ aucmd_del(ac);
}
}
}
}
// Remove the group because it's not currently in use.
- augroup_map_del(i, name);
+ augroup_map_del(group, name);
au_cleanup();
}
@@ -636,14 +559,14 @@ void do_augroup(char *arg, int del_group)
void free_all_autocmds(void)
{
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ aucmd_del(&kv_A(*acs, i));
}
+ kv_destroy(*acs);
+ au_need_clean = false;
}
- au_need_clean = true;
- au_cleanup();
-
// Delete the augroup_map, including free the data
String name;
int id;
@@ -727,8 +650,7 @@ static bool event_ignored(event_T event)
while (*p != NUL) {
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
return true;
- }
- if (event_name2nr(p, &p) == event) {
+ } else if (event_name2nr(p, &p) == event) {
return true;
}
}
@@ -900,7 +822,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
}
}
- bool is_showing = !forceit && *cmd == NUL;
+ const bool is_showing = !forceit && *cmd == NUL;
// Print header when showing autocommands.
if (is_showing) {
@@ -925,8 +847,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
event_T event = event_name2nr(arg, &arg);
assert(event < NUM_EVENTS);
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) == FAIL) {
break;
}
}
@@ -939,11 +860,10 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
xfree(envpat);
}
-void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool del, int group)
+void do_all_autocmd_events(const char *pat, bool once, int nested, char *cmd, bool del, int group)
{
FOR_ALL_AUEVENTS(event) {
- if (do_autocmd_event(event, pat, once, nested, cmd, del, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, del, group) == FAIL) {
return;
}
}
@@ -956,30 +876,21 @@ void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool del
// If *cmd == NUL: show entries.
// If forceit == true: delete entries.
// If group is not AUGROUP_ALL: only use this group.
-int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, bool del,
+int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char *cmd, bool del,
int group)
FUNC_ATTR_NONNULL_ALL
{
// Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events
assert(*pat != NUL || del);
- AutoPat *ap;
- AutoPat **prev_ap;
- int findgroup;
- int buflocal_nr;
char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
bool is_adding_cmd = *cmd != NUL;
-
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// Delete all aupat for an event.
if (*pat == NUL && del) {
- aupat_del_for_event_and_group(event, findgroup);
+ aucmd_del_for_event_and_group(event, findgroup);
return OK;
}
@@ -988,9 +899,8 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
while (patlen) {
// detect special <buffer[=X]> buffer-local patterns
int is_buflocal = aupat_is_buflocal(pat, patlen);
-
if (is_buflocal) {
- buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+ const int buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
// normalize pat into standard "<buffer>#N" form
aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
@@ -1002,31 +912,25 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
if (del) {
assert(*pat != NUL);
- // Find AutoPat entries with this pattern.
- prev_ap = &first_autopat[(int)event];
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Remove existing autocommands.
- // If adding any new autocmd's for this AutoPat, don't
- // delete the pattern from the autopat list, append to
- // this list.
- if (is_adding_cmd && ap->next == NULL) {
- aupat_remove_cmds(ap);
- break;
- }
- aupat_del(ap);
- }
+ // Find existing autocommands with this pattern.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if (ap != NULL && ap->group == findgroup && ap->patlen == patlen
+ && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
+ // Remove existing autocommands.
+ // If adding any new autocmd's for this AutoPat, don't
+ // delete the pattern from the autopat list, append to
+ // this list.
+ aucmd_del(ac);
}
- prev_ap = &ap->next;
}
}
@@ -1045,32 +949,23 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
return OK;
}
-int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group, bool once,
+int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int group, bool once,
bool nested, char *desc, AucmdExecutable aucmd)
{
// 0 is not a valid group.
assert(group != 0);
- AutoPat *ap;
- AutoPat **prev_ap;
- AutoCmd *ac;
- int findgroup;
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
-
if (patlen > (int)strlen(pat)) {
return FAIL;
}
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// detect special <buffer[=X]> buffer-local patterns
- int is_buflocal = aupat_is_buflocal(pat, patlen);
+ const int is_buflocal = aupat_is_buflocal(pat, patlen);
int buflocal_nr = 0;
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
if (is_buflocal) {
buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
@@ -1081,67 +976,52 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
patlen = (int)strlen(buflocal_pat);
}
- // always goes at or after the last one, so start at the end.
- if (last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
- prev_ap = &first_autopat[(int)event];
- }
-
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- if (ap->next == NULL) {
- // Add autocmd to this autopat, if it's the last one.
- break;
- }
- }
+ // Try to reuse pattern from the last existing autocommand.
+ AutoPat *ap = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (ptrdiff_t i = (ptrdiff_t)kv_size(*acs) - 1; i >= 0; i--) {
+ ap = kv_A(*acs, i).pat;
+ if (ap == NULL) {
+ continue; // Skip deleted autocommands.
}
- prev_ap = &ap->next;
+ // Set result back to NULL if the last pattern doesn't match.
+ if (ap->group != findgroup || ap->patlen != patlen
+ || strncmp(pat, ap->pat, (size_t)patlen) != 0) {
+ ap = NULL;
+ }
+ break;
}
- // If the pattern we want to add a command to does appear at the
- // end of the list (or not is not in the list at all), add the
- // pattern at the end of the list.
+ // No matching pattern found, allocate a new one.
if (ap == NULL) {
// refuse to add buffer-local ap if buffer number is invalid
- if (is_buflocal
- && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
+ if (is_buflocal && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
return FAIL;
}
ap = xmalloc(sizeof(AutoPat));
- ap->pat = xstrnsave(pat, (size_t)patlen);
- ap->patlen = patlen;
if (is_buflocal) {
ap->buflocal_nr = buflocal_nr;
ap->reg_prog = NULL;
} else {
- char *reg_pat;
-
ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
+ char *reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
if (reg_pat != NULL) {
ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
}
xfree(reg_pat);
if (reg_pat == NULL || ap->reg_prog == NULL) {
- xfree(ap->pat);
xfree(ap);
return FAIL;
}
}
+ ap->refcount = 0;
+ ap->pat = xstrnsave(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
// need to initialize last_mode for the first ModeChanged autocmd
if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
get_mode(last_mode);
@@ -1168,53 +1048,34 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
use_tabpage(save_curtab);
}
- ap->cmds = NULL;
- *prev_ap = ap;
- last_autopat[(int)event] = ap;
- ap->next = NULL;
- if (group == AUGROUP_ALL) {
- ap->group = current_augroup;
- } else {
- ap->group = group;
- }
- }
-
- // Add the autocmd at the end of the AutoCmd list.
- AutoCmd **prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL) {
- prev_ac = &ac->next;
+ ap->group = group == AUGROUP_ALL ? current_augroup : group;
}
- ac = xmalloc(sizeof(AutoCmd));
- *prev_ac = ac;
+ ap->refcount++;
+ // Add the autocmd at the end of the AutoCmd vector.
+ AutoCmd *ac = kv_pushp(autocmds[(int)event]);
+ ac->pat = ap;
ac->id = id;
ac->exec = aucmd_exec_copy(aucmd);
ac->script_ctx = current_sctx;
ac->script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&ac->script_ctx);
- ac->next = NULL;
ac->once = once;
ac->nested = nested;
- ac->desc = NULL;
-
- // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there.
- // perhaps: <lua>DESCRIPTION or similar
- if (desc != NULL) {
- ac->desc = xstrdup(desc);
- }
+ ac->desc = desc == NULL ? NULL : xstrdup(desc);
return OK;
}
-size_t aucmd_pattern_length(char *pat)
+size_t aucmd_pattern_length(const char *pat)
FUNC_ATTR_PURE
{
if (*pat == NUL) {
return 0;
}
- char *endpat;
+ const char *endpat;
for (; *pat; pat = endpat + 1) {
// Find end of the pattern.
@@ -1225,8 +1086,7 @@ size_t aucmd_pattern_length(char *pat)
continue;
}
int brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- endpat++) {
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); endpat++) {
if (*endpat == '{') {
brace_level++;
} else if (*endpat == '}') {
@@ -1240,14 +1100,13 @@ size_t aucmd_pattern_length(char *pat)
return strlen(pat);
}
-char *aucmd_next_pattern(char *pat, size_t patlen)
+const char *aucmd_next_pattern(const char *pat, size_t patlen)
FUNC_ATTR_PURE
{
pat = pat + patlen;
if (*pat == ',') {
pat = pat + 1;
}
-
return pat;
}
@@ -1637,8 +1496,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
return false;
}
- bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, NULL, NULL);
+ bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, NULL, NULL);
if (did_cmd && aborting()) {
*retval = FAIL;
}
@@ -1650,7 +1508,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
/// @param event the autocommand to check
bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return first_autopat[event] != NULL;
+ return kv_size(autocmds[(int)event]) != 0;
}
/// Return true when there is a CursorHold/CursorHoldI autocommand defined for
@@ -1658,7 +1516,6 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return has_event((get_real_state() == MODE_NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI));
- // return first_autopat[] != NULL;
}
/// Return true if the CursorHold/CursorHoldI event can be triggered.
@@ -1692,7 +1549,6 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
char *sfname = NULL; // short file name
bool retval = false;
static int nesting = 0;
- AutoPat *ap;
char *save_cmdarg;
long save_cmdbang;
static int filechangeshell_busy = false;
@@ -1703,8 +1559,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Quickly return if there are no autocommands for this event or
// autocommands are blocked.
- if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
- || is_autocmd_blocked()) {
+ if (event == NUM_EVENTS || kv_size(autocmds[(int)event]) == 0 || is_autocmd_blocked()) {
goto BYPASS_AU;
}
@@ -1722,8 +1577,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// FileChangedShell never nests, because it can create an endless loop.
if (filechangeshell_busy
- && (event == EVENT_FILECHANGEDSHELL
- || event == EVENT_FILECHANGEDSHELLPOST)) {
+ && (event == EVENT_FILECHANGEDSHELL || event == EVENT_FILECHANGEDSHELLPOST)) {
goto BYPASS_AU;
}
@@ -1742,8 +1596,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Check if these autocommands are disabled. Used when doing ":all" or
// ":ball".
if ((autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER))
- || (autocmd_no_leave
- && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
+ || (autocmd_no_leave && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
goto BYPASS_AU;
}
@@ -1779,11 +1632,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
}
// Set the buffer number to be used for <abuf>.
- if (buf == NULL) {
- autocmd_bufnr = 0;
- } else {
- autocmd_bufnr = buf->b_fnum;
- }
+ autocmd_bufnr = buf == NULL ? 0 : buf->b_fnum;
// When the file name is NULL or empty, use the file name of buffer "buf".
// Always use the full path of the file name to match with, in case
@@ -1886,18 +1735,24 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Find first autocommand that matches
AutoPatCmd patcmd = {
- .curpat = first_autopat[(int)event],
- .group = group,
+ // aucmd_next will set lastpat back to NULL if there are no more autocommands left to run
+ .lastpat = NULL,
+ // current autocommand index
+ .auidx = 0,
+ // save vector size, to avoid an endless loop when more patterns
+ // are added when executing autocommands
+ .ausize = kv_size(autocmds[(int)event]),
.fname = fname,
.sfname = sfname,
.tail = tail,
+ .group = group,
.event = event,
.arg_bufnr = autocmd_bufnr,
};
- auto_next_pat(&patcmd, false);
+ aucmd_next(&patcmd);
- // found one, start executing the autocommands
- if (patcmd.curpat != NULL) {
+ // Found first autocommand, start executing them
+ if (patcmd.lastpat != NULL) {
// add to active_apc_list
patcmd.next = active_apc_list;
active_apc_list = &patcmd;
@@ -1914,12 +1769,6 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
save_cmdarg = NULL; // avoid gcc warning
}
retval = true;
- // mark the last pattern, to avoid an endless loop when more patterns
- // are added when executing autocommands
- for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) {
- ap->last = false;
- }
- ap->last = true;
// Make sure cursor and topline are valid. The first time the current
// values are saved, restored by reset_lnums(). When nested only the
@@ -1931,8 +1780,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
}
// Execute the autocmd. The `getnextac` callback handles iteration.
- do_cmdline(NULL, getnextac, (void *)&patcmd,
- DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+ do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
if (nesting == 1) {
// restore cursor and topline, unless they were changed
@@ -2038,8 +1886,7 @@ void unblock_autocmds(void)
// When v:termresponse was set while autocommands were blocked, trigger
// the autocommands now. Esp. useful when executing a shell command
// during startup (nvim -d).
- if (!is_autocmd_blocked()
- && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
+ if (!is_autocmd_blocked() && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
}
}
@@ -2050,77 +1897,70 @@ bool is_autocmd_blocked(void)
return autocmd_blocked != 0;
}
-/// Find next autocommand pattern that matches.
-/// stop when 'last' flag is set
-void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
+/// Find next matching autocommand.
+/// If next autocommand was not found, sets lastpat to NULL and cmdidx to SIZE_MAX on apc.
+static void aucmd_next(AutoPatCmd *apc)
{
- AutoPat *ap;
- AutoCmd *cp;
- char *s;
-
estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
- // Clear the exestack entry for this ETYPE_AUCMD entry.
- XFREE_CLEAR(entry->es_name);
- entry->es_info.aucmd = NULL;
-
- for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
- apc->curpat = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
+ assert(apc->ausize <= kv_size(*acs));
+ for (size_t i = apc->auidx; i < apc->ausize && !got_int; i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
- // Only use a pattern when it has not been removed, has commands and
- // the group matches. For buffer-local autocommands only check the
- // buffer number.
- if (ap->pat != NULL && ap->cmds != NULL
- && (apc->group == AUGROUP_ALL || apc->group == ap->group)) {
- // execution-condition
+ // Skip deleted autocommands.
+ if (ap == NULL) {
+ continue;
+ }
+ // Skip matching if pattern didn't change.
+ if (ap != apc->lastpat) {
+ // Skip autocommands that don't match the group.
+ if (apc->group != AUGROUP_ALL && apc->group != ap->group) {
+ continue;
+ }
+ // Skip autocommands that don't match the pattern or buffer number.
if (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- apc->fname,
- apc->sfname,
- apc->tail,
- ap->allow_dirs)
- : ap->buflocal_nr == apc->arg_bufnr) {
- const char *const name = event_nr2name(apc->event);
- s = _("%s Autocommands for \"%s\"");
-
- const size_t sourcing_name_len
- = (strlen(s) + strlen(name) + (size_t)ap->patlen + 1);
-
- char *const namep = xmalloc(sourcing_name_len);
- snprintf(namep, sourcing_name_len, s, name, ap->pat);
- if (p_verbose >= 8) {
- verbose_enter();
- smsg(_("Executing %s"), namep);
- verbose_leave();
- }
+ ? !match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname, apc->tail, ap->allow_dirs)
+ : ap->buflocal_nr != apc->arg_bufnr) {
+ continue;
+ }
- // Update the exestack entry for this autocmd.
- entry->es_name = namep;
- entry->es_info.aucmd = apc;
+ const char *const name = event_nr2name(apc->event);
+ const char *const s = _("%s Autocommands for \"%s\"");
- apc->curpat = ap;
- apc->nextcmd = ap->cmds;
- // mark last command
- for (cp = ap->cmds; cp->next != NULL; cp = cp->next) {
- cp->last = false;
- }
- cp->last = true;
+ const size_t sourcing_name_len = strlen(s) + strlen(name) + (size_t)ap->patlen + 1;
+ char *const namep = xmalloc(sourcing_name_len);
+ snprintf(namep, sourcing_name_len, s, name, ap->pat);
+ if (p_verbose >= 8) {
+ verbose_enter();
+ smsg(_("Executing %s"), namep);
+ verbose_leave();
}
- line_breakcheck();
- if (apc->curpat != NULL) { // found a match
- break;
- }
- }
- if (stop_at_last && ap->last) {
- break;
+
+ // Update the exestack entry for this autocmd.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_name = namep;
+ entry->es_info.aucmd = apc;
}
+
+ apc->lastpat = ap;
+ apc->auidx = i;
+
+ line_breakcheck();
+ return;
}
+
+ // Clear the exestack entry for this ETYPE_AUCMD entry.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_info.aucmd = NULL;
+
+ apc->lastpat = NULL;
+ apc->auidx = SIZE_MAX;
}
static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
{
- bool ret = false;
Callback callback = ac->exec.callable.cb;
if (callback.type == kCallbackLua) {
Dictionary data = ARRAY_DICT_INIT;
@@ -2134,7 +1974,7 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
PUT(data, "data", copy_object(*apc->data, NULL));
}
- int group = apc->curpat->group;
+ int group = ac->pat->group;
switch (group) {
case AUGROUP_ERROR:
abort(); // unreachable
@@ -2152,18 +1992,19 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
ADD_C(args, DICTIONARY_OBJ(data));
Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL);
+ bool ret = false;
if (result.type == kObjectTypeBoolean) {
ret = result.data.boolean;
}
api_free_dictionary(data);
api_free_object(result);
+ return ret;
} else {
typval_T argsin = TV_INITIAL_VALUE;
typval_T rettv = TV_INITIAL_VALUE;
callback_call(&callback, 0, &argsin, &rettv);
+ return false;
}
-
- return ret;
}
/// Get next autocommand command.
@@ -2176,45 +2017,16 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
(void)indent;
(void)do_concat;
- AutoPatCmd *acp = (AutoPatCmd *)cookie;
- char *retval;
+ AutoPatCmd *const apc = (AutoPatCmd *)cookie;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
- // Can be called again after returning the last line.
- if (acp->curpat == NULL) {
+ aucmd_next(apc);
+ if (apc->lastpat == NULL) {
return NULL;
}
- // repeat until we find an autocommand to execute
- while (true) {
- // skip removed commands
- while (acp->nextcmd != NULL
- && aucmd_exec_is_deleted(acp->nextcmd->exec)) {
- if (acp->nextcmd->last) {
- acp->nextcmd = NULL;
- } else {
- acp->nextcmd = acp->nextcmd->next;
- }
- }
-
- if (acp->nextcmd != NULL) {
- break;
- }
-
- // at end of commands, find next pattern that matches
- if (acp->curpat->last) {
- acp->curpat = NULL;
- } else {
- acp->curpat = acp->curpat->next;
- }
- if (acp->curpat != NULL) {
- auto_next_pat(acp, true);
- }
- if (acp->curpat == NULL) {
- return NULL;
- }
- }
-
- AutoCmd *ac = acp->nextcmd;
+ assert(apc->auidx < kv_size(*acs));
+ AutoCmd *const ac = &kv_A(*acs, apc->auidx);
bool oneshot = ac->once;
if (p_verbose >= 9) {
@@ -2230,10 +2042,12 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// lua code, so that it works properly
autocmd_nested = ac->nested;
current_sctx = ac->script_ctx;
- acp->script_ctx = current_sctx;
+ apc->script_ctx = current_sctx;
+ char *retval;
if (ac->exec.type == CALLABLE_CB) {
- if (call_autocmd_callback(ac, acp)) {
+ // Can potentially reallocate kvec_t data and invalidate the ac pointer
+ if (call_autocmd_callback(ac, apc)) {
// If an autocommand callback returns true, delete the autocommand
oneshot = true;
}
@@ -2248,19 +2062,20 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// 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 would be that could be expensive.
- retval = xstrdup("");
+ retval = xcalloc(1, 1);
} else {
retval = xstrdup(ac->exec.callable.cmd);
}
// Remove one-shot ("once") autocmd in anticipation of its execution.
if (oneshot) {
- aucmd_del(ac);
+ aucmd_del(&kv_A(*acs, apc->auidx));
}
- if (ac->last) {
- acp->nextcmd = NULL;
+
+ if (apc->auidx < apc->ausize) {
+ apc->auidx++;
} else {
- acp->nextcmd = ac->next;
+ apc->auidx = SIZE_MAX;
}
return retval;
@@ -2292,15 +2107,12 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
forward_slash(fname);
#endif
- for (AutoPat *ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->pat != NULL && ap->cmds != NULL
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL
&& (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- fname,
- sfname,
- tail,
- ap->allow_dirs)
+ ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail, ap->allow_dirs)
: buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
retval = true;
break;
@@ -2315,13 +2127,10 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
return retval;
}
-// Function given to ExpandGeneric() to obtain the list of autocommand group
-// names.
+// Function given to ExpandGeneric() to obtain the list of autocommand group names.
char *expand_get_augroup_name(expand_T *xp, int idx)
{
- // Required for ExpandGeneric
- (void)xp;
-
+ (void)xp; // Required for ExpandGeneric
return augroup_name(idx + 1);
}
@@ -2374,8 +2183,7 @@ char *set_context_in_autocmd(expand_T *xp, char *arg, int doautocmd)
// Function given to ExpandGeneric() to obtain the list of event names.
char *expand_get_event_name(expand_T *xp, int idx)
{
- // xp is a required parameter to be used with ExpandGeneric
- (void)xp;
+ (void)xp; // xp is a required parameter to be used with ExpandGeneric
// List group names
char *name = augroup_name(idx + 1);
@@ -2416,7 +2224,8 @@ bool autocmd_supported(const char *const event)
/// exists("#Event#pat")
///
/// @param arg autocommand string
-bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
+bool au_exists(const char *const arg)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
buf_T *buflocal_buf = NULL;
bool retval = false;
@@ -2463,8 +2272,8 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
// Find the first autocommand for this event.
// If there isn't any, return false;
// If there is one and no pattern given, return true;
- AutoPat *ap = first_autopat[(int)event];
- if (ap == NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ if (kv_size(*acs) == 0) {
goto theend;
}
@@ -2475,11 +2284,11 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
}
// Check if there is an autocommand with the given pattern.
- for (; ap != NULL; ap = ap->next) {
- // only use a pattern when it has not been removed and has commands.
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ // Only use a pattern when it has not been removed.
// For buffer-local autocommands, path_fnamecmp() works fine.
- if (ap->pat != NULL && ap->cmds != NULL
- && (group == AUGROUP_ALL || ap->group == group)
+ if ((group == AUGROUP_ALL || ap->group == group)
&& (pattern == NULL
|| (buflocal_buf == NULL
? path_fnamecmp(ap->pat, pattern) == 0
@@ -2495,15 +2304,13 @@ theend:
}
// Checks if a pattern is buflocal
-bool aupat_is_buflocal(char *pat, int patlen)
+bool aupat_is_buflocal(const char *pat, int patlen)
FUNC_ATTR_PURE
{
- return patlen >= 8
- && strncmp(pat, "<buffer", 7) == 0
- && (pat)[patlen - 1] == '>';
+ return patlen >= 8 && strncmp(pat, "<buffer", 7) == 0 && (pat)[patlen - 1] == '>';
}
-int aupat_get_buflocal_nr(char *pat, int patlen)
+int aupat_get_buflocal_nr(const char *pat, int patlen)
{
assert(aupat_is_buflocal(pat, patlen));
@@ -2528,7 +2335,7 @@ int aupat_get_buflocal_nr(char *pat, int patlen)
}
// normalize buffer pattern
-void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int buflocal_nr)
+void aupat_normalize_buflocal_pat(char *dest, const char *pat, int patlen, int buflocal_nr)
{
assert(aupat_is_buflocal(pat, patlen));
@@ -2537,13 +2344,10 @@ void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int bufloca
}
// normalize pat into standard "<buffer>#N" form
- snprintf(dest,
- BUFLOCAL_PAT_LEN,
- "<buffer=%d>",
- buflocal_nr);
+ snprintf(dest, BUFLOCAL_PAT_LEN, "<buffer=%d>", buflocal_nr);
}
-int autocmd_delete_event(int group, event_T event, char *pat)
+int autocmd_delete_event(int group, event_T event, const char *pat)
FUNC_ATTR_NONNULL_ALL
{
return do_autocmd_event(event, pat, false, false, "", true, group);
@@ -2559,12 +2363,12 @@ bool autocmd_delete_id(int64_t id)
// Note that since multiple AutoCmd objects can have the same ID, we need to do a full scan.
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) { // -V756
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->id == id) {
- aucmd_del(ac);
- success = true;
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->id == id) {
+ aucmd_del(ac);
+ success = true;
}
}
}
@@ -2627,25 +2431,10 @@ AucmdExecutable aucmd_exec_copy(AucmdExecutable src)
abort();
}
-bool aucmd_exec_is_deleted(AucmdExecutable acc)
- FUNC_ATTR_PURE
-{
- switch (acc.type) {
- case CALLABLE_EX:
- return acc.callable.cmd == NULL;
- case CALLABLE_CB:
- return acc.callable.cb.type == kCallbackNone;
- case CALLABLE_NONE:
- return true;
- }
-
- abort();
-}
-
bool au_event_is_empty(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[event] == NULL;
+ return kv_size(autocmds[(int)event]) == 0;
}
// Arg Parsing Functions
@@ -2734,8 +2523,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
assert(chanid < VARNUMBER_MAX);
tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
tv_dict_set_keys_readonly(dict);
- apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
- NULL, NULL, false, curbuf);
+ apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, NULL, NULL, false, curbuf);
restore_v_event(dict, &save_v_event);
recursive = false;
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index 6dbd18ba7c..9e6c534581 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -12,9 +12,7 @@
#include "nvim/regexp_defs.h"
#include "nvim/types.h"
-struct AutoCmd_S;
struct AutoPatCmd_S;
-struct AutoPat_S;
// event_T definition
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -35,49 +33,45 @@ typedef struct {
int save_State; ///< saved State
} aco_save_T;
-typedef struct AutoCmd_S AutoCmd;
-struct AutoCmd_S {
- AucmdExecutable exec;
- bool once; // "One shot": removed after execution
- bool nested; // If autocommands nest here
- bool last; // last command in list
- int64_t id; // ID used for uniquely tracking an autocmd.
- sctx_T script_ctx; // script context where it is defined
- char *desc; // Description for the autocmd.
- AutoCmd *next; // Next AutoCmd in list
-};
+typedef struct {
+ size_t refcount; ///< Reference count (freed when reaches zero)
+ char *pat; ///< Pattern as typed
+ regprog_T *reg_prog; ///< Compiled regprog for pattern
+ int group; ///< Group ID
+ int patlen; ///< strlen() of pat
+ int buflocal_nr; ///< !=0 for buffer-local AutoPat
+ char allow_dirs; ///< Pattern may match whole path
+} AutoPat;
-typedef struct AutoPat_S AutoPat;
-struct AutoPat_S {
- AutoPat *next; // next AutoPat in AutoPat list; MUST
- // be the first entry
- char *pat; // pattern as typed (NULL when pattern
- // has been removed)
- regprog_T *reg_prog; // compiled regprog for pattern
- AutoCmd *cmds; // list of commands to do
- int group; // group ID
- int patlen; // strlen() of pat
- int buflocal_nr; // !=0 for buffer-local AutoPat
- char allow_dirs; // Pattern may match whole path
- char last; // last pattern for apply_autocmds()
-};
+typedef struct {
+ AucmdExecutable exec; ///< Command or callback function
+ AutoPat *pat; ///< Pattern reference (NULL when autocmd was removed)
+ int64_t id; ///< ID used for uniquely tracking an autocmd
+ char *desc; ///< Description for the autocmd
+ sctx_T script_ctx; ///< Script context where it is defined
+ bool once; ///< "One shot": removed after execution
+ bool nested; ///< If autocommands nest here
+} AutoCmd;
/// Struct used to keep status while executing autocommands for an event.
typedef struct AutoPatCmd_S AutoPatCmd;
struct AutoPatCmd_S {
- AutoPat *curpat; // next AutoPat to examine
- AutoCmd *nextcmd; // next AutoCmd to execute
- int group; // group being used
- char *fname; // fname to match with
- char *sfname; // sfname to match with
- char *tail; // tail of fname
- event_T event; // current event
- sctx_T script_ctx; // script context where it is defined
- int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted
- Object *data; // arbitrary data
- AutoPatCmd *next; // chain of active apc-s for auto-invalidation
+ AutoPat *lastpat; ///< Last matched AutoPat
+ size_t auidx; ///< Current autocmd index to execute
+ size_t ausize; ///< Saved AutoCmd vector size
+ char *fname; ///< Fname to match with
+ char *sfname; ///< Sfname to match with
+ char *tail; ///< Tail of fname
+ int group; ///< Group being used
+ event_T event; ///< Current event
+ sctx_T script_ctx; ///< Script context where it is defined
+ int arg_bufnr; ///< Initially equal to <abuf>, set to zero when buf is deleted
+ Object *data; ///< Arbitrary data
+ AutoPatCmd *next; ///< Chain of active apc-s for auto-invalidation
};
+typedef kvec_t(AutoCmd) AutoCmdVec;
+
// Set by the apply_autocmds_group function if the given event is equal to
// EVENT_FILETYPE. Used by the readfile function in order to determine if
// EVENT_BUFREADPOST triggered the EVENT_FILETYPE.
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 8a2dabaf30..0e6d4bba1b 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1885,8 +1885,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// avoid that a function call in 'statusline' does this
&& !getline_equal(fgetline, cookie, get_func_line)
// avoid that an autocommand, e.g. QuitPre, does this
- && !getline_equal(fgetline, cookie,
- getnextac)) {
+ && !getline_equal(fgetline, cookie, getnextac)) {
quitmore--;
}
diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua
index 27cec40b03..0eb231f012 100644
--- a/src/nvim/generators/gen_events.lua
+++ b/src/nvim/generators/gen_events.lua
@@ -35,27 +35,24 @@ names_tgt:write('\n {0, NULL, (event_T)0},')
enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
-local gen_autopat_events = function(name)
- names_tgt:write(string.format('\nstatic AutoPat *%s[NUM_EVENTS] = {\n ', name))
+do
+ names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = {\n ')
local line_len = 1
for _ = 1,((#events) - 1) do
- line_len = line_len + #(' NULL,')
+ line_len = line_len + #(' KV_INITIAL_VALUE,')
if line_len > 80 then
names_tgt:write('\n ')
- line_len = 1 + #(' NULL,')
+ line_len = 1 + #(' KV_INITIAL_VALUE,')
end
- names_tgt:write(' NULL,')
+ names_tgt:write(' KV_INITIAL_VALUE,')
end
- if line_len + #(' NULL') > 80 then
- names_tgt:write('\n NULL')
+ if line_len + #(' KV_INITIAL_VALUE') > 80 then
+ names_tgt:write('\n KV_INITIAL_VALUE')
else
- names_tgt:write(' NULL')
+ names_tgt:write(' KV_INITIAL_VALUE')
end
names_tgt:write('\n};\n')
end
-gen_autopat_events("first_autopat")
-gen_autopat_events("last_autopat")
-
enum_tgt:close()
names_tgt:close()
diff --git a/test/benchmark/autocmd_spec.lua b/test/benchmark/autocmd_spec.lua
new file mode 100644
index 0000000000..f243d9c94d
--- /dev/null
+++ b/test/benchmark/autocmd_spec.lua
@@ -0,0 +1,175 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+
+local N = 7500
+
+describe('autocmd perf', function()
+ before_each(function()
+ clear()
+
+ exec_lua([[
+ out = {}
+ function start()
+ ts = vim.loop.hrtime()
+ end
+ function stop(name)
+ out[#out+1] = ('%14.6f ms - %s'):format((vim.loop.hrtime() - ts) / 1000000, name)
+ end
+ ]])
+ end)
+
+ after_each(function()
+ for _, line in ipairs(exec_lua([[return out]])) do
+ print(line)
+ end
+ end)
+
+ it('nvim_create_autocmd, nvim_del_autocmd (same pattern)', function()
+ exec_lua([[
+ local N = ...
+ local ids = {}
+
+ start()
+ for i = 1, N do
+ ids[i] = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ end
+ stop('nvim_create_autocmd')
+
+ start()
+ for i = 1, N do
+ vim.api.nvim_del_autocmd(ids[i])
+ end
+ stop('nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_create_autocmd, nvim_del_autocmd (unique patterns)', function()
+ exec_lua([[
+ local N = ...
+ local ids = {}
+
+ start()
+ for i = 1, N do
+ ids[i] = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark' .. i,
+ command = 'eval 0', -- noop
+ })
+ end
+ stop('nvim_create_autocmd')
+
+ start()
+ for i = 1, N do
+ vim.api.nvim_del_autocmd(ids[i])
+ end
+ stop('nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_create_autocmd + nvim_del_autocmd', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for _ = 1, N do
+ local id = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ vim.api.nvim_del_autocmd(id)
+ end
+ stop('nvim_create_autocmd + nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_exec_autocmds (same pattern)', function()
+ exec_lua([[
+ local N = ...
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ end
+
+ start()
+ vim.api.nvim_exec_autocmds('User', { pattern = 'Benchmark', modeline = false })
+ stop('nvim_exec_autocmds')
+ ]], N)
+ end)
+
+ it('nvim_del_augroup_by_id', function()
+ exec_lua([[
+ local N = ...
+ local group = vim.api.nvim_create_augroup('Benchmark', {})
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ group = group,
+ })
+ end
+
+ start()
+ vim.api.nvim_del_augroup_by_id(group)
+ stop('nvim_del_augroup_by_id')
+ ]], N)
+ end)
+
+ it('nvim_del_augroup_by_name', function()
+ exec_lua([[
+ local N = ...
+ local group = vim.api.nvim_create_augroup('Benchmark', {})
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ group = group,
+ })
+ end
+
+ start()
+ vim.api.nvim_del_augroup_by_name('Benchmark')
+ stop('nvim_del_augroup_by_id')
+ ]], N)
+ end)
+
+ it(':autocmd, :autocmd! (same pattern)', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for i = 1, N do
+ vim.cmd('autocmd User Benchmark eval 0')
+ end
+ stop(':autocmd')
+
+ start()
+ vim.cmd('autocmd! User Benchmark')
+ stop(':autocmd!')
+ ]], N)
+ end)
+
+ it(':autocmd, :autocmd! (unique patterns)', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for i = 1, N do
+ vim.cmd(('autocmd User Benchmark%d eval 0'):format(i))
+ end
+ stop(':autocmd')
+
+ start()
+ vim.cmd('autocmd! User')
+ stop(':autocmd!')
+ ]], N)
+ end)
+end)
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index fb5bab445c..82c7f9502f 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -611,4 +611,22 @@ describe('autocmd', function()
eq(4, #meths.get_autocmds { event = "BufReadCmd", group = "TestingPatterns" })
end)
end)
+
+ it('no use-after-free when adding autocommands from a callback', function()
+ exec_lua [[
+ vim.cmd "autocmd! TabNew"
+ vim.g.count = 0
+ vim.api.nvim_create_autocmd('TabNew', {
+ callback = function()
+ vim.g.count = vim.g.count + 1
+ for _ = 1, 100 do
+ vim.cmd "autocmd TabNew * let g:count += 1"
+ end
+ return true
+ end,
+ })
+ vim.cmd "tabnew"
+ ]]
+ eq(1, eval('g:count')) -- Added autocommands should not be executed
+ end)
end)
diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua
index 505bed834b..9e0a5b819a 100644
--- a/test/functional/autocmd/show_spec.lua
+++ b/test/functional/autocmd/show_spec.lua
@@ -180,4 +180,45 @@ describe(":autocmd", function()
test_3 User
B echo "B3"]]), funcs.execute('autocmd test_3 * B'))
end)
+
+ it('should skip consecutive patterns', function()
+ exec([[
+ autocmd! BufEnter
+ augroup test_1
+ autocmd BufEnter A echo 'A'
+ autocmd BufEnter A echo 'B'
+ autocmd BufEnter A echo 'C'
+ autocmd BufEnter B echo 'D'
+ autocmd BufEnter B echo 'E'
+ autocmd BufEnter B echo 'F'
+ augroup END
+ augroup test_2
+ autocmd BufEnter C echo 'A'
+ autocmd BufEnter C echo 'B'
+ autocmd BufEnter C echo 'C'
+ autocmd BufEnter D echo 'D'
+ autocmd BufEnter D echo 'E'
+ autocmd BufEnter D echo 'F'
+ augroup END
+
+ let g:output = execute('autocmd BufEnter')
+ ]])
+ eq(dedent([[
+
+ --- Autocommands ---
+ test_1 BufEnter
+ A echo 'A'
+ echo 'B'
+ echo 'C'
+ B echo 'D'
+ echo 'E'
+ echo 'F'
+ test_2 BufEnter
+ C echo 'A'
+ echo 'B'
+ echo 'C'
+ D echo 'D'
+ echo 'E'
+ echo 'F']]), eval('g:output'))
+ end)
end)