diff options
Diffstat (limited to 'src/nvim/fileio.c')
-rw-r--r-- | src/nvim/fileio.c | 2147 |
1 files changed, 2 insertions, 2145 deletions
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 6ee3a3f579..ab8072ae1d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -67,85 +67,6 @@ #define UV_FS_COPYFILE_FICLONE 0 #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. -// -typedef struct AutoCmd { - char_u *cmd; // Command to be executed (NULL when - // command has been removed) - bool once; // "One shot": removed after execution - char nested; // If autocommands nest here - char last; // last command in list - sctx_T script_ctx; // script context where defined - struct AutoCmd *next; // Next AutoCmd in list -} AutoCmd; - -typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST - // be the first entry - char_u *pat; // pattern as typed (NULL when pattern - // has been removed) - regprog_T *reg_prog; // compiled regprog for pattern - AutoCmd *cmds; // list of commands to do - int group; // group ID - int patlen; // strlen() of pat - int buflocal_nr; // !=0 for buffer-local AutoPat - char allow_dirs; // Pattern may match whole path - char last; // last pattern for apply_autocmds() -} AutoPat; - -/// -/// Struct used to keep status while executing autocommands for an event. -/// -typedef struct AutoPatCmd { - AutoPat *curpat; // next AutoPat to examine - AutoCmd *nextcmd; // next AutoCmd to execute - int group; // group being used - char_u *fname; // fname to match with - char_u *sfname; // sfname to match with - char_u *tail; // tail of fname - event_T event; // current event - int arg_bufnr; // initially equal to <abuf>, set to zero when - // buf is deleted - struct AutoPatCmd *next; // chain of active apc-s for auto-invalidation -} AutoPatCmd; - -#define AUGROUP_DEFAULT -1 /* default autocmd group */ -#define AUGROUP_ERROR -2 /* erroneous autocmd group */ -#define AUGROUP_ALL -3 /* all autocmd groups */ - #define HAS_BW_FLAGS #define FIO_LATIN1 0x01 /* convert Latin1 */ #define FIO_UTF8 0x02 /* convert UTF-8 */ @@ -195,14 +116,6 @@ struct bw_info { static char *e_auchangedbuf = N_( "E812: Autocommands changed buffer or buffer name"); -// Set by the apply_autocmds_group function if the given event is equal to -// EVENT_FILETYPE. Used by the readfile function in order to determine if -// EVENT_BUFREADPOST triggered the EVENT_FILETYPE. -// -// Relying on this value requires one to reset it prior calling -// apply_autocmds_group. -static bool au_did_filetype INIT(= false); - void filemess(buf_T *buf, char_u *name, char_u *s, int attr) { int msg_scroll_save; @@ -231,14 +144,6 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } -static AutoPat *last_autopat[NUM_EVENTS] = { - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -}; /* * Read lines from file "fname" into the buffer after line "from". @@ -5404,2054 +5309,6 @@ char_u *vim_tempname(void) } -/* - * Code for automatic commands. - */ -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "auevents_name_map.generated.h" -#endif - -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]) - -/* - * 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 */ - -// use get_deleted_augroup() to get this -static const char *deleted_augroup = 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; - if (ap->pat == NULL) /* pattern has been removed */ - 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. - */ -static char_u * -find_end_event ( - char_u *arg, - int have_group /* TRUE when group name was found */ -) -{ - 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, (int)(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; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)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, (int)(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[25]; /* 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[(int)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 */ - sprintf((char *)buflocal_pat, "<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); - } - /* - * Show autocmd's for this autopat, or buflocals <buffer=X> - */ - else if (*cmd == NUL) - show_autocmd(ap, event); - - /* - * Add autocmd to this autopat, if it's the last one. - */ - else if (ap->next == NULL) - 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, 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(); - } - } -} - -static int autocmd_nested = 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 -/// @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. -static 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 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 first_autopat[(int)(get_real_state() == NORMAL_BUSY - ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != 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; -} - -/// 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; -} - -/// 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 - || autocmd_blocked > 0) { - 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; -} - -static char_u *old_termresponse = NULL; - -/* - * 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 (autocmd_blocked == 0) - 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 (autocmd_blocked == 0 - && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) - apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); -} - -// Find next autocommand pattern that matches. -static void -auto_next_pat( - AutoPatCmd *apc, - int stop_at_last /* stop when 'last' flag is set */ -) -{ - 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) + 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); -} - -static int include_groups = FALSE; - -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 */ - 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) - 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 (!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; -} /// Tries matching a filename with a "pattern" ("prog" is NULL), or use the /// precompiled regprog "prog" ("pattern" is NULL). That avoids calling @@ -7467,8 +5324,8 @@ theend: /// @param allow_dirs Allow matching with dir /// /// @return true if there is a match, false otherwise -static bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, - char_u *sfname, char_u *tail, int allow_dirs) +bool match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, + char_u *sfname, char_u *tail, int allow_dirs) { regmatch_T regmatch; bool result = false; |