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.c2092
1 files changed, 2092 insertions, 0 deletions
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
new file mode 100644
index 0000000000..53b11c250e
--- /dev/null
+++ b/src/nvim/autocmd.c
@@ -0,0 +1,2092 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// autocmd.c: Autocommand related functions
+
+#include "nvim/autocmd.h"
+
+#include "nvim/api/private/handle.h"
+#include "nvim/ascii.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/edit.h"
+#include "nvim/eval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/fileio.h"
+#include "nvim/getchar.h"
+#include "nvim/misc1.h"
+#include "nvim/option.h"
+#include "nvim/regexp.h"
+#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/ui_compositor.h"
+#include "nvim/vim.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+#include "auevents_name_map.generated.h"
+#include "autocmd.c.generated.h"
+#endif
+
+//
+// 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.
+//
+
+// 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
+
+// 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.
+static int current_augroup = AUGROUP_DEFAULT;
+
+static int au_need_clean = false; // need to delete marked patterns
+
+static event_T last_event;
+static int last_group;
+static int autocmd_blocked = 0; // block all autocmds
+
+static bool autocmd_nested = false;
+static bool autocmd_include_groups = false;
+
+static char_u *old_termresponse = NULL;
+
+static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
+{
+ if (deleted_augroup == NULL) {
+ deleted_augroup = _("--Deleted--");
+ }
+ return deleted_augroup;
+}
+
+// Show the autocommands for one AutoPat.
+static void show_autocmd(AutoPat *ap, event_T event)
+{
+ AutoCmd *ac;
+
+ // 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;
+ }
+ // 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
+ continue;
+ }
+ if (msg_col >= 14) {
+ msg_putchar('\n');
+ }
+ msg_col = 14;
+ if (got_int) {
+ return;
+ }
+ msg_outtrans(ac->cmd);
+ 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;
+ }
+ }
+ }
+}
+
+// Mark an autocommand handler for deletion.
+static void au_remove_pat(AutoPat *ap)
+{
+ XFREE_CLEAR(ap->pat);
+ ap->buflocal_nr = -1;
+ au_need_clean = true;
+}
+
+// Mark all commands for a pattern for deletion.
+static void au_remove_cmds(AutoPat *ap)
+{
+ for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
+ XFREE_CLEAR(ac->cmd);
+ }
+ au_need_clean = true;
+}
+
+// Delete one command from an autocmd pattern.
+static void au_del_cmd(AutoCmd *ac)
+{
+ XFREE_CLEAR(ac->cmd);
+ au_need_clean = true;
+}
+
+/// Cleanup autocommands and patterns that have been deleted.
+/// This is only done when not executing autocommands.
+static void au_cleanup(void)
+{
+ AutoPat *ap, **prev_ap;
+ event_T event;
+
+ if (autocmd_busy || !au_need_clean) {
+ return;
+ }
+
+ // Loop over all events.
+ for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
+ event = (event_T)((int)event + 1)) {
+ // Loop over all autocommand patterns.
+ prev_ap = &(first_autopat[(int)event]);
+ for (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 || ac->cmd == NULL) {
+ *prev_ac = ac->next;
+ xfree(ac->cmd);
+ xfree(ac);
+ } else {
+ has_cmd = true;
+ prev_ac = &(ac->next);
+ }
+ }
+
+ 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);
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ au_need_clean = false;
+}
+
+// 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
+ for (apc = active_apc_list; apc; apc = apc->next) {
+ if (buf->b_fnum == apc->arg_bufnr) {
+ apc->arg_bufnr = 0;
+ }
+ }
+
+ // invalidate buflocals looping through events
+ for (event = (event_T)0; (int)event < (int)NUM_EVENTS;
+ event = (event_T)((int)event + 1)) {
+ // loop over all autocommand patterns
+ for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ if (ap->buflocal_nr == buf->b_fnum) {
+ au_remove_pat(ap);
+ if (p_verbose >= 6) {
+ verbose_enter();
+ smsg(_("auto-removing autocommand: %s <buffer=%d>"),
+ event_nr2name(event), buf->b_fnum);
+ verbose_leave();
+ }
+ }
+ }
+ }
+ au_cleanup();
+}
+
+// Add an autocmd group name.
+// Return its ID. Returns AUGROUP_ERROR (< 0) for error.
+static int au_new_group(char_u *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);
+ }
+
+ AUGROUP_NAME(i) = xstrdup((char *)name);
+ if (i == augroups.ga_len) {
+ augroups.ga_len++;
+ }
+ }
+
+ return i;
+}
+
+static void au_del_group(char_u *name)
+{
+ int i = au_find_group(name);
+ if (i == AUGROUP_ERROR) { // the group doesn't exist
+ EMSG2(_("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 < (int)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;
+ }
+ }
+ }
+ xfree(AUGROUP_NAME(i));
+ if (in_use) {
+ AUGROUP_NAME(i) = (char *)get_deleted_augroup();
+ } else {
+ AUGROUP_NAME(i) = NULL;
+ }
+ }
+}
+
+/// Find the ID of an autocmd group name.
+///
+/// @param name augroup name
+///
+/// @return the ID or AUGROUP_ERROR (< 0) for error.
+static int au_find_group(const char_u *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;
+ }
+ }
+ return AUGROUP_ERROR;
+}
+
+/// Return true if augroup "name" exists.
+///
+/// @param name augroup name
+bool au_has_group(const char_u *name)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return au_find_group(name) != AUGROUP_ERROR;
+}
+
+/// ":augroup {name}".
+void do_augroup(char_u *arg, int del_group)
+{
+ if (del_group) {
+ if (*arg == NUL) {
+ EMSG(_(e_argreq));
+ } else {
+ au_del_group(arg);
+ }
+ } 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
+ int i = au_new_group(arg);
+ if (i != AUGROUP_ERROR) {
+ current_augroup = i;
+ }
+ } 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(" ");
+ }
+ }
+ msg_clr_eos();
+ msg_end();
+ }
+}
+
+#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);
+ }
+ }
+ ga_clear(&augroups);
+}
+#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)
+{
+ const char_u *p;
+ int i;
+ int len;
+
+ // the event name ends with end of line, '|', a blank or a comma
+ for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) {
+ }
+ for (i = 0; event_names[i].name != NULL; i++) {
+ len = (int)event_names[i].len;
+ if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) {
+ break;
+ }
+ }
+ if (*p == ',') {
+ p++;
+ }
+ *end = (char_u *)p;
+ if (event_names[i].name == NULL) {
+ return NUM_EVENTS;
+ }
+ return event_names[i].event;
+}
+
+/// Return the name for event
+///
+/// @param[in] event Event to return name for.
+///
+/// @return Event name, static string. Returns "Unknown" for unknown events.
+static const char *event_nr2name(event_T event)
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST
+{
+ int i;
+
+ for (i = 0; event_names[i].name != NULL; i++) {
+ if (event_names[i].event == event) {
+ return event_names[i].name;
+ }
+ }
+ 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])) {
+ EMSG2(_("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) >= (int)NUM_EVENTS) {
+ if (have_group) {
+ EMSG2(_("E216: No such event: %s"), pat);
+ } else {
+ EMSG2(_("E216: No such group or event: %s"), pat);
+ }
+ return NULL;
+ }
+ }
+ }
+ return pat;
+}
+
+/// Return true if "event" is included in 'eventignore'.
+///
+/// @param event event to check
+static bool event_ignored(event_T event)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *p = p_ei;
+
+ while (*p != NUL) {
+ if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
+ return true;
+ }
+ if (event_name2nr(p, &p) == event) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Return OK when the contents of p_ei is valid, FAIL otherwise.
+int check_ei(void)
+{
+ char_u *p = p_ei;
+
+ while (*p) {
+ if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
+ p += 3;
+ if (*p == ',') {
+ p++;
+ }
+ } else if (event_name2nr(p, &p) == NUM_EVENTS) {
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+// Add "what" to 'eventignore' to skip loading syntax highlighting for every
+// buffer loaded into the window. "what" must start with a comma.
+// Returns the old value of 'eventignore' in allocated memory.
+char_u *au_event_disable(char *what)
+{
+ char_u *new_ei;
+ char_u *save_ei;
+
+ save_ei = vim_strsave(p_ei);
+ new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what));
+ if (*what == ',' && *p_ei == NUL) {
+ STRCPY(new_ei, what + 1);
+ } else {
+ STRCAT(new_ei, what);
+ }
+ set_string_option_direct((char_u *)"ei", -1, new_ei, OPT_FREE, SID_NONE);
+ xfree(new_ei);
+
+ return save_ei;
+}
+
+void au_event_restore(char_u *old_ei)
+{
+ if (old_ei != NULL) {
+ set_string_option_direct((char_u *)"ei", -1, old_ei, OPT_FREE, SID_NONE);
+ xfree(old_ei);
+ }
+}
+
+// Implements :autocmd.
+// Defines an autocmd (does not execute; cf. apply_autocmds_group).
+//
+// Can be used in the following ways:
+//
+// :autocmd <event> <pat> <cmd> Add <cmd> to the list of commands that
+// will be automatically executed for <event>
+// when editing a file matching <pat>, in
+// the current group.
+// :autocmd <event> <pat> Show the autocommands associated with
+// <event> and <pat>.
+// :autocmd <event> Show the autocommands associated with
+// <event>.
+// :autocmd Show all autocommands.
+// :autocmd! <event> <pat> <cmd> Remove all autocommands associated with
+// <event> and <pat>, and add the command
+// <cmd>, for the current group.
+// :autocmd! <event> <pat> Remove all autocommands associated with
+// <event> and <pat> for the current group.
+// :autocmd! <event> Remove all autocommands associated with
+// <event> for the current group.
+// :autocmd! Remove ALL autocommands for the current
+// group.
+//
+// Multiple events and patterns may be given separated by commas. Here are
+// some examples:
+// :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic
+// :autocmd bufleave * set tw=79 nosmartindent ic infercase
+//
+// :autocmd * *.c show all autocommands for *.c files.
+//
+// Mostly a {group} argument can optionally appear before <event>.
+void do_autocmd(char_u *arg_in, int forceit)
+{
+ char_u *arg = arg_in;
+ char_u *pat;
+ char_u *envpat = NULL;
+ char_u *cmd;
+ int need_free = false;
+ int nested = false;
+ bool once = false;
+ int group;
+
+ if (*arg == '|') {
+ arg = (char_u *)"";
+ 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);
+ }
+
+ // Scan over the events.
+ // If we find an illegal name, return here, don't do anything.
+ pat = find_end_event(arg, group != AUGROUP_ALL);
+ if (pat == NULL) {
+ return;
+ }
+
+ pat = skipwhite(pat);
+ if (*pat == '|') {
+ pat = (char_u *)"";
+ cmd = (char_u *)"";
+ } else {
+ // Scan over the pattern. Put a NUL at the end.
+ cmd = pat;
+ while (*cmd && (!ascii_iswhite(*cmd) || cmd[-1] == '\\')) {
+ cmd++;
+ }
+ if (*cmd) {
+ *cmd++ = NUL;
+ }
+
+ // Expand environment variables in the pattern. Set 'shellslash', we want
+ // forward slashes here.
+ if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) {
+#ifdef BACKSLASH_IN_FILENAME
+ int p_ssl_save = p_ssl;
+
+ p_ssl = true;
+#endif
+ envpat = expand_env_save(pat);
+#ifdef BACKSLASH_IN_FILENAME
+ p_ssl = p_ssl_save;
+#endif
+ if (envpat != NULL) {
+ pat = envpat;
+ }
+ }
+
+ cmd = skipwhite(cmd);
+ 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) {
+ EMSG2(_(e_duparg2), "++once");
+ }
+ once = true;
+ cmd = skipwhite(cmd + 6);
+ }
+
+ // Check for "++nested" flag.
+ if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) {
+ if (nested) {
+ EMSG2(_(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) {
+ EMSG2(_(e_duparg2), "nested");
+ }
+ nested = true;
+ cmd = skipwhite(cmd + 6);
+ }
+ }
+ }
+
+ // Find the start of the commands.
+ // Expand <sfile> in it.
+ if (*cmd != NUL) {
+ cmd = expand_sfile(cmd);
+ if (cmd == NULL) { // some error
+ return;
+ }
+ need_free = true;
+ }
+ }
+
+ // Print header when showing autocommands.
+ if (!forceit && *cmd == NUL) {
+ // 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 == '|') {
+ for (event_T event = (event_T)0; event < (int)NUM_EVENTS;
+ event = (event_T)(event + 1)) {
+ 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) {
+ xfree(cmd);
+ }
+ 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)
+{
+ 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
+ }
+ xfree(group_name);
+ }
+ return group;
+}
+
+// do_autocmd() for one event.
+// Defines an autocmd (does not execute; cf. apply_autocmds_group).
+//
+// If *pat == NUL: do for all patterns.
+// 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)
+{
+ 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>"
+
+ 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);
+ }
+ }
+ }
+
+ // 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;
+ 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) {
+ // normalize pat into standard "<buffer>#N" form
+ snprintf(
+ (char *)buflocal_pat,
+ BUFLOCAL_PAT_LEN,
+ "<buffer=%d>",
+ buflocal_nr);
+
+ pat = buflocal_pat; // can modify pat and patlen
+ patlen = (int)STRLEN(buflocal_pat); // but not endpat
+ }
+
+ // 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 {
+ 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);
+ 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;
+ }
+ }
+ }
+ 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)) {
+ emsgf(_("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;
+
+ 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;
+ }
+ }
+ 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.
+ prev_ac = &(ap->cmds);
+ while ((ac = *prev_ac) != NULL) {
+ prev_ac = &ac->next;
+ }
+ 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;
+ }
+ }
+
+ au_cleanup(); // may really delete removed patterns/commands now
+ return OK;
+}
+
+// Implementation of ":doautocmd [group] event [fname]".
+// Return OK for success, FAIL for failure;
+int do_doautocmd(char_u *arg,
+ int do_msg, // give message for no matching autocmds?
+ bool *did_something)
+{
+ char_u *fname;
+ int nothing_done = true;
+ int group;
+
+ if (did_something != NULL) {
+ *did_something = false;
+ }
+
+ // Check for a legal group name. If not, use AUGROUP_ALL.
+ group = au_get_grouparg(&arg);
+
+ if (*arg == '*') {
+ EMSG(_("E217: Can't execute autocommands for ALL events"));
+ return FAIL;
+ }
+
+ // Scan over the events.
+ // If we find an illegal name, return here, don't do anything.
+ fname = find_end_event(arg, group != AUGROUP_ALL);
+ if (fname == NULL) {
+ return FAIL;
+ }
+
+ fname = skipwhite(fname);
+
+ // Loop over the events.
+ while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) {
+ if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, group,
+ curbuf, NULL)) {
+ nothing_done = false;
+ }
+ }
+
+ if (nothing_done && do_msg) {
+ MSG(_("No matching autocommands"));
+ }
+ if (did_something != NULL) {
+ *did_something = !nothing_done;
+ }
+
+ return aborting() ? FAIL : OK;
+}
+
+// ":doautoall": execute autocommands for each loaded buffer.
+void ex_doautoall(exarg_T *eap)
+{
+ int retval;
+ aco_save_T aco;
+ char_u *arg = eap->arg;
+ int call_do_modelines = check_nomodeline(&arg);
+ bufref_T bufref;
+
+ // This is a bit tricky: For some commands curwin->w_buffer needs to be
+ // equal to curbuf, but for some buffers there may not be a window.
+ // So we change the buffer for the current window for a moment. This
+ // gives problems when the autocommands make changes to the list of
+ // buffers or windows...
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ml.ml_mfp == NULL) {
+ continue;
+ }
+ // Find a window for this buffer and save some values.
+ aucmd_prepbuf(&aco, buf);
+ set_bufref(&bufref, buf);
+
+ bool did_aucmd;
+ // execute the autocommands for this buffer
+ retval = do_doautocmd(arg, false, &did_aucmd);
+
+ if (call_do_modelines && did_aucmd) {
+ // Execute the modeline settings, but don't set window-local
+ // options if we are using the current window for another
+ // buffer.
+ do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0);
+ }
+
+ // restore the current window
+ aucmd_restbuf(&aco);
+
+ // Stop if there is some error or buffer was deleted.
+ if (retval == FAIL || !bufref_valid(&bufref)) {
+ break;
+ }
+ }
+
+ check_cursor(); // just in case lines got deleted
+}
+
+/// Check *argp for <nomodeline>. When it is present return false, otherwise
+/// return true and advance *argp to after it. Thus do_modelines() should be
+/// called when true is returned.
+///
+/// @param[in,out] argp argument string
+bool check_nomodeline(char_u **argp)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (STRNCMP(*argp, "<nomodeline>", 12) == 0) {
+ *argp = skipwhite(*argp + 12);
+ return false;
+ }
+ return true;
+}
+
+/// Prepare for executing autocommands for (hidden) buffer `buf`.
+/// If the current buffer is not in any visible window, put it in a temporary
+/// floating window `aucmd_win`.
+/// Set `curbuf` and `curwin` to match `buf`.
+///
+/// @param aco structure to save values in
+/// @param buf new curbuf
+void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
+{
+ win_T *win;
+ bool need_append = true; // Append `aucmd_win` to the window list.
+
+ // Find a window that is for the new buffer
+ if (buf == curbuf) { // be quick when buf is curbuf
+ win = curwin;
+ } else {
+ win = NULL;
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ win = wp;
+ break;
+ }
+ }
+ }
+
+ // Allocate the `aucmd_win` dummy floating window.
+ if (win == NULL && aucmd_win == NULL) {
+ win_alloc_aucmd_win();
+ need_append = false;
+ }
+ if (win == NULL && aucmd_win_used) {
+ // Strange recursive autocommand, fall back to using the current
+ // window. Expect a few side effects...
+ win = curwin;
+ }
+
+ aco->save_curwin = curwin;
+ aco->save_prevwin = prevwin;
+ aco->save_curbuf = curbuf;
+ if (win != NULL) {
+ // There is a window for "buf" in the current tab page, make it the
+ // curwin. This is preferred, it has the least side effects (esp. if
+ // "buf" is curbuf).
+ aco->use_aucmd_win = false;
+ curwin = win;
+ } else {
+ // There is no window for "buf", use "aucmd_win". To minimize the side
+ // effects, insert it in the current tab page.
+ // Anything related to a window (e.g., setting folds) may have
+ // unexpected results.
+ aco->use_aucmd_win = true;
+ aucmd_win_used = true;
+ aucmd_win->w_buffer = buf;
+ aucmd_win->w_s = &buf->b_s;
+ buf->b_nwindows++;
+ win_init_empty(aucmd_win); // set cursor and topline to safe values
+
+ // Make sure w_localdir and globaldir are NULL to avoid a chdir() in
+ // win_enter_ext().
+ XFREE_CLEAR(aucmd_win->w_localdir);
+ aco->globaldir = globaldir;
+ globaldir = NULL;
+
+ block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
+ if (need_append) {
+ win_append(lastwin, aucmd_win);
+ handle_register_window(aucmd_win);
+ win_config_float(aucmd_win, aucmd_win->w_float_config);
+ }
+ // Prevent chdir() call in win_enter_ext(), through do_autochdir()
+ int save_acd = p_acd;
+ p_acd = false;
+ win_enter(aucmd_win, false);
+ p_acd = save_acd;
+ unblock_autocmds();
+ curwin = aucmd_win;
+ }
+ curbuf = buf;
+ aco->new_curwin = curwin;
+ set_bufref(&aco->new_curbuf, curbuf);
+}
+
+/// Cleanup after executing autocommands for a (hidden) buffer.
+/// Restore the window as it was (if possible).
+///
+/// @param aco structure holding saved values
+void aucmd_restbuf(aco_save_T *aco)
+{
+ if (aco->use_aucmd_win) {
+ curbuf->b_nwindows--;
+ // Find "aucmd_win", it can't be closed, but it may be in another tab page.
+ // Do not trigger autocommands here.
+ block_autocmds();
+ if (curwin != aucmd_win) {
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp == aucmd_win) {
+ if (tp != curtab) {
+ goto_tabpage_tp(tp, true, true);
+ }
+ win_goto(aucmd_win);
+ goto win_found;
+ }
+ }
+ }
+ win_found:
+
+ win_remove(curwin, NULL);
+ handle_unregister_window(curwin);
+ if (curwin->w_grid.chars != NULL) {
+ ui_comp_remove_grid(&curwin->w_grid);
+ ui_call_win_hide(curwin->w_grid.handle);
+ grid_free(&curwin->w_grid);
+ }
+
+ aucmd_win_used = false;
+ last_status(false); // may need to remove last status line
+
+ if (!valid_tabpage_win(curtab)) {
+ // no valid window in current tabpage
+ close_tabpage(curtab);
+ }
+
+ unblock_autocmds();
+
+ if (win_valid(aco->save_curwin)) {
+ curwin = aco->save_curwin;
+ } else {
+ // Hmm, original window disappeared. Just use the first one.
+ curwin = firstwin;
+ }
+ prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
+ : firstwin; // window disappeared?
+ vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables
+ hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab
+ curbuf = curwin->w_buffer;
+
+ xfree(globaldir);
+ globaldir = aco->globaldir;
+
+ // the buffer contents may have changed
+ check_cursor();
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
+ curwin->w_topfill = 0;
+ }
+ } else {
+ // restore curwin
+ if (win_valid(aco->save_curwin)) {
+ // Restore the buffer which was previously edited by curwin, if it was
+ // changed, we are still the same window and the buffer is valid.
+ if (curwin == aco->new_curwin && curbuf != aco->new_curbuf.br_buf
+ && bufref_valid(&aco->new_curbuf)
+ && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) {
+ if (curwin->w_s == &curbuf->b_s) {
+ curwin->w_s = &aco->new_curbuf.br_buf->b_s;
+ }
+ curbuf->b_nwindows--;
+ curbuf = aco->new_curbuf.br_buf;
+ curwin->w_buffer = curbuf;
+ curbuf->b_nwindows++;
+ }
+
+ curwin = aco->save_curwin;
+ prevwin = win_valid(aco->save_prevwin) ? aco->save_prevwin
+ : firstwin; // window disappeared?
+ curbuf = curwin->w_buffer;
+ // In case the autocommand moves the cursor to a position that does not
+ // exist in curbuf
+ check_cursor();
+ }
+ }
+}
+
+/// Execute autocommands for "event" and file name "fname".
+///
+/// @param event event that occurred
+/// @param fname filename, NULL or empty means use actual file name
+/// @param fname_io filename to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+///
+/// @return true if some commands were executed.
+bool apply_autocmds(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf)
+{
+ return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf,
+ NULL);
+}
+
+/// Like apply_autocmds(), but with extra "eap" argument. This takes care of
+/// setting v:filearg.
+///
+/// @param event event that occurred
+/// @param fname NULL or empty means use actual file name
+/// @param fname_io fname to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+/// @param exarg Ex command arguments
+///
+/// @return true if some commands were executed.
+bool apply_autocmds_exarg(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf,
+ exarg_T *eap)
+{
+ return apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf,
+ eap);
+}
+
+/// Like apply_autocmds(), but handles the caller's retval. If the script
+/// processing is being aborted or if retval is FAIL when inside a try
+/// conditional, no autocommands are executed. If otherwise the autocommands
+/// cause the script to be aborted, retval is set to FAIL.
+///
+/// @param event event that occurred
+/// @param fname NULL or empty means use actual file name
+/// @param fname_io fname to use for <afile> on cmdline
+/// @param force When true, ignore autocmd_busy
+/// @param buf Buffer for <abuf>
+/// @param[in,out] retval caller's retval
+///
+/// @return true if some autocommands were executed
+bool apply_autocmds_retval(event_T event,
+ char_u *fname,
+ char_u *fname_io,
+ bool force,
+ buf_T *buf,
+ int *retval)
+{
+ if (should_abort(*retval)) {
+ return false;
+ }
+
+ bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
+ AUGROUP_ALL, buf, NULL);
+ if (did_cmd && aborting()) {
+ *retval = FAIL;
+ }
+ return did_cmd;
+}
+
+/// Return true if "event" autocommand is defined.
+///
+/// @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 true when there is a CursorHold/CursorHoldI autocommand defined for
+/// the current mode.
+bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return has_event(
+ (get_real_state() == NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI));
+ // return first_autopat[] != NULL;
+}
+
+/// Return true if the CursorHold/CursorHoldI event can be triggered.
+bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int state;
+
+ if (!did_cursorhold && has_cursorhold() && reg_recording == 0
+ && typebuf.tb_len == 0 && !ins_compl_active()) {
+ state = get_real_state();
+ if (state == NORMAL_BUSY || (state & INSERT) != 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Execute autocommands for "event" and file name "fname".
+///
+/// @param event event that occurred
+/// @param fname filename, NULL or empty means use actual file name
+/// @param fname_io filename to use for <afile> on cmdline,
+/// NULL means use `fname`.
+/// @param force When true, ignore autocmd_busy
+/// @param group autocmd group ID or AUGROUP_ALL
+/// @param buf Buffer for <abuf>
+/// @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)
+{
+ char_u *sfname = NULL; // short file name
+ char_u *tail;
+ bool save_changed;
+ buf_T *old_curbuf;
+ bool retval = false;
+ char_u *save_sourcing_name;
+ linenr_T save_sourcing_lnum;
+ char_u *save_autocmd_fname;
+ int save_autocmd_bufnr;
+ char_u *save_autocmd_match;
+ int save_autocmd_busy;
+ int save_autocmd_nested;
+ static int nesting = 0;
+ AutoPatCmd patcmd;
+ AutoPat *ap;
+ char_u *save_cmdarg;
+ long save_cmdbang;
+ static int filechangeshell_busy = false;
+ proftime_T wait_time;
+ bool did_save_redobuff = false;
+ save_redo_T save_redo;
+ const bool save_KeyTyped = KeyTyped;
+
+ // 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()) {
+ goto BYPASS_AU;
+ }
+
+ // When autocommands are busy, new autocommands are only executed when
+ // explicitly enabled with the "nested" flag.
+ if (autocmd_busy && !(force || autocmd_nested)) {
+ goto BYPASS_AU;
+ }
+
+ // Quickly return when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ if (aborting()) {
+ goto BYPASS_AU;
+ }
+
+ // FileChangedShell never nests, because it can create an endless loop.
+ if (filechangeshell_busy
+ && (event == EVENT_FILECHANGEDSHELL
+ || event == EVENT_FILECHANGEDSHELLPOST)) {
+ goto BYPASS_AU;
+ }
+
+ // Ignore events in 'eventignore'.
+ if (event_ignored(event)) {
+ goto BYPASS_AU;
+ }
+
+ // Allow nesting of autocommands, but restrict the depth, because it's
+ // possible to create an endless loop.
+ if (nesting == 10) {
+ EMSG(_("E218: autocommand nesting too deep"));
+ goto BYPASS_AU;
+ }
+
+ // 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))) {
+ goto BYPASS_AU;
+ }
+
+ // Save the autocmd_* variables and info about the current buffer.
+ save_autocmd_fname = autocmd_fname;
+ save_autocmd_bufnr = autocmd_bufnr;
+ save_autocmd_match = autocmd_match;
+ save_autocmd_busy = autocmd_busy;
+ save_autocmd_nested = autocmd_nested;
+ save_changed = curbuf->b_changed;
+ old_curbuf = curbuf;
+
+ // Set the file name to be used for <afile>.
+ // Make a copy to avoid that changing a buffer name or directory makes it
+ // invalid.
+ if (fname_io == NULL) {
+ if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
+ || event == EVENT_OPTIONSET) {
+ autocmd_fname = NULL;
+ } else if (fname != NULL && !ends_excmd(*fname)) {
+ autocmd_fname = fname;
+ } else if (buf != NULL) {
+ autocmd_fname = buf->b_ffname;
+ } else {
+ autocmd_fname = NULL;
+ }
+ } else {
+ autocmd_fname = fname_io;
+ }
+ if (autocmd_fname != NULL) {
+ // Allocate MAXPATHL for when eval_vars() resolves the fullpath.
+ autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL);
+ }
+
+ // Set the buffer number to be used for <abuf>.
+ if (buf == NULL) {
+ autocmd_bufnr = 0;
+ } else {
+ autocmd_bufnr = 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
+ // "allow_dirs" is set.
+ if (fname == NULL || *fname == NUL) {
+ if (buf == NULL) {
+ fname = NULL;
+ } else {
+ if (event == EVENT_SYNTAX) {
+ fname = buf->b_p_syn;
+ } else if (event == EVENT_FILETYPE) {
+ fname = buf->b_p_ft;
+ } else {
+ if (buf->b_sfname != NULL) {
+ sfname = vim_strsave(buf->b_sfname);
+ }
+ fname = buf->b_ffname;
+ }
+ }
+ if (fname == NULL) {
+ fname = (char_u *)"";
+ }
+ fname = vim_strsave(fname); // make a copy, so we can change it
+ } else {
+ sfname = vim_strsave(fname);
+ // Don't try expanding the following events.
+ if (event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER
+ || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER
+ || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED
+ || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
+ || event == EVENT_DIRCHANGED || event == EVENT_FILETYPE
+ || event == EVENT_FUNCUNDEFINED || event == EVENT_OPTIONSET
+ || event == EVENT_QUICKFIXCMDPOST || event == EVENT_QUICKFIXCMDPRE
+ || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING
+ || event == EVENT_SYNTAX || event == EVENT_SIGNAL
+ || event == EVENT_TABCLOSED || event == EVENT_WINCLOSED) {
+ fname = vim_strsave(fname);
+ } else {
+ fname = (char_u *)FullName_save((char *)fname, false);
+ }
+ }
+ if (fname == NULL) { // out of memory
+ xfree(sfname);
+ retval = false;
+ goto BYPASS_AU;
+ }
+
+#ifdef BACKSLASH_IN_FILENAME
+ // Replace all backslashes with forward slashes. This makes the
+ // autocommand patterns portable between Unix and Windows.
+ if (sfname != NULL) {
+ forward_slash(sfname);
+ }
+ forward_slash(fname);
+#endif
+
+ // Set the name to be used for <amatch>.
+ autocmd_match = fname;
+
+ // Don't redraw while doing autocommands.
+ RedrawingDisabled++;
+ save_sourcing_name = sourcing_name;
+ sourcing_name = NULL; // don't free this one
+ save_sourcing_lnum = sourcing_lnum;
+ sourcing_lnum = 0; // no line number here
+
+ const sctx_T save_current_sctx = current_sctx;
+
+ if (do_profiling == PROF_YES) {
+ prof_child_enter(&wait_time); // doesn't count for the caller itself
+ }
+
+ // Don't use local function variables, if called from a function.
+ funccal_entry_T funccal_entry;
+ save_funccal(&funccal_entry);
+
+ // When starting to execute autocommands, save the search patterns.
+ if (!autocmd_busy) {
+ save_search_patterns();
+ if (!ins_compl_active()) {
+ saveRedobuff(&save_redo);
+ did_save_redobuff = true;
+ }
+ did_filetype = keep_filetype;
+ }
+
+ // Note that we are applying autocmds. Some commands need to know.
+ autocmd_busy = true;
+ filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL);
+ nesting++; // see matching decrement below
+
+ // Remember that FileType was triggered. Used for did_filetype().
+ if (event == EVENT_FILETYPE) {
+ did_filetype = true;
+ }
+
+ tail = path_tail(fname);
+
+ // Find first autocommand that matches
+ patcmd.curpat = first_autopat[(int)event];
+ patcmd.nextcmd = NULL;
+ patcmd.group = group;
+ patcmd.fname = fname;
+ patcmd.sfname = sfname;
+ patcmd.tail = tail;
+ patcmd.event = event;
+ patcmd.arg_bufnr = autocmd_bufnr;
+ patcmd.next = NULL;
+ auto_next_pat(&patcmd, false);
+
+ // found one, start executing the autocommands
+ if (patcmd.curpat != NULL) {
+ // add to active_apc_list
+ patcmd.next = active_apc_list;
+ active_apc_list = &patcmd;
+
+ // set v:cmdarg (only when there is a matching pattern)
+ save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
+ if (eap != NULL) {
+ save_cmdarg = set_cmdarg(eap, NULL);
+ set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
+ } else {
+ 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;
+ check_lnums(true); // make sure cursor and topline are valid
+
+ // Execute the autocmd. The `getnextac` callback handles iteration.
+ do_cmdline(NULL, getnextac, (void *)&patcmd,
+ DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+
+ reset_lnums(); // restore cursor and topline, unless they were changed
+
+ if (eap != NULL) {
+ (void)set_cmdarg(NULL, save_cmdarg);
+ set_vim_var_nr(VV_CMDBANG, save_cmdbang);
+ }
+ // delete from active_apc_list
+ if (active_apc_list == &patcmd) { // just in case
+ active_apc_list = patcmd.next;
+ }
+ }
+
+ RedrawingDisabled--;
+ autocmd_busy = save_autocmd_busy;
+ filechangeshell_busy = false;
+ autocmd_nested = save_autocmd_nested;
+ xfree(sourcing_name);
+ sourcing_name = save_sourcing_name;
+ sourcing_lnum = save_sourcing_lnum;
+ xfree(autocmd_fname);
+ autocmd_fname = save_autocmd_fname;
+ autocmd_bufnr = save_autocmd_bufnr;
+ autocmd_match = save_autocmd_match;
+ current_sctx = save_current_sctx;
+ restore_funccal();
+ if (do_profiling == PROF_YES) {
+ prof_child_exit(&wait_time);
+ }
+ KeyTyped = save_KeyTyped;
+ xfree(fname);
+ xfree(sfname);
+ nesting--; // see matching increment above
+
+ // When stopping to execute autocommands, restore the search patterns and
+ // the redo buffer. Free any buffers in the au_pending_free_buf list and
+ // free any windows in the au_pending_free_win list.
+ if (!autocmd_busy) {
+ restore_search_patterns();
+ if (did_save_redobuff) {
+ restoreRedobuff(&save_redo);
+ }
+ did_filetype = false;
+ while (au_pending_free_buf != NULL) {
+ buf_T *b = au_pending_free_buf->b_next;
+ xfree(au_pending_free_buf);
+ au_pending_free_buf = b;
+ }
+ while (au_pending_free_win != NULL) {
+ win_T *w = au_pending_free_win->w_next;
+ xfree(au_pending_free_win);
+ au_pending_free_win = w;
+ }
+ }
+
+ // Some events don't set or reset the Changed flag.
+ // Check if still in the same buffer!
+ if (curbuf == old_curbuf
+ && (event == EVENT_BUFREADPOST || event == EVENT_BUFWRITEPOST
+ || event == EVENT_FILEAPPENDPOST || event == EVENT_VIMLEAVE
+ || event == EVENT_VIMLEAVEPRE)) {
+ if (curbuf->b_changed != save_changed) {
+ need_maketitle = true;
+ }
+ curbuf->b_changed = save_changed;
+ }
+
+ au_cleanup(); // may really delete removed patterns/commands now
+
+BYPASS_AU:
+ // When wiping out a buffer make sure all its buffer-local autocommands
+ // are deleted.
+ if (event == EVENT_BUFWIPEOUT && buf != NULL) {
+ aubuflocal_remove(buf);
+ }
+
+ if (retval == OK && event == EVENT_FILETYPE) {
+ au_did_filetype = true;
+ }
+
+ return retval;
+}
+
+// Block triggering autocommands until unblock_autocmd() is called.
+// Can be used recursively, so long as it's symmetric.
+void block_autocmds(void)
+{
+ // Remember the value of v:termresponse.
+ if (is_autocmd_blocked()) {
+ old_termresponse = get_vim_var_str(VV_TERMRESPONSE);
+ }
+ autocmd_blocked++;
+}
+
+void unblock_autocmds(void)
+{
+ autocmd_blocked--;
+
+ // 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) {
+ apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
+ }
+}
+
+static inline 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)
+{
+ AutoPat *ap;
+ AutoCmd *cp;
+ char *s;
+
+ XFREE_CLEAR(sourcing_name);
+
+ for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
+ apc->curpat = NULL;
+
+ // 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
+ 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);
+
+ sourcing_name = xmalloc(sourcing_name_len);
+ snprintf((char *)sourcing_name, sourcing_name_len, s, name,
+ (char *)ap->pat);
+ if (p_verbose >= 8) {
+ verbose_enter();
+ smsg(_("Executing %s"), sourcing_name);
+ verbose_leave();
+ }
+
+ 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;
+ }
+ line_breakcheck();
+ if (apc->curpat != NULL) { // found a match
+ break;
+ }
+ }
+ if (stop_at_last && ap->last) {
+ break;
+ }
+ }
+}
+
+/// Get next autocommand command.
+/// Called by do_cmdline() to get the next line for ":if".
+/// @return allocated string, or NULL for end of autocommands.
+char_u *getnextac(int c, void *cookie, int indent, bool do_concat)
+{
+ AutoPatCmd *acp = (AutoPatCmd *)cookie;
+ char_u *retval;
+ AutoCmd *ac;
+
+ // Can be called again after returning the last line.
+ if (acp->curpat == NULL) {
+ return NULL;
+ }
+
+ // repeat until we find an autocommand to execute
+ for (;;) {
+ // skip removed commands
+ while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) {
+ 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;
+ }
+ }
+
+ ac = acp->nextcmd;
+
+ if (p_verbose >= 9) {
+ verbose_enter_scroll();
+ smsg(_("autocommand %s"), ac->cmd);
+ msg_puts("\n"); // don't overwrite this either
+ verbose_leave_scroll();
+ }
+ retval = vim_strsave(ac->cmd);
+ // Remove one-shot ("once") autocmd in anticipation of its execution.
+ if (ac->once) {
+ au_del_cmd(ac);
+ }
+ autocmd_nested = ac->nested;
+ current_sctx = ac->script_ctx;
+ if (ac->last) {
+ acp->nextcmd = NULL;
+ } else {
+ acp->nextcmd = ac->next;
+ }
+
+ return retval;
+}
+
+/// Return true if there is a matching autocommand for "fname".
+/// To account for buffer-local autocommands, function needs to know
+/// in which buffer the file will be opened.
+///
+/// @param event event that occurred.
+/// @param sfname filename the event occurred in.
+/// @param buf buffer the file is open in
+bool has_autocmd(event_T event,
+ char_u *sfname,
+ buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ AutoPat *ap;
+ char_u *fname;
+ char_u *tail = path_tail(sfname);
+ bool retval = false;
+
+ fname = (char_u *)FullName_save((char *)sfname, false);
+ if (fname == NULL) {
+ return false;
+ }
+
+#ifdef BACKSLASH_IN_FILENAME
+ // Replace all backslashes with forward slashes. This makes the
+ // autocommand patterns portable between Unix and Windows.
+ sfname = vim_strsave(sfname);
+ forward_slash(sfname);
+ forward_slash(fname);
+#endif
+
+ for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
+ if (ap->pat != NULL && ap->cmds != NULL
+ && (ap->buflocal_nr == 0
+ ? match_file_pat(
+ NULL,
+ &ap->reg_prog,
+ fname,
+ sfname,
+ tail,
+ ap->allow_dirs)
+ : buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
+ retval = true;
+ break;
+ }
+ }
+
+ xfree(fname);
+#ifdef BACKSLASH_IN_FILENAME
+ xfree(sfname);
+#endif
+
+ return retval;
+}
+
+// Function given to ExpandGeneric() to obtain the list of autocommand group
+// names.
+char_u *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);
+}
+
+char_u *set_context_in_autocmd(
+ expand_T *xp,
+ char_u *arg,
+ int doautocmd // true for :doauto*, false for :autocmd
+)
+{
+ char_u *p;
+ int group;
+
+ // check for a group name, skip it if present
+ autocmd_include_groups = false;
+ p = arg;
+ group = au_get_grouparg(&arg);
+
+ // If there only is a group name that's what we expand.
+ if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) {
+ arg = p;
+ group = AUGROUP_ALL;
+ }
+
+ // skip over event name
+ for (p = arg; *p != NUL && !ascii_iswhite(*p); p++) {
+ if (*p == ',') {
+ arg = p + 1;
+ }
+ }
+ if (*p == NUL) {
+ if (group == AUGROUP_ALL) {
+ autocmd_include_groups = true;
+ }
+ xp->xp_context = EXPAND_EVENTS; // expand event name
+ xp->xp_pattern = arg;
+ return NULL;
+ }
+
+ // skip over pattern
+ arg = skipwhite(p);
+ while (*arg && (!ascii_iswhite(*arg) || arg[-1] == '\\')) {
+ arg++;
+ }
+ if (*arg) {
+ return arg; // expand (next) command
+ }
+
+ if (doautocmd) {
+ xp->xp_context = EXPAND_FILES; // expand file names
+ } else {
+ xp->xp_context = EXPAND_NOTHING; // pattern is not expanded
+ }
+ return NULL;
+}
+
+// Function given to ExpandGeneric() to obtain the list of event names.
+char_u *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
+ }
+ return (char_u *)AUGROUP_NAME(idx);
+ }
+ return (char_u *)event_names[idx - augroups.ga_len].name;
+}
+
+/// Check whether given autocommand is supported
+///
+/// @param[in] event Event to check.
+///
+/// @return True if it is, false otherwise.
+bool autocmd_supported(const char *const event)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ char_u *p;
+ return event_name2nr((const char_u *)event, &p) != NUM_EVENTS;
+}
+
+/// Return true if an autocommand is defined for a group, event and
+/// pattern: The group can be omitted to accept any group.
+/// `event` and `pattern` can be omitted to accept any event and pattern.
+/// Buffer-local patterns <buffer> or <buffer=N> are accepted.
+/// Used for:
+/// exists("#Group") or
+/// exists("#Group#Event") or
+/// exists("#Group#Event#pat") or
+/// exists("#Event") or
+/// exists("#Event#pat")
+///
+/// @param arg autocommand string
+bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ event_T event;
+ AutoPat *ap;
+ buf_T *buflocal_buf = NULL;
+ int group;
+ bool retval = false;
+
+ // Make a copy so that we can change the '#' chars to a NUL.
+ char *const arg_save = xstrdup(arg);
+ char *p = strchr(arg_save, '#');
+ if (p != NULL) {
+ *p++ = NUL;
+ }
+
+ // First, look for an autocmd group name.
+ group = au_find_group((char_u *)arg_save);
+ char *event_name;
+ if (group == AUGROUP_ERROR) {
+ // Didn't match a group name, assume the first argument is an event.
+ group = AUGROUP_ALL;
+ event_name = arg_save;
+ } else {
+ if (p == NULL) {
+ // "Group": group name is present and it's recognized
+ retval = true;
+ goto theend;
+ }
+
+ // Must be "Group#Event" or "Group#Event#pat".
+ event_name = p;
+ p = strchr(event_name, '#');
+ if (p != NULL) {
+ *p++ = NUL; // "Group#Event#pat"
+ }
+ }
+
+ char *pattern = p; // "pattern" is NULL when there is no pattern.
+
+ // Find the index (enum) for the event name.
+ event = event_name2nr((char_u *)event_name, (char_u **)&p);
+
+ // return false if the event name is not recognized
+ if (event == NUM_EVENTS) {
+ goto theend;
+ }
+
+ // Find the first autocommand for this event.
+ // If there isn't any, return false;
+ // If there is one and no pattern given, return true;
+ ap = first_autopat[(int)event];
+ if (ap == NULL) {
+ goto theend;
+ }
+
+ // if pattern is "<buffer>", special handling is needed which uses curbuf
+ // for pattern "<buffer=N>, fnamecmp() will work fine
+ if (pattern != NULL && STRICMP(pattern, "<buffer>") == 0) {
+ buflocal_buf = curbuf;
+ }
+
+ // 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 buffer-local autocommands, fnamecmp() works fine.
+ if (ap->pat != NULL && ap->cmds != NULL
+ && (group == AUGROUP_ALL || ap->group == group)
+ && (pattern == NULL
+ || (buflocal_buf == NULL
+ ? fnamecmp(ap->pat, (char_u *)pattern) == 0
+ : ap->buflocal_nr == buflocal_buf->b_fnum))) {
+ retval = true;
+ break;
+ }
+ }
+
+theend:
+ xfree(arg_save);
+ return retval;
+}