aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/autocmd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvim/autocmd.c')
-rw-r--r--src/nvim/autocmd.c1347
1 files changed, 947 insertions, 400 deletions
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 9117dde089..a9e1978ac0 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -2,10 +2,15 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// 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"
@@ -13,8 +18,11 @@
#include "nvim/eval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/getchar.h"
+#include "nvim/lua/executor.h"
+#include "nvim/map.h"
#include "nvim/option.h"
#include "nvim/os/input.h"
#include "nvim/regexp.h"
@@ -28,6 +36,14 @@
# include "autocmd.c.generated.h"
#endif
+// Naming Conventions:
+// - general autocmd behavior start with au_
+// - AutoCmd start with aucmd_
+// - Autocmd.exec stat with aucmd_exec
+// - AutoPat start with aupat_
+// - Groups start with augroup_
+// - Events start with event_
+
//
// The autocommands are stored in a list for each event.
// Autocommands for the same pattern, that are consecutive, are joined
@@ -67,21 +83,20 @@
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
-/// List of autocmd group names
-static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL };
-#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i])
-#define BUFLOCAL_PAT_LEN 25
+// ID for associating autocmds created via nvim_create_autocmd
+// Used to delete autocmds from nvim_del_autocmd
+static int next_augroup_id = 1;
// use get_deleted_augroup() to get this
static const char *deleted_augroup = NULL;
-// The ID of the current group. Group 0 is the default one.
+// The ID of the current group.
static int current_augroup = AUGROUP_DEFAULT;
-static int au_need_clean = false; // need to delete marked patterns
+// Whether we need to delete marked patterns.
+// While deleting autocmds, they aren't actually remover, just marked.
+static int au_need_clean = false;
-static event_T last_event;
-static int last_group;
static int autocmd_blocked = 0; // block all autocmds
static bool autocmd_nested = false;
@@ -89,6 +104,33 @@ static bool autocmd_include_groups = false;
static char_u *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
+
+/// 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;
+
+static void augroup_map_del(char *name)
+{
+ String key = map_key(String, int)(&augroup_map, cstr_as_string(name));
+ map_del(String, int)(&augroup_map, key);
+ api_free_string(key);
+}
+
+
static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
{
if (deleted_augroup == NULL) {
@@ -98,7 +140,7 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
}
// Show the autocommands for one AutoPat.
-static void show_autocmd(AutoPat *ap, event_T event)
+static void aupat_show(AutoPat *ap)
{
AutoCmd *ac;
@@ -107,39 +149,21 @@ static void show_autocmd(AutoPat *ap, event_T event)
if (got_int) {
return;
}
+
// pattern has been removed
if (ap->pat == NULL) {
return;
}
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- if (event != last_event || ap->group != last_group) {
- if (ap->group != AUGROUP_DEFAULT) {
- if (AUGROUP_NAME(ap->group) == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T));
- }
- msg_puts(" ");
- }
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- last_event = event;
- last_group = ap->group;
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- }
msg_col = 4;
msg_outtrans(ap->pat);
for (ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->cmd == NULL) { // skip removed commands
+ // skip removed commands
+ if (aucmd_exec_is_deleted(ac->exec)) {
continue;
}
+
if (msg_col >= 14) {
msg_putchar('\n');
}
@@ -147,7 +171,7 @@ static void show_autocmd(AutoPat *ap, event_T event)
if (got_int) {
return;
}
- msg_outtrans(ac->cmd);
+ msg_outtrans((char_u *)aucmd_exec_to_string(ac, ac->exec));
if (p_verbose > 0) {
last_set_msg(ac->script_ctx);
}
@@ -163,27 +187,96 @@ static void show_autocmd(AutoPat *ap, event_T event)
}
}
+static void au_show_for_all_events(int group)
+{
+ FOR_ALL_AUEVENTS(event) {
+ au_show_for_event(group, event);
+ }
+}
+
+static void au_show_for_event(int group, event_T event)
+{
+ // Return early if there are no autocmds for this event
+ if (au_event_is_empty(event)) {
+ return;
+ }
+
+ int previous_group = AUGROUP_ERROR;
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (group != AUGROUP_ALL && group != ap->group) {
+ continue;
+ }
+
+ char *name = augroup_name(ap->group);
+
+ msg_putchar('\n');
+ // 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(" ");
+ }
+
+ // show the event name
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
+ msg_putchar('\n');
+ }
+
+ if (got_int) {
+ return;
+ }
+
+ aupat_show(ap);
+
+ previous_group = ap->group;
+ }
+}
+
// Mark an autocommand handler for deletion.
-static void au_remove_pat(AutoPat *ap)
+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)
+{
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == group) {
+ aupat_del(ap);
+ }
+ }
+
+ au_need_clean = true;
+ au_cleanup(); // may really delete removed patterns/commands now
+}
+
// Mark all commands for a pattern for deletion.
-static void au_remove_cmds(AutoPat *ap)
+static void aupat_remove_cmds(AutoPat *ap)
{
for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- XFREE_CLEAR(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
}
au_need_clean = true;
}
// Delete one command from an autocmd pattern.
-static void au_del_cmd(AutoCmd *ac)
+static void aucmd_del(AutoCmd *ac)
{
- XFREE_CLEAR(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
au_need_clean = true;
}
@@ -191,16 +284,15 @@ static void au_del_cmd(AutoCmd *ac)
/// This is only done when not executing autocommands.
static void au_cleanup(void)
{
- AutoPat *ap, **prev_ap;
- event_T event;
+ AutoPat *ap;
+ AutoPat **prev_ap;
if (autocmd_busy || !au_need_clean) {
return;
}
// Loop over all events.
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
+ FOR_ALL_AUEVENTS(event) {
// Loop over all autocommand patterns.
prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
@@ -211,9 +303,13 @@ static void au_cleanup(void)
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 || ac->cmd == NULL) {
+ if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) {
*prev_ac = ac->next;
- xfree(ac->cmd);
+ aucmd_exec_free(&ac->exec);
+ if (ac->desc != NULL) {
+ XFREE_CLEAR(ac->desc);
+ }
+
xfree(ac);
} else {
has_cmd = true;
@@ -224,7 +320,7 @@ static void au_cleanup(void)
if (ap->pat != NULL && !has_cmd) {
// Pattern was not marked for deletion, but all of its commands were.
// So mark the pattern for deletion.
- au_remove_pat(ap);
+ aupat_del(ap);
}
// Remove the pattern if it has been marked for deletion.
@@ -250,12 +346,17 @@ static void au_cleanup(void)
au_need_clean = false;
}
+
+// Get the first AutoPat for a particular event.
+AutoPat *au_get_autopat_for_event(event_T event)
+{
+ return first_autopat[(int)event];
+}
+
// Called when buffer is freed, to remove/invalidate related buffer-local
// autocmds.
void aubuflocal_remove(buf_T *buf)
{
- AutoPat *ap;
- event_T event;
AutoPatCmd *apc;
// invalidate currently executing autocommands
@@ -266,12 +367,11 @@ void aubuflocal_remove(buf_T *buf)
}
// invalidate buflocals looping through events
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- // loop over all autocommand patterns
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
if (ap->buflocal_nr == buf->b_fnum) {
- au_remove_pat(ap);
+ aupat_del(ap);
+
if (p_verbose >= 6) {
verbose_enter();
smsg(_("auto-removing autocommand: %s <buffer=%d>"),
@@ -284,60 +384,61 @@ void aubuflocal_remove(buf_T *buf)
au_cleanup();
}
-// Add an autocmd group name.
+// Add an autocmd group name or return existing group matching name.
// Return its ID.
-static int au_new_group(char_u *name)
+int augroup_add(char *name)
{
- int i = au_find_group(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it.
- // First try using a free entry.
- for (i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) == NULL) {
- break;
- }
- }
- if (i == augroups.ga_len) {
- ga_grow(&augroups, 1);
- }
+ assert(STRICMP(name, "end") != 0);
- AUGROUP_NAME(i) = xstrdup((char *)name);
- if (i == augroups.ga_len) {
- augroups.ga_len++;
- }
+ int existing_id = augroup_find(name);
+ if (existing_id > 0) {
+ assert(existing_id != AUGROUP_DELETED);
+ return existing_id;
}
- return i;
+ if (existing_id == AUGROUP_DELETED) {
+ augroup_map_del(name);
+ }
+
+ int next_id = next_augroup_id++;
+ String name_copy = cstr_to_string(name);
+ map_put(String, int)(&augroup_map, name_copy, next_id);
+
+ return next_id;
}
-static void au_del_group(char_u *name)
+/// Delete the augroup that matches name.
+void augroup_del(char *name, bool stupid_legacy_mode)
{
- int i = au_find_group(name);
+ int i = augroup_find(name);
if (i == AUGROUP_ERROR) { // the group doesn't exist
semsg(_("E367: No such group: \"%s\""), name);
} else if (i == current_augroup) {
emsg(_("E936: Cannot delete the current group"));
} else {
- event_T event;
- AutoPat *ap;
- int in_use = false;
-
- for (event = (event_T)0; (int)event < NUM_EVENTS;
- event = (event_T)((int)event + 1)) {
- for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->group == i && ap->pat != NULL) {
- give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true);
- in_use = true;
- event = NUM_EVENTS;
- break;
+ if (stupid_legacy_mode) {
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == i && ap->pat != NULL) {
+ give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true);
+ map_put(String, int)(&augroup_map, cstr_as_string(name), AUGROUP_DELETED);
+ return;
+ }
}
}
- }
- xfree(AUGROUP_NAME(i));
- if (in_use) {
- AUGROUP_NAME(i) = (char *)get_deleted_augroup();
} else {
- AUGROUP_NAME(i) = NULL;
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ if (ap->group == i) {
+ aupat_del(ap);
+ }
+ }
+ }
}
+
+ // Remove the group because it's not currently in use.
+ augroup_map_del(name);
+ au_cleanup();
}
}
@@ -346,25 +447,70 @@ static void au_del_group(char_u *name)
/// @param name augroup name
///
/// @return the ID or AUGROUP_ERROR (< 0) for error.
-static int au_find_group(const char_u *name)
+int augroup_find(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (int i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup()
- && STRCMP(AUGROUP_NAME(i), name) == 0) {
- return i;
- }
+ int existing_id = map_get(String, int)(&augroup_map, cstr_as_string((char *)name));
+ if (existing_id == AUGROUP_DELETED) {
+ return existing_id;
+ }
+
+ if (existing_id > 0) {
+ return existing_id;
}
+
return AUGROUP_ERROR;
}
+/// Gets the name for a particular group.
+char *augroup_name(int group)
+{
+ assert(group != 0);
+
+ if (group == AUGROUP_DELETED) {
+ return (char *)get_deleted_augroup();
+ }
+
+ if (group == AUGROUP_ALL) {
+ group = current_augroup;
+ }
+
+ // next_augroup_id is the "source of truth" about what autocmds have existed
+ //
+ // The map_size is not the source of truth because groups can be removed from
+ // the map. When this happens, the map size is reduced. That's why this function
+ // relies on next_augroup_id instead.
+
+ // "END" is always considered the last augroup ID.
+ // Used for expand_get_event_name and expand_get_augroup_name
+ if (group == next_augroup_id) {
+ return "END";
+ }
+
+ // If it's larger than the largest group, then it doesn't have a name
+ if (group > next_augroup_id) {
+ return NULL;
+ }
+
+ String key;
+ int value;
+ map_foreach(&augroup_map, key, value, {
+ if (value == group) {
+ return key.data;
+ }
+ });
+
+ // If it's not in the map anymore, then it must have been deleted.
+ return (char *)get_deleted_augroup();
+}
+
/// Return true if augroup "name" exists.
///
/// @param name augroup name
-bool au_has_group(const char_u *name)
+bool augroup_exists(const char *name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return au_find_group(name) != AUGROUP_ERROR;
+ return augroup_find(name) > 0;
}
/// ":augroup {name}".
@@ -374,20 +520,27 @@ void do_augroup(char_u *arg, int del_group)
if (*arg == NUL) {
emsg(_(e_argreq));
} else {
- au_del_group(arg);
+ augroup_del((char *)arg, true);
}
} else if (STRICMP(arg, "end") == 0) { // ":aug end": back to group 0
current_augroup = AUGROUP_DEFAULT;
} else if (*arg) { // ":aug xxx": switch to group xxx
- current_augroup = au_new_group(arg);
+ current_augroup = augroup_add((char *)arg);
} else { // ":aug": list the group names
msg_start();
- for (int i = 0; i < augroups.ga_len; i++) {
- if (AUGROUP_NAME(i) != NULL) {
- msg_puts(AUGROUP_NAME(i));
- msg_puts(" ");
+
+ String name;
+ int value;
+ map_foreach(&augroup_map, name, value, {
+ if (value > 0) {
+ msg_puts(name.data);
+ } else {
+ msg_puts(augroup_name(value));
}
- }
+
+ msg_puts(" ");
+ });
+
msg_clr_eos();
msg_end();
}
@@ -396,25 +549,30 @@ void do_augroup(char_u *arg, int del_group)
#if defined(EXITFREE)
void free_all_autocmds(void)
{
- for (current_augroup = -1; current_augroup < augroups.ga_len;
- current_augroup++) {
- do_autocmd((char_u *)"", true);
- }
-
- for (int i = 0; i < augroups.ga_len; i++) {
- char *const s = ((char **)(augroups.ga_data))[i];
- if ((const char *)s != get_deleted_augroup()) {
- xfree(s);
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ aupat_del(ap);
}
}
- ga_clear(&augroups);
+
+ au_need_clean = true;
+ au_cleanup(); // may really delete removed patterns/commands now
+
+ // Delete the augroup_map, including free the data
+ String name;
+ int id;
+ map_foreach(&augroup_map, name, id, {
+ (void)id;
+ api_free_string(name);
+ })
+ map_destroy(String, int)(&augroup_map);
}
#endif
// Return the event number for event name "start".
// Return NUM_EVENTS if the event name was not found.
// Return a pointer to the next event name in "end".
-static event_T event_name2nr(const char_u *start, char_u **end)
+event_T event_name2nr(const char_u *start, char_u **end)
{
const char_u *p;
int i;
@@ -457,33 +615,6 @@ static const char *event_nr2name(event_T event)
return "Unknown";
}
-/// Scan over the events. "*" stands for all events.
-/// true when group name was found
-static char_u *find_end_event(char_u *arg, int have_group)
-{
- char_u *pat;
- char_u *p;
-
- if (*arg == '*') {
- if (arg[1] && !ascii_iswhite(arg[1])) {
- semsg(_("E215: Illegal character after *: %s"), arg);
- return NULL;
- }
- pat = arg + 1;
- } else {
- for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
- if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) {
- if (have_group) {
- semsg(_("E216: No such event: %s"), pat);
- } else {
- semsg(_("E216: No such group or event: %s"), pat);
- }
- return NULL;
- }
- }
- }
- return pat;
-}
/// Return true if "event" is included in 'eventignore'.
///
@@ -601,12 +732,12 @@ void do_autocmd(char_u *arg_in, int forceit)
group = AUGROUP_ALL; // no argument, use all groups
} else {
// Check for a legal group name. If not, use AUGROUP_ALL.
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
}
// Scan over the events.
// If we find an illegal name, return here, don't do anything.
- pat = find_end_event(arg, group != AUGROUP_ALL);
+ pat = arg_event_skip(arg, group != AUGROUP_ALL);
if (pat == NULL) {
return;
}
@@ -643,37 +774,22 @@ void do_autocmd(char_u *arg_in, int forceit)
}
cmd = skipwhite(cmd);
+
+ bool invalid_flags = false;
for (size_t i = 0; i < 2; i++) {
if (*cmd != NUL) {
- // Check for "++once" flag.
- if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (once) {
- semsg(_(e_duparg2), "++once");
- }
- once = true;
- cmd = skipwhite(cmd + 6);
- }
+ ARG_GET_FLAG(invalid_flags, cmd, once, "++once", 6);
+ ARG_GET_FLAG(invalid_flags, cmd, nested, "++nested", 8);
- // Check for "++nested" flag.
- if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) {
- if (nested) {
- semsg(_(e_duparg2), "++nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 8);
- }
-
- // Check for the old (deprecated) "nested" flag.
- if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) {
- if (nested) {
- semsg(_(e_duparg2), "nested");
- }
- nested = true;
- cmd = skipwhite(cmd + 6);
- }
+ // Check the deprecated "nested" flag.
+ ARG_GET_FLAG(invalid_flags, cmd, nested, "nested", 6);
}
}
+ if (invalid_flags) {
+ return;
+ }
+
// Find the start of the commands.
// Expand <sfile> in it.
if (*cmd != NUL) {
@@ -685,36 +801,37 @@ void do_autocmd(char_u *arg_in, int forceit)
}
}
+ bool is_showing = !forceit && *cmd == NUL;
+
// Print header when showing autocommands.
- if (!forceit && *cmd == NUL) {
+ if (is_showing) {
// Highlight title
msg_puts_title(_("\n--- Autocommands ---"));
- }
- // Loop over the events.
- last_event = (event_T)-1; // for listing the event name
- last_group = AUGROUP_ERROR; // for listing the group name
- if (*arg == '*' || *arg == NUL || *arg == '|') {
- if (!forceit && *cmd != NUL) {
- emsg(_(e_cannot_define_autocommands_for_all_events));
+ if (*arg == '*' || *arg == '|' || *arg == NUL) {
+ au_show_for_all_events(group);
} else {
- for (event_T event = (event_T)0; event < NUM_EVENTS;
- event = (event_T)(event + 1)) {
+ event_T event = event_name2nr(arg, &arg);
+ assert(event < NUM_EVENTS);
+ au_show_for_event(group, event);
+ }
+ } else {
+ if (*arg == '*' || *arg == NUL || *arg == '|') {
+ if (!forceit && *cmd != NUL) {
+ emsg(_(e_cannot_define_autocommands_for_all_events));
+ } else {
+ do_all_autocmd_events(pat, once, nested, cmd, forceit, group);
+ }
+ } else {
+ 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) {
break;
}
}
}
- } else {
- 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) {
- break;
- }
- }
}
if (need_free) {
@@ -723,30 +840,14 @@ void do_autocmd(char_u *arg_in, int forceit)
xfree(envpat);
}
-// Find the group ID in a ":autocmd" or ":doautocmd" argument.
-// The "argp" argument is advanced to the following argument.
-//
-// Returns the group ID or AUGROUP_ALL.
-static int au_get_grouparg(char_u **argp)
+void do_all_autocmd_events(char_u *pat, bool once, int nested, char_u *cmd, bool delete, int group)
{
- char_u *group_name;
- char_u *p;
- char_u *arg = *argp;
- int group = AUGROUP_ALL;
-
- for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
- }
- if (p > arg) {
- group_name = vim_strnsave(arg, (size_t)(p - arg));
- group = au_find_group(group_name);
- if (group == AUGROUP_ERROR) {
- group = AUGROUP_ALL; // no match, use all groups
- } else {
- *argp = skipwhite(p); // match, skip over group name
+ FOR_ALL_AUEVENTS(event) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, delete, group)
+ == FAIL) {
+ return;
}
- xfree(group_name);
}
- return group;
}
// do_autocmd() for one event.
@@ -756,216 +857,293 @@ static int au_get_grouparg(char_u **argp)
// If *cmd == NUL: show entries.
// If forceit == true: delete entries.
// If group is not AUGROUP_ALL: only use this group.
-static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd,
- int forceit, int group)
+int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, bool delete,
+ 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 || delete);
+
AutoPat *ap;
AutoPat **prev_ap;
- AutoCmd *ac;
- AutoCmd **prev_ac;
- int brace_level;
- char_u *endpat;
int findgroup;
- int allgroups;
int patlen;
int is_buflocal;
int buflocal_nr;
char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+ bool is_adding_cmd = *cmd != NUL;
+
if (group == AUGROUP_ALL) {
findgroup = current_augroup;
} else {
findgroup = group;
}
- allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL);
- // Show or delete all patterns for an event.
- if (*pat == NUL) {
- for (ap = first_autopat[event]; ap != NULL; ap = ap->next) {
- if (forceit) { // delete the AutoPat, if it's in the current group
- if (ap->group == findgroup) {
- au_remove_pat(ap);
- }
- } else if (group == AUGROUP_ALL || ap->group == group) {
- show_autocmd(ap, event);
- }
- }
+ // Delete all aupat for an event.
+ if (*pat == NUL && delete) {
+ aupat_del_for_event_and_group(event, findgroup);
+ return OK;
}
// Loop through all the specified patterns.
- for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
- // Find end of the pattern.
- // Watch out for a comma in braces, like "*.\{obj,o\}".
- endpat = pat;
- // ignore single comma
- if (*endpat == ',') {
- continue;
- }
- brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- endpat++) {
- if (*endpat == '{') {
- brace_level++;
- } else if (*endpat == '}') {
- brace_level--;
- }
- }
- patlen = (int)(endpat - pat);
-
- // detect special <buflocal[=X]> buffer-local patterns
- is_buflocal = false;
+ patlen = (int)aucmd_pattern_length(pat);
+ while (patlen) {
+ // detect special <buffer[=X]> buffer-local patterns
+ is_buflocal = aupat_is_buflocal(pat, patlen);
buflocal_nr = 0;
- if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0
- && pat[patlen - 1] == '>') {
- // "<buffer...>": Error will be printed only for addition.
- // printing and removing will proceed silently.
- is_buflocal = true;
- if (patlen == 8) {
- // "<buffer>"
- buflocal_nr = curbuf->b_fnum;
- } else if (patlen > 9 && pat[7] == '=') {
- if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) {
- // "<buffer=abuf>"
- buflocal_nr = autocmd_bufnr;
- } else if (skipdigits(pat + 8) == pat + patlen - 1) {
- // "<buffer=123>"
- buflocal_nr = atoi((char *)pat + 8);
- }
- }
- }
-
if (is_buflocal) {
+ buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+
// normalize pat into standard "<buffer>#N" form
- snprintf((char *)buflocal_pat,
- BUFLOCAL_PAT_LEN,
- "<buffer=%d>",
- buflocal_nr);
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
- pat = buflocal_pat; // can modify pat and patlen
- patlen = (int)STRLEN(buflocal_pat); // but not endpat
+ pat = buflocal_pat;
+ patlen = (int)STRLEN(buflocal_pat);
}
- // Find AutoPat entries with this pattern. When adding a command it
- // always goes at or after the last one, so start at the end.
- if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
+ if (delete) {
+ assert(*pat != NUL);
+
+ // Find AutoPat entries with this pattern.
+ // always goes at or after the last one, so start at the end.
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, or a group was
- // not specified and it's the current group, or a group was
- // not specified and we are listing
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if ((allgroups || ap->group == findgroup) && ap->patlen == patlen
- && STRNCMP(pat, ap->pat, 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 (forceit) {
- if (*cmd != NUL && ap->next == NULL) {
- au_remove_cmds(ap);
+ 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, 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;
}
- au_remove_pat(ap);
- } else if (*cmd == NUL) {
- // Show autocmd's for this autopat, or buflocals <buffer=X>
- show_autocmd(ap, event);
- } else if (ap->next == NULL) {
- // Add autocmd to this autopat, if it's the last one.
- break;
+ aupat_del(ap);
}
}
+ prev_ap = &ap->next;
}
- prev_ap = &ap->next;
}
- // Add a new command.
- if (*cmd != NUL) {
- // 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.
- 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)) {
- semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
- return FAIL;
- }
+ if (is_adding_cmd) {
+ AucmdExecutable exec = AUCMD_EXECUTABLE_INIT;
+ exec.type = CALLABLE_EX;
+ exec.callable.cmd = cmd;
+ autocmd_register(0, event, pat, patlen, group, once, nested, NULL, exec);
+ }
- ap = xmalloc(sizeof(AutoPat));
- ap->pat = vim_strnsave(pat, (size_t)patlen);
- ap->patlen = patlen;
+ pat = aucmd_next_pattern(pat, (size_t)patlen);
+ patlen = (int)aucmd_pattern_length(pat);
+ }
- if (is_buflocal) {
- ap->buflocal_nr = buflocal_nr;
- ap->reg_prog = NULL;
- } else {
- char_u *reg_pat;
+ au_cleanup();
+ return OK;
+}
- ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, endpat, &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;
- }
- }
+int autocmd_register(int64_t id, event_T event, char_u *pat, int patlen, int group, bool once,
+ bool nested, char *desc, AucmdExecutable aucmd)
+{
+ // 0 is not a valid group.
+ assert(group != 0);
- // need to initialize last_mode for the first ModeChanged autocmd
- if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
- xfree(last_mode);
- last_mode = get_mode();
- }
+ AutoPat *ap;
+ AutoPat **prev_ap;
+ AutoCmd *ac;
+ AutoCmd **prev_ac;
+ int is_buflocal;
+ int buflocal_nr;
+ int findgroup;
+ char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
- // If the event is CursorMoved, update the last cursor position
- // position to avoid immediately triggering the autocommand
- if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
- curwin->w_last_cursormoved = curwin->w_cursor;
- }
+ if (patlen > (int)STRLEN(pat)) {
+ return FAIL;
+ }
- 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;
+ if (group == AUGROUP_ALL) {
+ findgroup = current_augroup;
+ } else {
+ findgroup = group;
+ }
+
+
+ // detect special <buffer[=X]> buffer-local patterns
+ is_buflocal = aupat_is_buflocal(pat, patlen);
+ buflocal_nr = 0;
+
+ if (is_buflocal) {
+ 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);
+
+ pat = buflocal_pat;
+ patlen = (int)STRLEN(buflocal_pat);
+ }
+
+ 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, patlen) == 0) {
+ if (ap->next == NULL) {
+ // Add autocmd to this autopat, if it's the last one.
+ break;
}
}
+ }
+ prev_ap = &ap->next;
+ }
+
+ // 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.
+ 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)) {
+ semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
+ return FAIL;
+ }
+
+ ap = xmalloc(sizeof(AutoPat));
+ ap->pat = vim_strnsave(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
+ if (is_buflocal) {
+ ap->buflocal_nr = buflocal_nr;
+ ap->reg_prog = NULL;
+ } else {
+ char_u *reg_pat;
- // Add the autocmd at the end of the AutoCmd list.
- prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL) {
- prev_ac = &ac->next;
+ ap->buflocal_nr = 0;
+ 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;
}
- ac = xmalloc(sizeof(AutoCmd));
- ac->cmd = vim_strsave(cmd);
- ac->script_ctx = current_sctx;
- ac->script_ctx.sc_lnum += sourcing_lnum;
- ac->next = NULL;
- *prev_ac = ac;
- ac->once = once;
- ac->nested = nested;
+ }
+
+ // need to initialize last_mode for the first ModeChanged autocmd
+ if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
+ xfree(last_mode);
+ last_mode = get_mode();
+ }
+
+ // If the event is CursorMoved, update the last cursor position
+ // position to avoid immediately triggering the autocommand
+ if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
+ curwin->w_last_cursormoved = curwin->w_cursor;
+ }
+
+ 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;
}
}
- au_cleanup(); // may really delete removed patterns/commands now
+ // Add the autocmd at the end of the AutoCmd list.
+ prev_ac = &(ap->cmds);
+ while ((ac = *prev_ac) != NULL) {
+ prev_ac = &ac->next;
+ }
+
+ ac = xmalloc(sizeof(AutoCmd));
+ *prev_ac = ac;
+
+ ac->id = id;
+ ac->exec = aucmd_exec_copy(aucmd);
+ ac->script_ctx = current_sctx;
+ ac->script_ctx.sc_lnum += sourcing_lnum;
+ 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);
+ } else {
+ ac->desc = aucmd_exec_default_desc(aucmd);
+ }
+
return OK;
}
+size_t aucmd_pattern_length(char_u *pat)
+{
+ if (*pat == NUL) {
+ return 0;
+ }
+
+ char_u *endpat;
+
+ for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) {
+ // Find end of the pattern.
+ // Watch out for a comma in braces, like "*.\{obj,o\}".
+ endpat = pat;
+ // ignore single comma
+ if (*endpat == ',') {
+ continue;
+ }
+ int brace_level = 0;
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
+ endpat++) {
+ if (*endpat == '{') {
+ brace_level++;
+ } else if (*endpat == '}') {
+ brace_level--;
+ }
+ }
+
+ return (size_t)(endpat - pat);
+ }
+
+ return STRLEN(pat);
+}
+
+char_u *aucmd_next_pattern(char_u *pat, size_t patlen)
+{
+ pat = pat + patlen;
+ if (*pat == ',') {
+ pat = pat + 1;
+ }
+
+ return pat;
+}
+
/// Implementation of ":doautocmd [group] event [fname]".
/// Return OK for success, FAIL for failure;
///
@@ -981,7 +1159,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something)
}
// Check for a legal group name. If not, use AUGROUP_ALL.
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
if (*arg == '*') {
emsg(_("E217: Can't execute autocommands for ALL events"));
@@ -990,7 +1168,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something)
// Scan over the events.
// If we find an illegal name, return here, don't do anything.
- fname = find_end_event(arg, group != AUGROUP_ALL);
+ fname = arg_event_skip(arg, group != AUGROUP_ALL);
if (fname == NULL) {
return FAIL;
}
@@ -1378,8 +1556,8 @@ bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
/// @param eap Ex command arguments
///
/// @return true if some commands were executed.
-static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force,
- int group, buf_T *buf, exarg_T *eap)
+bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, int group,
+ buf_T *buf, exarg_T *eap)
{
char_u *sfname = NULL; // short file name
char_u *tail;
@@ -1402,7 +1580,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io,
proftime_T wait_time;
bool did_save_redobuff = false;
save_redo_T save_redo;
- const bool save_KeyTyped = KeyTyped;
+ const bool save_KeyTyped = KeyTyped; // NOLINT
// Quickly return if there are no autocommands for this event or
// autocommands are blocked.
@@ -1814,6 +1992,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
/// @return allocated string, or NULL for end of autocommands.
char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
{
+ // These arguments are required for do_cmdline.
+ (void)c;
+ (void)indent;
+ (void)do_concat;
+
AutoPatCmd *acp = (AutoPatCmd *)cookie;
char_u *retval;
AutoCmd *ac;
@@ -1826,7 +2009,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
// repeat until we find an autocommand to execute
for (;;) {
// skip removed commands
- while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) {
+ while (acp->nextcmd != NULL
+ && aucmd_exec_is_deleted(acp->nextcmd->exec)) {
if (acp->nextcmd->last) {
acp->nextcmd = NULL;
} else {
@@ -1856,14 +2040,32 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
if (p_verbose >= 9) {
verbose_enter_scroll();
- smsg(_("autocommand %s"), ac->cmd);
+ smsg(_("autocommand %s"), aucmd_exec_to_string(ac, ac->exec));
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
- retval = vim_strsave(ac->cmd);
+
+ if (ac->exec.type == CALLABLE_CB) {
+ typval_T argsin = TV_INITIAL_VALUE;
+ typval_T rettv = TV_INITIAL_VALUE;
+ callback_call(&ac->exec.callable.cb, 0, &argsin, &rettv);
+
+ // 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:
+ // 1. do_cmdline to accept something besides a string
+ // 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.
+ retval = vim_strsave((char_u *)"");
+ } else {
+ retval = vim_strsave(ac->exec.callable.cmd);
+ }
+
// Remove one-shot ("once") autocmd in anticipation of its execution.
if (ac->once) {
- au_del_cmd(ac);
+ aucmd_del(ac);
}
autocmd_nested = ac->nested;
current_sctx = ac->script_ctx;
@@ -1928,19 +2130,12 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE
// Function given to ExpandGeneric() to obtain the list of autocommand group
// names.
-char_u *get_augroup_name(expand_T *xp, int idx)
+char_u *expand_get_augroup_name(expand_T *xp, int idx)
{
- if (idx == augroups.ga_len) { // add "END" add the end
- return (char_u *)"END";
- }
- if (idx >= augroups.ga_len) { // end of list
- return NULL;
- }
- if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- // skip deleted entries
- return (char_u *)"";
- }
- return (char_u *)AUGROUP_NAME(idx);
+ // Required for ExpandGeneric
+ (void)xp;
+
+ return (char_u *)augroup_name(idx + 1);
}
/// @param doautocmd true for :doauto*, false for :autocmd
@@ -1952,7 +2147,7 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd)
// check for a group name, skip it if present
autocmd_include_groups = false;
p = arg;
- group = au_get_grouparg(&arg);
+ group = arg_augroup_get(&arg);
// If there only is a group name that's what we expand.
if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) {
@@ -1993,16 +2188,25 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd)
}
// Function given to ExpandGeneric() to obtain the list of event names.
-char_u *get_event_name(expand_T *xp, int idx)
+char_u *expand_get_event_name(expand_T *xp, int idx)
{
- if (idx < augroups.ga_len) { // First list group names, if wanted
- if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL
- || AUGROUP_NAME(idx) == get_deleted_augroup()) {
- return (char_u *)""; // skip deleted entries
+ // xp is a required parameter to be used with ExpandGeneric
+ (void)xp;
+
+
+ // List group names
+ char *name = augroup_name(idx + 1);
+ if (name != NULL) {
+ // skip when not including groups or skip deleted entries
+ if (!autocmd_include_groups || name == get_deleted_augroup()) {
+ return (char_u *)"";
}
- return (char_u *)AUGROUP_NAME(idx);
+
+ return (char_u *)name;
}
- return (char_u *)event_names[idx - augroups.ga_len].name;
+
+ // List event names
+ return (char_u *)event_names[idx - next_augroup_id].name;
}
/// Check whether given autocommand is supported
@@ -2045,7 +2249,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
}
// First, look for an autocmd group name.
- group = au_find_group((char_u *)arg_save);
+ group = augroup_find(arg_save);
char *event_name;
if (group == AUGROUP_ERROR) {
// Didn't match a group name, assume the first argument is an event.
@@ -2109,3 +2313,346 @@ theend:
xfree(arg_save);
return retval;
}
+
+// Checks if a pattern is buflocal
+bool aupat_is_buflocal(char_u *pat, int patlen)
+{
+ return patlen >= 8
+ && STRNCMP(pat, "<buffer", 7) == 0
+ && (pat)[patlen - 1] == '>';
+}
+
+int aupat_get_buflocal_nr(char_u *pat, int patlen)
+{
+ assert(aupat_is_buflocal(pat, patlen));
+
+ // "<buffer>"
+ if (patlen == 8) {
+ return curbuf->b_fnum;
+ }
+
+ if (patlen > 9 && (pat)[7] == '=') {
+ // "<buffer=abuf>"
+ if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) {
+ return autocmd_bufnr;
+ }
+
+ // "<buffer=123>"
+ if (skipdigits(pat + 8) == pat + patlen - 1) {
+ return atoi((char *)pat + 8);
+ }
+ }
+
+ return 0;
+}
+
+// normalize buffer pattern
+void aupat_normalize_buflocal_pat(char_u *dest, char_u *pat, int patlen, int buflocal_nr)
+{
+ assert(aupat_is_buflocal(pat, patlen));
+
+ if (buflocal_nr == 0) {
+ buflocal_nr = curbuf->handle;
+ }
+
+ // normalize pat into standard "<buffer>#N" form
+ snprintf((char *)dest,
+ BUFLOCAL_PAT_LEN,
+ "<buffer=%d>",
+ buflocal_nr);
+}
+
+int autocmd_delete_event(int group, event_T event, char_u *pat)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return do_autocmd_event(event, pat, false, false, (char_u *)"", true, group);
+}
+
+/// Deletes an autocmd by ID.
+/// Only autocmds created via the API have IDs associated with them. There
+/// is no way to delete a specific autocmd created via :autocmd
+void autocmd_delete_id(int64_t id)
+{
+ FOR_ALL_AUEVENTS(event) {
+ FOR_ALL_AUPATS_IN_EVENT(event, ap) {
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ if (ac->id == id) {
+ aucmd_del(ac);
+ }
+ }
+ }
+ }
+}
+
+// ===========================================================================
+// AucmdExecutable Functions
+// ===========================================================================
+char *aucmd_exec_default_desc(AucmdExecutable acc)
+{
+ size_t msglen = 100;
+
+ switch (acc.type) {
+ case CALLABLE_CB:
+ switch (acc.callable.cb.type) {
+ case kCallbackLua: {
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<Lua function %d>", acc.callable.cb.data.luaref);
+ return msg;
+ }
+ case kCallbackFuncref: {
+ // TODO(tjdevries): Is this enough space for this?
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<vim function: %s>", acc.callable.cb.data.funcref);
+ return msg;
+ }
+ case kCallbackPartial: {
+ char *msg = (char *)xmallocz(msglen);
+ snprintf(msg, msglen, "<vim partial: %s>", acc.callable.cb.data.partial->pt_name);
+ return msg;
+ }
+ default:
+ return NULL;
+ }
+ default:
+ return NULL;
+ }
+
+ abort();
+}
+char *aucmd_exec_to_string(AutoCmd *ac, AucmdExecutable acc)
+{
+ switch (acc.type) {
+ case CALLABLE_EX:
+ return (char *)acc.callable.cmd;
+ case CALLABLE_CB:
+ return ac->desc;
+ case CALLABLE_NONE:
+ return "This is not possible";
+ }
+
+ abort();
+}
+
+void aucmd_exec_free(AucmdExecutable *acc)
+{
+ switch (acc->type) {
+ case CALLABLE_EX:
+ XFREE_CLEAR(acc->callable.cmd);
+ break;
+ case CALLABLE_CB:
+ callback_free(&acc->callable.cb);
+ break;
+ case CALLABLE_NONE:
+ return;
+ }
+
+ acc->type = CALLABLE_NONE;
+}
+
+AucmdExecutable aucmd_exec_copy(AucmdExecutable src)
+{
+ AucmdExecutable dest = AUCMD_EXECUTABLE_INIT;
+
+ switch (src.type) {
+ case CALLABLE_EX:
+ dest.type = CALLABLE_EX;
+ dest.callable.cmd = vim_strsave(src.callable.cmd);
+ return dest;
+ case CALLABLE_CB:
+ dest.type = CALLABLE_CB;
+ callback_copy(&dest.callable.cb, &src.callable.cb);
+ return dest;
+ case CALLABLE_NONE:
+ return dest;
+ }
+
+ abort();
+}
+
+bool aucmd_exec_is_deleted(AucmdExecutable acc)
+{
+ switch (acc.type) {
+ case CALLABLE_EX:
+ return acc.callable.cmd == NULL;
+ case CALLABLE_CB:
+ return callback_is_freed(acc.callable.cb);
+ case CALLABLE_NONE:
+ return true;
+ }
+
+ abort();
+}
+
+bool au_event_is_empty(event_T event)
+{
+ return first_autopat[event] == NULL;
+}
+
+// Arg Parsing Functions
+
+/// Scan over the events. "*" stands for all events.
+/// true when group name was found
+static char_u *arg_event_skip(char_u *arg, int have_group)
+{
+ char_u *pat;
+ char_u *p;
+
+ if (*arg == '*') {
+ if (arg[1] && !ascii_iswhite(arg[1])) {
+ semsg(_("E215: Illegal character after *: %s"), arg);
+ return NULL;
+ }
+ pat = arg + 1;
+ } else {
+ for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) {
+ if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) {
+ if (have_group) {
+ semsg(_("E216: No such event: %s"), pat);
+ } else {
+ semsg(_("E216: No such group or event: %s"), pat);
+ }
+ return NULL;
+ }
+ }
+ }
+ return pat;
+}
+
+// Find the group ID in a ":autocmd" or ":doautocmd" argument.
+// The "argp" argument is advanced to the following argument.
+//
+// Returns the group ID or AUGROUP_ALL.
+static int arg_augroup_get(char_u **argp)
+{
+ char_u *group_name;
+ char_u *p;
+ char_u *arg = *argp;
+ int group = AUGROUP_ALL;
+
+ for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) {
+ }
+ if (p > arg) {
+ group_name = vim_strnsave(arg, (size_t)(p - arg));
+ group = augroup_find((char *)group_name);
+ if (group == AUGROUP_ERROR) {
+ group = AUGROUP_ALL; // no match, use all groups
+ } else {
+ *argp = skipwhite(p); // match, skip over group name
+ }
+ xfree(group_name);
+ }
+ return group;
+}
+
+
+// UI Enter
+void do_autocmd_uienter(uint64_t chanid, bool attached)
+{
+ static bool recursive = false;
+
+ if (recursive) {
+ return; // disallow recursion
+ }
+ recursive = true;
+
+ save_v_event_T save_v_event;
+ dict_T *dict = get_v_event(&save_v_event);
+ 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);
+ restore_v_event(dict, &save_v_event);
+
+ recursive = false;
+}
+
+// FocusGained
+
+static void focusgained_event(void **argv)
+{
+ bool *gainedp = argv[0];
+ do_autocmd_focusgained(*gainedp);
+ xfree(gainedp);
+}
+
+void autocmd_schedule_focusgained(bool gained)
+{
+ bool *gainedp = xmalloc(sizeof(*gainedp));
+ *gainedp = gained;
+ loop_schedule_deferred(&main_loop,
+ event_create(focusgained_event, 1, gainedp));
+}
+
+static void do_autocmd_focusgained(bool gained)
+{
+ static bool recursive = false;
+ static Timestamp last_time = (time_t)0;
+ bool need_redraw = false;
+
+ if (recursive) {
+ return; // disallow recursion
+ }
+ recursive = true;
+ need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
+ NULL, NULL, false, curbuf);
+
+ // When activated: Check if any file was modified outside of Vim.
+ // Only do this when not done within the last two seconds as:
+ // 1. Some filesystems have modification time granularity in seconds. Fat32
+ // has a granularity of 2 seconds.
+ // 2. We could get multiple notifications in a row.
+ if (gained && last_time + (Timestamp)2000 < os_now()) {
+ need_redraw = check_timestamps(true);
+ last_time = os_now();
+ }
+
+ if (need_redraw) {
+ // Something was executed, make sure the cursor is put back where it
+ // belongs.
+ need_wait_return = false;
+
+ if (State & CMDLINE) {
+ redrawcmdline();
+ } else if ((State & NORMAL) || (State & INSERT)) {
+ if (must_redraw != 0) {
+ update_screen(0);
+ }
+
+ setcursor();
+ }
+
+ ui_flush();
+ }
+
+ if (need_maketitle) {
+ maketitle();
+ }
+
+ recursive = false;
+}
+
+// initialization
+
+void init_default_autocmds(void)
+{
+ // open terminals when opening files that start with term://
+#define PROTO "term://"
+ do_cmdline_cmd("augroup nvim_terminal");
+ do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested "
+ "if !exists('b:term_title')|call termopen("
+ // Capture the command string
+ "matchstr(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
+ // capture the working directory
+ "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
+ "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
+ "|endif");
+ do_cmdline_cmd("augroup END");
+#undef PROTO
+
+ // limit syntax synchronization in the command window
+ do_cmdline_cmd("augroup nvim_cmdwin");
+ do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1");
+ do_cmdline_cmd("augroup END");
+}