/* vi:set ts=8 sts=4 sw=4 noet: * * VIM - Vi IMproved by Bram Moolenaar * * Do ":help uganda" in Vim to read copying and usage conditions. * Do ":help credits" in Vim to see a list of people who contributed. * See README.txt for an overview of the Vim source code. */ /* * autocmd.c: Autocommand related functions */ #include "vim.h" /* * 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; // The command to be executed (NULL // when command has been removed). char 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 it is 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; static struct event_name { char *name; // event name event_T event; // event number } event_names[] = { {"BufAdd", EVENT_BUFADD}, {"BufCreate", EVENT_BUFADD}, {"BufDelete", EVENT_BUFDELETE}, {"BufEnter", EVENT_BUFENTER}, {"BufFilePost", EVENT_BUFFILEPOST}, {"BufFilePre", EVENT_BUFFILEPRE}, {"BufHidden", EVENT_BUFHIDDEN}, {"BufLeave", EVENT_BUFLEAVE}, {"BufNew", EVENT_BUFNEW}, {"BufNewFile", EVENT_BUFNEWFILE}, {"BufRead", EVENT_BUFREADPOST}, {"BufReadCmd", EVENT_BUFREADCMD}, {"BufReadPost", EVENT_BUFREADPOST}, {"BufReadPre", EVENT_BUFREADPRE}, {"BufUnload", EVENT_BUFUNLOAD}, {"BufWinEnter", EVENT_BUFWINENTER}, {"BufWinLeave", EVENT_BUFWINLEAVE}, {"BufWipeout", EVENT_BUFWIPEOUT}, {"BufWrite", EVENT_BUFWRITEPRE}, {"BufWritePost", EVENT_BUFWRITEPOST}, {"BufWritePre", EVENT_BUFWRITEPRE}, {"BufWriteCmd", EVENT_BUFWRITECMD}, {"CmdlineChanged", EVENT_CMDLINECHANGED}, {"CmdlineEnter", EVENT_CMDLINEENTER}, {"CmdlineLeave", EVENT_CMDLINELEAVE}, {"CmdwinEnter", EVENT_CMDWINENTER}, {"CmdwinLeave", EVENT_CMDWINLEAVE}, {"CmdUndefined", EVENT_CMDUNDEFINED}, {"ColorScheme", EVENT_COLORSCHEME}, {"ColorSchemePre", EVENT_COLORSCHEMEPRE}, {"CompleteChanged", EVENT_COMPLETECHANGED}, {"CompleteDone", EVENT_COMPLETEDONE}, {"CompleteDonePre", EVENT_COMPLETEDONEPRE}, {"CursorHold", EVENT_CURSORHOLD}, {"CursorHoldI", EVENT_CURSORHOLDI}, {"CursorMoved", EVENT_CURSORMOVED}, {"CursorMovedI", EVENT_CURSORMOVEDI}, {"DiffUpdated", EVENT_DIFFUPDATED}, {"DirChanged", EVENT_DIRCHANGED}, {"DirChangedPre", EVENT_DIRCHANGEDPRE}, {"EncodingChanged", EVENT_ENCODINGCHANGED}, {"ExitPre", EVENT_EXITPRE}, {"FileEncoding", EVENT_ENCODINGCHANGED}, {"FileAppendPost", EVENT_FILEAPPENDPOST}, {"FileAppendPre", EVENT_FILEAPPENDPRE}, {"FileAppendCmd", EVENT_FILEAPPENDCMD}, {"FileChangedShell",EVENT_FILECHANGEDSHELL}, {"FileChangedShellPost",EVENT_FILECHANGEDSHELLPOST}, {"FileChangedRO", EVENT_FILECHANGEDRO}, {"FileReadPost", EVENT_FILEREADPOST}, {"FileReadPre", EVENT_FILEREADPRE}, {"FileReadCmd", EVENT_FILEREADCMD}, {"FileType", EVENT_FILETYPE}, {"FileWritePost", EVENT_FILEWRITEPOST}, {"FileWritePre", EVENT_FILEWRITEPRE}, {"FileWriteCmd", EVENT_FILEWRITECMD}, {"FilterReadPost", EVENT_FILTERREADPOST}, {"FilterReadPre", EVENT_FILTERREADPRE}, {"FilterWritePost", EVENT_FILTERWRITEPOST}, {"FilterWritePre", EVENT_FILTERWRITEPRE}, {"FocusGained", EVENT_FOCUSGAINED}, {"FocusLost", EVENT_FOCUSLOST}, {"FuncUndefined", EVENT_FUNCUNDEFINED}, {"GUIEnter", EVENT_GUIENTER}, {"GUIFailed", EVENT_GUIFAILED}, {"InsertChange", EVENT_INSERTCHANGE}, {"InsertEnter", EVENT_INSERTENTER}, {"InsertLeave", EVENT_INSERTLEAVE}, {"InsertLeavePre", EVENT_INSERTLEAVEPRE}, {"InsertCharPre", EVENT_INSERTCHARPRE}, {"MenuPopup", EVENT_MENUPOPUP}, {"ModeChanged", EVENT_MODECHANGED}, {"OptionSet", EVENT_OPTIONSET}, {"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST}, {"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE}, {"QuitPre", EVENT_QUITPRE}, {"RemoteReply", EVENT_REMOTEREPLY}, {"SafeState", EVENT_SAFESTATE}, {"SafeStateAgain", EVENT_SAFESTATEAGAIN}, {"SessionLoadPost", EVENT_SESSIONLOADPOST}, {"ShellCmdPost", EVENT_SHELLCMDPOST}, {"ShellFilterPost", EVENT_SHELLFILTERPOST}, {"SigUSR1", EVENT_SIGUSR1}, {"SourceCmd", EVENT_SOURCECMD}, {"SourcePre", EVENT_SOURCEPRE}, {"SourcePost", EVENT_SOURCEPOST}, {"SpellFileMissing",EVENT_SPELLFILEMISSING}, {"StdinReadPost", EVENT_STDINREADPOST}, {"StdinReadPre", EVENT_STDINREADPRE}, {"SwapExists", EVENT_SWAPEXISTS}, {"Syntax", EVENT_SYNTAX}, {"TabNew", EVENT_TABNEW}, {"TabClosed", EVENT_TABCLOSED}, {"TabEnter", EVENT_TABENTER}, {"TabLeave", EVENT_TABLEAVE}, {"TermChanged", EVENT_TERMCHANGED}, {"TerminalOpen", EVENT_TERMINALOPEN}, {"TerminalWinOpen", EVENT_TERMINALWINOPEN}, {"TermResponse", EVENT_TERMRESPONSE}, {"TextChanged", EVENT_TEXTCHANGED}, {"TextChangedI", EVENT_TEXTCHANGEDI}, {"TextChangedP", EVENT_TEXTCHANGEDP}, {"TextChangedT", EVENT_TEXTCHANGEDT}, {"User", EVENT_USER}, {"VimEnter", EVENT_VIMENTER}, {"VimLeave", EVENT_VIMLEAVE}, {"VimLeavePre", EVENT_VIMLEAVEPRE}, {"WinNew", EVENT_WINNEW}, {"WinClosed", EVENT_WINCLOSED}, {"WinEnter", EVENT_WINENTER}, {"WinLeave", EVENT_WINLEAVE}, {"WinResized", EVENT_WINRESIZED}, {"WinScrolled", EVENT_WINSCROLLED}, {"VimResized", EVENT_VIMRESIZED}, {"TextYankPost", EVENT_TEXTYANKPOST}, {"VimSuspend", EVENT_VIMSUSPEND}, {"VimResume", EVENT_VIMRESUME}, {NULL, (event_T)0} }; static AutoPat *first_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 }; 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 }; #define AUGROUP_DEFAULT (-1) // default autocmd group #define AUGROUP_ERROR (-2) // erroneous autocmd group #define AUGROUP_ALL (-3) // all autocmd groups /* * struct used to keep status while executing autocommands for an event. */ struct AutoPatCmd_S { 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 sctx_T script_ctx; // script context where it is defined int arg_bufnr; // Initially equal to , set to zero when // buf is deleted. AutoPatCmd_T *next; // chain of active apc-s for auto-invalidation }; static AutoPatCmd_T *active_apc_list = NULL; // stack of active autocommands // Macro to loop over all the patterns for an autocmd event #define FOR_ALL_AUTOCMD_PATTERNS(event, ap) \ for ((ap) = first_autopat[(int)(event)]; (ap) != NULL; (ap) = (ap)->next) /* * augroups stores a list of autocmd group names. */ static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; #define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) // use get_deleted_augroup() to get this static char_u *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 char_u *event_nr2name(event_T event); static int au_get_grouparg(char_u **argp); static int do_autocmd_event(event_T event, char_u *pat, int once, int nested, char_u *cmd, int forceit, int group, int flags); static int apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, int force, int group, buf_T *buf, exarg_T *eap); static void auto_next_pat(AutoPatCmd_T *apc, int stop_at_last); static int au_find_group(char_u *name); static event_T last_event; static int last_group; static int autocmd_blocked = 0; // block all autocmds static char_u * get_deleted_augroup(void) { if (deleted_augroup == NULL) deleted_augroup = (char_u *)_("--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; // Make sure no info referenced by "ap" is cleared, e.g. when a timer // clears an augroup. Jump to "theend" after this! // "ap->pat" may be cleared anyway. ++autocmd_busy; msg_putchar('\n'); if (got_int) goto theend; if (event != last_event || ap->group != last_group) { if (ap->group != AUGROUP_DEFAULT) { if (AUGROUP_NAME(ap->group) == NULL) msg_puts_attr((char *)get_deleted_augroup(), HL_ATTR(HLF_E)); else msg_puts_attr((char *)AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); msg_puts(" "); } msg_puts_attr((char *)event_nr2name(event), HL_ATTR(HLF_T)); last_event = event; last_group = ap->group; msg_putchar('\n'); if (got_int) goto theend; } if (ap->pat == NULL) goto theend; // timer might have cleared the pattern or group 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) goto theend; msg_outtrans(ac->cmd); #ifdef FEAT_EVAL if (p_verbose > 0) last_set_msg(ac->script_ctx); #endif if (got_int) goto theend; if (ac->next != NULL) { msg_putchar('\n'); if (got_int) goto theend; } } theend: --autocmd_busy; } /* * Mark an autocommand pattern for deletion. */ static void au_remove_pat(AutoPat *ap) { VIM_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) { AutoCmd *ac; for (ac = ap->cmds; ac != NULL; ac = ac->next) VIM_CLEAR(ac->cmd); au_need_clean = TRUE; } // Delete one command from an autocmd pattern. static void au_del_cmd(AutoCmd *ac) { VIM_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; AutoCmd *ac, **prev_ac; event_T event; if (autocmd_busy || !au_need_clean) return; // loop over all events for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) { // loop over all autocommand patterns prev_ap = &(first_autopat[(int)event]); for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { int has_cmd = FALSE; // loop over all commands for this pattern prev_ac = &(ap->cmds); for (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; vim_free(ac->cmd); vim_free(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); vim_free(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_T *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 < NUM_EVENTS; event = (event_T)((int)event + 1)) // loop over all autocommand patterns FOR_ALL_AUTOCMD_PATTERNS(event, ap) if (ap->buflocal_nr == buf->b_fnum) { au_remove_pat(ap); if (p_verbose >= 6) { verbose_enter(); smsg(_("auto-removing autocommand: %s "), 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; i = au_find_group(name); if (i != AUGROUP_ERROR) return i; // 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) == FAIL) return AUGROUP_ERROR; AUGROUP_NAME(i) = vim_strsave(name); if (AUGROUP_NAME(i) == NULL) return AUGROUP_ERROR; if (i == augroups.ga_len) ++augroups.ga_len; return i; } static void au_del_group(char_u *name) { int i; event_T event; AutoPat *ap; int in_use = FALSE; i = au_find_group(name); if (i == AUGROUP_ERROR) // the group doesn't exist { semsg(_(e_no_such_group_str), name); return; } if (i == current_augroup) { emsg(_(e_cannot_delete_current_group)); return; } for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) { FOR_ALL_AUTOCMD_PATTERNS(event, ap) 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; } } vim_free(AUGROUP_NAME(i)); if (in_use) AUGROUP_NAME(i) = get_deleted_augroup(); else AUGROUP_NAME(i) = NULL; } /* * Find the ID of an autocmd group name. * Return its ID. Returns AUGROUP_ERROR (< 0) for error. */ static int au_find_group(char_u *name) { int i; for (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. */ int au_has_group(char_u *name) { return au_find_group(name) != AUGROUP_ERROR; } /* * ":augroup {name}". */ void do_augroup(char_u *arg, int del_group) { int i; if (del_group) { if (*arg == NUL) emsg(_(e_argument_required)); 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 { i = au_new_group(arg); if (i != AUGROUP_ERROR) current_augroup = i; } else // ":aug": list the group names { msg_start(); for (i = 0; i < augroups.ga_len; ++i) { if (AUGROUP_NAME(i) != NULL) { msg_puts((char *)AUGROUP_NAME(i)); msg_puts(" "); } } msg_clr_eos(); msg_end(); } } void autocmd_init(void) { CLEAR_FIELD(aucmd_win); } #if defined(EXITFREE) || defined(PROTO) void free_all_autocmds(void) { char_u *s; for (current_augroup = -1; current_augroup < augroups.ga_len; ++current_augroup) do_autocmd(NULL, (char_u *)"", TRUE); for (int i = 0; i < augroups.ga_len; ++i) { s = ((char_u **)(augroups.ga_data))[i]; if (s != get_deleted_augroup()) vim_free(s); } ga_clear(&augroups); // aucmd_win[] is freed in win_free_all() } #endif /* * Return TRUE if "win" is an active entry in aucmd_win[]. */ int is_aucmd_win(win_T *win) { for (int i = 0; i < AUCMD_WIN_COUNT; ++i) if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win) return TRUE; return FALSE; } /* * 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(char_u *start, char_u **end) { char_u *p; int i; int len; // the event name ends with end of line, '|', a blank or a comma for (p = start; *p && !VIM_ISWHITE(*p) && *p != ',' && *p != '|'; ++p) ; for (i = 0; event_names[i].name != NULL; ++i) { len = (int)STRLEN(event_names[i].name); if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) break; } if (*p == ',') ++p; *end = p; if (event_names[i].name == NULL) return NUM_EVENTS; return event_names[i].event; } /* * Return the name for event "event". */ static char_u * event_nr2name(event_T event) { int i; for (i = 0; event_names[i].name != NULL; ++i) if (event_names[i].event == event) return (char_u *)event_names[i].name; return (char_u *)"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] && !VIM_ISWHITE(arg[1])) { semsg(_(e_illegal_character_after_star_str), arg); return NULL; } pat = arg + 1; } else { for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p) { if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) { if (have_group) semsg(_(e_no_such_event_str), pat); else semsg(_(e_no_such_group_or_event_str), pat); return NULL; } } } return pat; } /* * Return TRUE if "event" is included in 'eventignore'. */ static int event_ignored(event_T event) { 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; } # if defined(FEAT_SYN_HL) || defined(PROTO) /* * 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); if (save_ei == NULL) return NULL; new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); if (new_ei == NULL) { vim_free(save_ei); return NULL; } 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); vim_free(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); vim_free(old_ei); } } # endif // FEAT_SYN_HL /* * do_autocmd() -- implements the :autocmd command. Can be used in the * following ways: * * :autocmd Add to the list of commands that * will be automatically executed for * when editing a file matching , in * the current group. * :autocmd Show the autocommands associated with * and . * :autocmd Show the autocommands associated with * . * :autocmd Show all autocommands. * :autocmd! Remove all autocommands associated with * and , and add the command * , for the current group. * :autocmd! Remove all autocommands associated with * and for the current group. * :autocmd! Remove all autocommands associated with * 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 . * "eap" can be NULL. */ void do_autocmd(exarg_T *eap, char_u *arg_in, int forceit) { char_u *arg = arg_in; char_u *pat; char_u *envpat = NULL; char_u *cmd; int cmd_need_free = FALSE; event_T event; char_u *tofree = NULL; int nested = FALSE; int once = FALSE; int group; int i; int flags = 0; if (*arg == '|') { eap->nextcmd = arg + 1; 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); if (arg == NULL) // out of memory return; } /* * 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 == '|') { eap->nextcmd = pat + 1; pat = (char_u *)""; cmd = (char_u *)""; } else { /* * Scan over the pattern. Put a NUL at the end. */ cmd = pat; while (*cmd && (!VIM_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 (i = 0; i < 2; i++) { if (*cmd == NUL) continue; // Check for "++once" flag. if (STRNCMP(cmd, "++once", 6) == 0 && VIM_ISWHITE(cmd[6])) { if (once) semsg(_(e_duplicate_argument_str), "++once"); once = TRUE; cmd = skipwhite(cmd + 6); } // Check for "++nested" flag. if ((STRNCMP(cmd, "++nested", 8) == 0 && VIM_ISWHITE(cmd[8]))) { if (nested) { semsg(_(e_duplicate_argument_str), "++nested"); return; } nested = TRUE; cmd = skipwhite(cmd + 8); } // Check for the old "nested" flag in legacy script. if (STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6])) { if (in_vim9script()) { // If there ever is a :nested command this error should // be removed and "nested" accepted as the start of the // command. emsg(_(e_invalid_command_nested_did_you_mean_plusplus_nested)); return; } if (nested) { semsg(_(e_duplicate_argument_str), "nested"); return; } nested = TRUE; cmd = skipwhite(cmd + 6); } } /* * Find the start of the commands. * Expand in it. */ if (*cmd != NUL) { if (eap != NULL) // Read a {} block if it follows. cmd = may_get_cmd_block(eap, cmd, &tofree, &flags); cmd = expand_sfile(cmd); if (cmd == NULL) // some error return; cmd_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 == '|') { if (*cmd != NUL) emsg(_(e_cannot_define_autocommands_for_all_events)); else for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group, flags) == FAIL) break; } else { while (*arg && *arg != '|' && !VIM_ISWHITE(*arg)) if (do_autocmd_event(event_name2nr(arg, &arg), pat, once, nested, cmd, forceit, group, flags) == FAIL) break; } if (cmd_need_free) vim_free(cmd); vim_free(tofree); vim_free(envpat); } /* * Find the group ID in a ":autocmd" or ":doautocmd" argument. * The "argp" argument is advanced to the following argument. * * Returns the group ID, AUGROUP_ERROR for error (out of memory). */ 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 && !VIM_ISWHITE(*p) && *p != '|'; ++p) ; if (p <= arg) return AUGROUP_ALL; group_name = vim_strnsave(arg, p - arg); if (group_name == NULL) // out of memory return AUGROUP_ERROR; 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 vim_free(group_name); return group; } /* * do_autocmd() for one event. * 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, int once, int nested, char_u *cmd, int forceit, int group, int flags) { 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 "" 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_ALL_AUTOCMD_PATTERNS(event, ap) { 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\}". */ brace_level = 0; for (endpat = pat; *endpat && (*endpat != ',' || brace_level || (endpat > pat && endpat[-1] == '\\')); ++endpat) { if (*endpat == '{') brace_level++; else if (*endpat == '}') brace_level--; } if (pat == endpat) // ignore single comma continue; patlen = (int)(endpat - pat); /* * detect special buffer-local patterns */ is_buflocal = FALSE; buflocal_nr = 0; if (patlen >= 8 && STRNCMP(pat, "') { // "": Error will be printed only for addition. // printing and removing will proceed silently. is_buflocal = TRUE; if (patlen == 8) // "" buflocal_nr = curbuf->b_fnum; else if (patlen > 9 && pat[7] == '=') { if (patlen == 13 && STRNICMP(pat, "", 13) == 0) // "" buflocal_nr = autocmd_bufnr; else if (skipdigits(pat + 8) == pat + patlen - 1) // "" buflocal_nr = atoi((char *)pat + 8); } } if (is_buflocal) { // normalize pat into standard "#N" form sprintf((char *)buflocal_pat, "", 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 , 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 */ 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)) { semsg(_(e_buffer_nr_invalid_buffer_number), buflocal_nr); return FAIL; } ap = ALLOC_ONE(AutoPat); if (ap == NULL) return FAIL; ap->pat = vim_strnsave(pat, patlen); ap->patlen = patlen; if (ap->pat == NULL) { vim_free(ap); return FAIL; } #ifdef FEAT_EVAL // need to initialize last_mode for the first ModeChanged // autocmd if (event == EVENT_MODECHANGED && !has_modechanged()) get_mode(last_mode); #endif // Initialize the fields checked by the WinScrolled and // WinResized trigger to prevent them from firing right after // the first autocmd is defined. if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED) && !(has_winscrolled() || has_winresized())) { tabpage_T *save_curtab = curtab; tabpage_T *tp; FOR_ALL_TABPAGES(tp) { unuse_tabpage(curtab); use_tabpage(tp); snapshot_windows_scroll_size(); } unuse_tabpage(curtab); use_tabpage(save_curtab); } 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); vim_free(reg_pat); if (reg_pat == NULL || ap->reg_prog == NULL) { vim_free(ap->pat); vim_free(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 = ALLOC_ONE(AutoCmd); if (ac == NULL) return FAIL; ac->cmd = vim_strsave(cmd); ac->script_ctx = current_sctx; if (flags & UC_VIM9) ac->script_ctx.sc_version = SCRIPT_VERSION_VIM9; #ifdef FEAT_EVAL ac->script_ctx.sc_lnum += SOURCING_LNUM; #endif if (ac->cmd == NULL) { vim_free(ac); return FAIL; } 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_start, int do_msg, // give message for no matching autocmds? int *did_something) { char_u *arg = arg_start; 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 == NULL) // out of memory return FAIL; if (*arg == '*') { emsg(_(e_cant_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) && !VIM_ISWHITE(*arg)) if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, TRUE, group, curbuf, NULL)) nothing_done = FALSE; if (nothing_done && do_msg #ifdef FEAT_EVAL && !aborting() #endif ) smsg(_("No matching autocommands: %s"), arg_start); if (did_something != NULL) *did_something = !nothing_done; #ifdef FEAT_EVAL return aborting() ? FAIL : OK; #else return OK; #endif } /* * ":doautoall": execute autocommands for each loaded buffer. */ void ex_doautoall(exarg_T *eap) { int retval = OK; aco_save_T aco; buf_T *buf; bufref_T bufref; char_u *arg = eap->arg; int call_do_modelines = check_nomodeline(&arg); int did_aucmd; /* * 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) { // Only do loaded buffers and skip the current buffer, it's done last. if (buf->b_ml.ml_mfp == NULL || buf == curbuf) continue; // Find a window for this buffer and save some values. aucmd_prepbuf(&aco, buf); if (curbuf != buf) { // Failed to find a window for this buffer. Better not execute // autocommands then. retval = FAIL; break; } set_bufref(&bufref, buf); // 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(is_aucmd_win(curwin) ? 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)) { retval = FAIL; break; } } // Execute autocommands for the current buffer last. if (retval == OK) { do_doautocmd(arg, FALSE, &did_aucmd); if (call_do_modelines && did_aucmd) do_modelines(0); } } /* * Check *argp for . When it is present return FALSE, otherwise * return TRUE and advance *argp to after it. * Thus return TRUE when do_modelines() should be called. */ int check_nomodeline(char_u **argp) { if (STRNCMP(*argp, "", 12) == 0) { *argp = skipwhite(*argp + 12); return FALSE; } return TRUE; } /* * Prepare for executing autocommands for (hidden) buffer "buf". * Search for a visible window containing the current buffer. If there isn't * one then use an entry in "aucmd_win[]". * Set "curbuf" and "curwin" to match "buf". * When this fails "curbuf" is not equal "buf". */ void aucmd_prepbuf( aco_save_T *aco, // structure to save values in buf_T *buf) // new curbuf { win_T *win; int save_ea; #ifdef FEAT_AUTOCHDIR int save_acd; #endif // Find a window that is for the new buffer if (buf == curbuf) // be quick when buf is curbuf win = curwin; else FOR_ALL_WINDOWS(win) if (win->w_buffer == buf) break; // Allocate a window when needed. win_T *auc_win = NULL; int auc_idx = AUCMD_WIN_COUNT; if (win == NULL) { for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; ++auc_idx) if (!aucmd_win[auc_idx].auc_win_used) { if (aucmd_win[auc_idx].auc_win == NULL) aucmd_win[auc_idx].auc_win = win_alloc_popup_win(); auc_win = aucmd_win[auc_idx].auc_win; if (auc_win != NULL) aucmd_win[auc_idx].auc_win_used = TRUE; break; } // If this fails (out of memory or using all AUCMD_WIN_COUNT // entries) then we can't reliable execute the autocmd, return with // "curbuf" unequal "buf". if (auc_win == NULL) return; } aco->save_curwin_id = curwin->w_id; aco->save_curbuf = curbuf; aco->save_prevwin_id = prevwin == NULL ? 0 : prevwin->w_id; 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_idx = -1; curwin = win; } else { // There is no window for "buf", use "auc_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_idx = auc_idx; win_init_popup_win(auc_win, buf); aco->globaldir = globaldir; globaldir = NULL; // Split the current window, put the auc_win in the upper half. // We don't want the BufEnter or WinEnter autocommands. block_autocmds(); make_snapshot(SNAP_AUCMD_IDX); save_ea = p_ea; p_ea = FALSE; #ifdef FEAT_AUTOCHDIR // Prevent chdir() call in win_enter_ext(), through do_autochdir(). save_acd = p_acd; p_acd = FALSE; #endif // no redrawing and don't set the window title ++RedrawingDisabled; (void)win_split_ins(0, WSP_TOP, auc_win, 0); --RedrawingDisabled; (void)win_comp_pos(); // recompute window positions p_ea = save_ea; #ifdef FEAT_AUTOCHDIR p_acd = save_acd; #endif unblock_autocmds(); curwin = auc_win; } curbuf = buf; aco->new_curwin_id = curwin->w_id; set_bufref(&aco->new_curbuf, curbuf); // disable the Visual area, the position may be invalid in another buffer aco->save_VIsual_active = VIsual_active; VIsual_active = FALSE; } /* * Cleanup after executing autocommands for a (hidden) buffer. * Restore the window as it was (if possible). */ void aucmd_restbuf( aco_save_T *aco) // structure holding saved values { int dummy; win_T *save_curwin; if (aco->use_aucmd_win_idx >= 0) { win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win; --curbuf->b_nwindows; // Find "awp", it can't be closed, but it may be in another tab // page. Do not trigger autocommands here. block_autocmds(); if (curwin != awp) { tabpage_T *tp; win_T *wp; FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp == awp) { if (tp != curtab) goto_tabpage_tp(tp, TRUE, TRUE); win_goto(awp); goto win_found; } } } win_found: // Remove the window and frame from the tree of frames. (void)winframe_remove(curwin, &dummy, NULL); win_remove(curwin, NULL); // The window is marked as not used, but it is not freed, it can be // used again. aucmd_win[aco->use_aucmd_win_idx].auc_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); restore_snapshot(SNAP_AUCMD_IDX, FALSE); (void)win_comp_pos(); // recompute window positions unblock_autocmds(); save_curwin = win_find_by_id(aco->save_curwin_id); if (save_curwin != NULL) curwin = save_curwin; else // Hmm, original window disappeared. Just use the first one. curwin = firstwin; curbuf = curwin->w_buffer; #ifdef FEAT_JOB_CHANNEL // May need to restore insert mode for a prompt buffer. entering_window(curwin); #endif prevwin = win_find_by_id(aco->save_prevwin_id); #ifdef FEAT_EVAL vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab #endif vim_free(globaldir); globaldir = aco->globaldir; // the buffer contents may have changed VIsual_active = aco->save_VIsual_active; check_cursor(); if (curwin->w_topline > curbuf->b_ml.ml_line_count) { curwin->w_topline = curbuf->b_ml.ml_line_count; #ifdef FEAT_DIFF curwin->w_topfill = 0; #endif } #if defined(FEAT_GUI) if (gui.in_use) { // Hide the scrollbars from the "awp" and update. gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_LEFT], FALSE); gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_RIGHT], FALSE); gui_may_update_scrollbars(); } #endif } else { // Restore curwin. Use the window ID, a window may have been closed // and the memory re-used for another one. save_curwin = win_find_by_id(aco->save_curwin_id); if (save_curwin != NULL) { // 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->w_id == aco->new_curwin_id && curbuf != aco->new_curbuf.br_buf && bufref_valid(&aco->new_curbuf) && aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL) { # if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) if (curwin->w_s == &curbuf->b_s) curwin->w_s = &aco->new_curbuf.br_buf->b_s; # endif --curbuf->b_nwindows; curbuf = aco->new_curbuf.br_buf; curwin->w_buffer = curbuf; ++curbuf->b_nwindows; } curwin = save_curwin; curbuf = curwin->w_buffer; prevwin = win_find_by_id(aco->save_prevwin_id); // In case the autocommand moves the cursor to a position that // does not exist in curbuf. VIsual_active = aco->save_VIsual_active; check_cursor(); } } VIsual_active = aco->save_VIsual_active; check_cursor(); // just in case lines got deleted if (VIsual_active) check_pos(curbuf, &VIsual); } static int autocmd_nested = FALSE; /* * Execute autocommands for "event" and file name "fname". * Return TRUE if some commands were executed. */ int apply_autocmds( event_T event, char_u *fname, // NULL or empty means use actual file name char_u *fname_io, // fname to use for on cmdline int force, // when TRUE, ignore autocmd_busy buf_T *buf) // buffer for { 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. */ int apply_autocmds_exarg( event_T event, char_u *fname, char_u *fname_io, int 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. */ int apply_autocmds_retval( event_T event, char_u *fname, // NULL or empty means use actual file name char_u *fname_io, // fname to use for on cmdline int force, // when TRUE, ignore autocmd_busy buf_T *buf, // buffer for int *retval) // pointer to caller's retval { int did_cmd; #ifdef FEAT_EVAL if (should_abort(*retval)) return FALSE; #endif did_cmd = apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, NULL); if (did_cmd #ifdef FEAT_EVAL && aborting() #endif ) *retval = FAIL; return did_cmd; } /* * Return TRUE when there is a CursorHold autocommand defined. */ static int has_cursorhold(void) { return (first_autopat[(int)(get_real_state() == MODE_NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL); } /* * Return TRUE if the CursorHold event can be triggered. */ int trigger_cursorhold(void) { int state; if (!did_cursorhold && has_cursorhold() && reg_recording == 0 && typebuf.tb_len == 0 && !ins_compl_active()) { state = get_real_state(); if (state == MODE_NORMAL_BUSY || (state & MODE_INSERT) != 0) return TRUE; } return FALSE; } /* * Return TRUE when there is a WinResized autocommand defined. */ int has_winresized(void) { return (first_autopat[(int)EVENT_WINRESIZED] != NULL); } /* * Return TRUE when there is a WinScrolled autocommand defined. */ int has_winscrolled(void) { return (first_autopat[(int)EVENT_WINSCROLLED] != NULL); } /* * Return TRUE when there is a CursorMoved autocommand defined. */ int has_cursormoved(void) { return (first_autopat[(int)EVENT_CURSORMOVED] != NULL); } /* * Return TRUE when there is a CursorMovedI autocommand defined. */ int has_cursormovedI(void) { return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL); } /* * Return TRUE when there is a TextChanged autocommand defined. */ int has_textchanged(void) { return (first_autopat[(int)EVENT_TEXTCHANGED] != NULL); } /* * Return TRUE when there is a TextChangedI autocommand defined. */ int has_textchangedI(void) { return (first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL); } /* * Return TRUE when there is a TextChangedP autocommand defined. */ int has_textchangedP(void) { return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL); } /* * Return TRUE when there is an InsertCharPre autocommand defined. */ int has_insertcharpre(void) { return (first_autopat[(int)EVENT_INSERTCHARPRE] != NULL); } /* * Return TRUE when there is an CmdUndefined autocommand defined. */ int has_cmdundefined(void) { return (first_autopat[(int)EVENT_CMDUNDEFINED] != NULL); } #if defined(FEAT_EVAL) || defined(PROTO) /* * Return TRUE when there is a TextYankPost autocommand defined. */ int has_textyankpost(void) { return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL); } #endif #if defined(FEAT_EVAL) || defined(PROTO) /* * Return TRUE when there is a CompleteChanged autocommand defined. */ int has_completechanged(void) { return (first_autopat[(int)EVENT_COMPLETECHANGED] != NULL); } #endif #if defined(FEAT_EVAL) || defined(PROTO) /* * Return TRUE when there is a ModeChanged autocommand defined. */ int has_modechanged(void) { return (first_autopat[(int)EVENT_MODECHANGED] != NULL); } #endif /* * Execute autocommands for "event" and file name "fname". * Return TRUE if some commands were executed. */ static int apply_autocmds_group( event_T event, char_u *fname, // NULL or empty means use actual file name char_u *fname_io, // fname to use for on cmdline, NULL means // use fname int force, // when TRUE, ignore autocmd_busy int group, // group ID, or AUGROUP_ALL buf_T *buf, // buffer for exarg_T *eap UNUSED) // command arguments { char_u *sfname = NULL; // short file name char_u *tail; int save_changed; buf_T *old_curbuf; int retval = FALSE; char_u *save_autocmd_fname; int save_autocmd_fname_full; int save_autocmd_bufnr; char_u *save_autocmd_match; int save_autocmd_busy; int save_autocmd_nested; static int nesting = 0; AutoPatCmd_T patcmd; AutoPat *ap; sctx_T save_current_sctx; #ifdef FEAT_EVAL funccal_entry_T funccal_entry; char_u *save_cmdarg; long save_cmdbang; #endif static int filechangeshell_busy = FALSE; #ifdef FEAT_PROFILE proftime_T wait_time; #endif int did_save_redobuff = FALSE; save_redo_T save_redo; int save_KeyTyped = KeyTyped; int save_did_emsg; ESTACK_CHECK_DECLARATION /* * 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; #ifdef FEAT_EVAL /* * 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; #endif /* * 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(_(e_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; if (event == EVENT_CMDLINECHANGED) ++aucmd_cmdline_changed_count; /* * Save the autocmd_* variables and info about the current buffer. */ save_autocmd_fname = autocmd_fname; save_autocmd_fname_full = autocmd_fname_full; 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 . * 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 || event == EVENT_MODECHANGED) 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) autocmd_fname = vim_strsave(autocmd_fname); autocmd_fname_full = FALSE; // call FullName_save() later /* * Set the buffer number to be used for . */ 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 { #ifdef FEAT_SYN_HL if (event == EVENT_SYNTAX) fname = buf->b_p_syn; else #endif 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 FileType, Syntax, FuncUndefined, WindowID, // ColorScheme, QuickFixCmd*, DirChanged and similar. if (event == EVENT_FILETYPE || event == EVENT_SYNTAX || event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER || event == EVENT_CMDLINELEAVE || event == EVENT_CMDWINENTER || event == EVENT_CMDWINLEAVE || event == EVENT_CMDUNDEFINED || event == EVENT_FUNCUNDEFINED || event == EVENT_REMOTEREPLY || event == EVENT_SPELLFILEMISSING || event == EVENT_QUICKFIXCMDPRE || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE || event == EVENT_MODECHANGED || event == EVENT_MENUPOPUP || event == EVENT_USER || event == EVENT_WINCLOSED || event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) { fname = vim_strsave(fname); autocmd_fname_full = TRUE; // don't expand it later } else fname = FullName_save(fname, FALSE); } if (fname == NULL) // out of memory { vim_free(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 MS-DOS. */ if (sfname != NULL) forward_slash(sfname); forward_slash(fname); #endif #ifdef VMS // remove version for correct match if (sfname != NULL) vms_remove_version(sfname); vms_remove_version(fname); #endif /* * Set the name to be used for . */ autocmd_match = fname; // Don't redraw while doing autocommands. ++RedrawingDisabled; // name and lnum are filled in later estack_push(ETYPE_AUCMD, NULL, 0); ESTACK_CHECK_SETUP save_current_sctx = current_sctx; #ifdef FEAT_EVAL # ifdef FEAT_PROFILE if (do_profiling == PROF_YES) prof_child_enter(&wait_time); // doesn't count for the caller itself # endif // Don't use local function variables, if called from a function. save_funccal(&funccal_entry); #endif /* * 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 = gettail(fname); // Find first autocommand that matches CLEAR_FIELD(patcmd); patcmd.curpat = first_autopat[(int)event]; patcmd.group = group; patcmd.fname = fname; patcmd.sfname = sfname; patcmd.tail = tail; patcmd.event = event; patcmd.arg_bufnr = autocmd_bufnr; 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; #ifdef FEAT_EVAL // 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 #endif retval = TRUE; // mark the last pattern, to avoid an endless loop when more patterns // are added when executing autocommands for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) ap->last = FALSE; ap->last = TRUE; // Make sure cursor and topline are valid. The first time the current // values are saved, restored by reset_lnums(). When nested only the // values are corrected when needed. if (nesting == 1) check_lnums(TRUE); else check_lnums_nested(TRUE); save_did_emsg = did_emsg; do_cmdline(NULL, getnextac, (void *)&patcmd, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); did_emsg += save_did_emsg; if (nesting == 1) // restore cursor and topline, unless they were changed reset_lnums(); #ifdef FEAT_EVAL if (eap != NULL) { (void)set_cmdarg(NULL, save_cmdarg); set_vim_var_nr(VV_CMDBANG, save_cmdbang); } #endif // 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; vim_free(SOURCING_NAME); ESTACK_CHECK_NOW estack_pop(); vim_free(autocmd_fname); autocmd_fname = save_autocmd_fname; autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; autocmd_match = save_autocmd_match; current_sctx = save_current_sctx; #ifdef FEAT_EVAL restore_funccal(); # ifdef FEAT_PROFILE if (do_profiling == PROF_YES) prof_child_exit(&wait_time); # endif #endif KeyTyped = save_KeyTyped; vim_free(fname); vim_free(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; vim_free(au_pending_free_buf); au_pending_free_buf = b; } while (au_pending_free_win != NULL) { win_T *w = au_pending_free_win->w_next; vim_free(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; } # ifdef FEAT_EVAL static char_u *old_termresponse = NULL; # endif /* * Block triggering autocommands until unblock_autocmd() is called. * Can be used recursively, so long as it's symmetric. */ void block_autocmds(void) { # ifdef FEAT_EVAL // Remember the value of v:termresponse. if (autocmd_blocked == 0) old_termresponse = get_vim_var_str(VV_TERMRESPONSE); # endif ++autocmd_blocked; } void unblock_autocmds(void) { --autocmd_blocked; # ifdef FEAT_EVAL // When v:termresponse was set while autocommands were blocked, trigger // the autocommands now. Esp. useful when executing a shell command // during startup (vimdiff). if (autocmd_blocked == 0 && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); # endif } int is_autocmd_blocked(void) { return autocmd_blocked != 0; } /* * Find next autocommand pattern that matches. */ static void auto_next_pat( AutoPatCmd_T *apc, int stop_at_last) // stop when 'last' flag is set { AutoPat *ap; AutoCmd *cp; char_u *name; char *s; estack_T *entry; char_u *namep; entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1; // Clear the exestack entry for this ETYPE_AUCMD entry. VIM_CLEAR(entry->es_name); entry->es_info.aucmd = NULL; 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) { name = event_nr2name(apc->event); s = _("%s Autocommands for \"%s\""); namep = alloc(STRLEN(s) + STRLEN(name) + ap->patlen + 1); if (namep != NULL) { sprintf((char *)namep, s, (char *)name, (char *)ap->pat); if (p_verbose >= 8) { verbose_enter(); smsg(_("Executing %s"), namep); verbose_leave(); } } // Update the exestack entry for this autocmd. entry->es_name = namep; entry->es_info.aucmd = apc; 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 the script context where autocommand "acp" is defined. */ sctx_T * acp_script_ctx(AutoPatCmd_T *acp) { return &acp->script_ctx; } /* * Get next autocommand command. * Called by do_cmdline() to get the next line for ":if". * Returns allocated string, or NULL for end of autocommands. */ char_u * getnextac( int c UNUSED, void *cookie, int indent UNUSED, getline_opt_T options UNUSED) { AutoPatCmd_T *acp = (AutoPatCmd_T *)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; acp->script_ctx = current_sctx; 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. */ int has_autocmd(event_T event, char_u *sfname, buf_T *buf) { AutoPat *ap; char_u *fname; char_u *tail = gettail(sfname); int retval = FALSE; fname = FullName_save(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 MS-DOS. */ sfname = vim_strsave(sfname); if (sfname != NULL) forward_slash(sfname); forward_slash(fname); #endif FOR_ALL_AUTOCMD_PATTERNS(event, ap) 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; } vim_free(fname); #ifdef BACKSLASH_IN_FILENAME vim_free(sfname); #endif return retval; } /* * Function given to ExpandGeneric() to obtain the list of autocommand group * names. */ char_u * get_augroup_name(expand_T *xp UNUSED, int idx) { if (idx == augroups.ga_len) // add "END" add the end return (char_u *)"END"; if (idx < 0 || 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 AUGROUP_NAME(idx); // return a name } 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 (group == AUGROUP_ERROR) return NULL; // If there only is a group name that's what we expand. if (*arg == NUL && group != AUGROUP_ALL && !VIM_ISWHITE(arg[-1])) { arg = p; group = AUGROUP_ALL; } // skip over event name for (p = arg; *p != NUL && !VIM_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 && (!VIM_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 UNUSED, 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 AUGROUP_NAME(idx); // return a name } return (char_u *)event_names[idx - augroups.ga_len].name; } #if defined(FEAT_EVAL) || defined(PROTO) /* * Return TRUE if autocmd is supported. */ int autocmd_supported(char_u *name) { char_u *p; return (event_name2nr(name, &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 NULL to accept any event and pattern. "pattern" can be NULL to accept * any pattern. Buffer-local patterns or are accepted. * Used for: * exists("#Group") or * exists("#Group#Event") or * exists("#Group#Event#pat") or * exists("#Event") or * exists("#Event#pat") */ int au_exists(char_u *arg) { char_u *arg_save; char_u *pattern = NULL; char_u *event_name; char_u *p; event_T event; AutoPat *ap; buf_T *buflocal_buf = NULL; int group; int retval = FALSE; // Make a copy so that we can change the '#' chars to a NUL. arg_save = vim_strsave(arg); if (arg_save == NULL) return FALSE; p = vim_strchr(arg_save, '#'); if (p != NULL) *p++ = NUL; // First, look for an autocmd group name group = au_find_group(arg_save); 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 = vim_strchr(event_name, '#'); if (p != NULL) *p++ = NUL; // "Group#Event#pat" } pattern = p; // "pattern" is NULL when there is no pattern // find the index (enum) for the event name event = event_name2nr(event_name, &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 "", special handling is needed which uses curbuf // for pattern ", fnamecmp() will work fine if (pattern != NULL && STRICMP(pattern, "") == 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, pattern) == 0 : ap->buflocal_nr == buflocal_buf->b_fnum))) { retval = TRUE; break; } theend: vim_free(arg_save); return retval; } /* * autocmd_add() and autocmd_delete() functions */ static void autocmd_add_or_delete(typval_T *argvars, typval_T *rettv, int delete) { list_T *aucmd_list; listitem_T *li; dict_T *event_dict; dictitem_T *di; char_u *event_name = NULL; list_T *event_list; listitem_T *eli; event_T event; char_u *group_name = NULL; int group; char_u *pat = NULL; list_T *pat_list; listitem_T *pli; char_u *cmd = NULL; char_u *end; int once; int nested; int replace; // replace the cmd for a group/event int retval = VVAL_TRUE; int save_augroup = current_augroup; rettv->v_type = VAR_BOOL; rettv->vval.v_number = VVAL_FALSE; if (check_for_list_arg(argvars, 0) == FAIL) return; aucmd_list = argvars[0].vval.v_list; if (aucmd_list == NULL) return; FOR_ALL_LIST_ITEMS(aucmd_list, li) { VIM_CLEAR(group_name); VIM_CLEAR(cmd); event_name = NULL; event_list = NULL; pat = NULL; pat_list = NULL; if (li->li_tv.v_type != VAR_DICT) continue; event_dict = li->li_tv.vval.v_dict; if (event_dict == NULL) continue; di = dict_find(event_dict, (char_u *)"event", -1); if (di != NULL) { if (di->di_tv.v_type == VAR_STRING) { event_name = di->di_tv.vval.v_string; if (event_name == NULL) { emsg(_(e_string_required)); continue; } } else if (di->di_tv.v_type == VAR_LIST) { event_list = di->di_tv.vval.v_list; if (event_list == NULL) { emsg(_(e_list_required)); continue; } } else { emsg(_(e_string_or_list_expected)); continue; } } group_name = dict_get_string(event_dict, "group", TRUE); if (group_name == NULL || *group_name == NUL) // if the autocmd group name is not specified, then use the current // autocmd group group = current_augroup; else { group = au_find_group(group_name); if (group == AUGROUP_ERROR) { if (delete) { semsg(_(e_no_such_group_str), group_name); retval = VVAL_FALSE; break; } // group is not found, create it now group = au_new_group(group_name); if (group == AUGROUP_ERROR) { semsg(_(e_no_such_group_str), group_name); retval = VVAL_FALSE; break; } current_augroup = group; } } // if a buffer number is specified, then generate a pattern of the form // ". Otherwise, use the pattern supplied by the user. if (dict_has_key(event_dict, "bufnr")) { varnumber_T bnum; bnum = dict_get_number_def(event_dict, "bufnr", -1); if (bnum == -1) continue; vim_snprintf((char *)IObuff, IOSIZE, "", (int)bnum); pat = IObuff; } else { di = dict_find(event_dict, (char_u *)"pattern", -1); if (di != NULL) { if (di->di_tv.v_type == VAR_STRING) { pat = di->di_tv.vval.v_string; if (pat == NULL) { emsg(_(e_string_required)); continue; } } else if (di->di_tv.v_type == VAR_LIST) { pat_list = di->di_tv.vval.v_list; if (pat_list == NULL) { emsg(_(e_list_required)); continue; } } else { emsg(_(e_string_or_list_expected)); continue; } } else if (delete) pat = (char_u *)""; } once = dict_get_bool(event_dict, "once", FALSE); nested = dict_get_bool(event_dict, "nested", FALSE); // if 'replace' is true, then remove all the commands associated with // this autocmd event/group and add the new command. replace = dict_get_bool(event_dict, "replace", FALSE); cmd = dict_get_string(event_dict, "cmd", TRUE); if (cmd == NULL) { if (delete) cmd = vim_strsave((char_u *)""); else continue; } if (delete && (event_name == NULL || (event_name[0] == '*' && event_name[1] == NUL))) { // if the event name is not specified or '*', delete all the events for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) { if (do_autocmd_event(event, pat, once, nested, cmd, delete, group, 0) == FAIL) { retval = VVAL_FALSE; break; } } } else { char_u *p = NULL; eli = NULL; end = NULL; while (TRUE) { if (event_list != NULL) { if (eli == NULL) eli = event_list->lv_first; else eli = eli->li_next; if (eli == NULL) break; if (eli->li_tv.v_type != VAR_STRING || (p = eli->li_tv.vval.v_string) == NULL) { emsg(_(e_string_required)); break; } } else { if (p == NULL) p = event_name; if (p == NULL || *p == NUL) break; } event = event_name2nr(p, &end); if (event == NUM_EVENTS || *end != NUL) { // this also catches something following a valid event name semsg(_(e_no_such_event_str), p); retval = VVAL_FALSE; break; } if (pat != NULL) { if (do_autocmd_event(event, pat, once, nested, cmd, delete | replace, group, 0) == FAIL) { retval = VVAL_FALSE; break; } } else if (pat_list != NULL) { FOR_ALL_LIST_ITEMS(pat_list, pli) { if (pli->li_tv.v_type != VAR_STRING || pli->li_tv.vval.v_string == NULL) { emsg(_(e_string_required)); continue; } if (do_autocmd_event(event, pli->li_tv.vval.v_string, once, nested, cmd, delete | replace, group, 0) == FAIL) { retval = VVAL_FALSE; break; } } if (retval == VVAL_FALSE) break; } if (event_name != NULL) p = end; } } // if only the autocmd group name is specified for delete and the // autocmd event, pattern and cmd are not specified, then delete the // autocmd group. if (delete && group_name != NULL && (event_name == NULL || event_name[0] == NUL) && (pat == NULL || pat[0] == NUL) && (cmd == NULL || cmd[0] == NUL)) au_del_group(group_name); } VIM_CLEAR(group_name); VIM_CLEAR(cmd); current_augroup = save_augroup; rettv->vval.v_number = retval; } /* * autocmd_add() function */ void f_autocmd_add(typval_T *argvars, typval_T *rettv) { autocmd_add_or_delete(argvars, rettv, FALSE); } /* * autocmd_delete() function */ void f_autocmd_delete(typval_T *argvars, typval_T *rettv) { autocmd_add_or_delete(argvars, rettv, TRUE); } /* * autocmd_get() function * Returns a List of autocmds. */ void f_autocmd_get(typval_T *argvars, typval_T *rettv) { event_T event_arg = NUM_EVENTS; event_T event; AutoPat *ap; AutoCmd *ac; list_T *event_list; dict_T *event_dict; char_u *event_name = NULL; char_u *pat = NULL; char_u *name = NULL; int group = AUGROUP_ALL; if (rettv_list_alloc(rettv) == FAIL) return; if (check_for_opt_dict_arg(argvars, 0) == FAIL) return; if (argvars[0].v_type == VAR_DICT) { // return only the autocmds in the specified group if (dict_has_key(argvars[0].vval.v_dict, "group")) { name = dict_get_string(argvars[0].vval.v_dict, "group", TRUE); if (name == NULL) return; if (*name == NUL) group = AUGROUP_DEFAULT; else { group = au_find_group(name); if (group == AUGROUP_ERROR) { semsg(_(e_no_such_group_str), name); vim_free(name); return; } } vim_free(name); } // return only the autocmds for the specified event if (dict_has_key(argvars[0].vval.v_dict, "event")) { int i; name = dict_get_string(argvars[0].vval.v_dict, "event", TRUE); if (name == NULL) return; if (name[0] == '*' && name[1] == NUL) event_arg = NUM_EVENTS; else { for (i = 0; event_names[i].name != NULL; i++) if (STRICMP(event_names[i].name, name) == 0) break; if (event_names[i].name == NULL) { semsg(_(e_no_such_event_str), name); vim_free(name); return; } event_arg = event_names[i].event; } vim_free(name); } // return only the autocmds for the specified pattern if (dict_has_key(argvars[0].vval.v_dict, "pattern")) { pat = dict_get_string(argvars[0].vval.v_dict, "pattern", TRUE); if (pat == NULL) return; } } event_list = rettv->vval.v_list; // iterate through all the autocmd events for (event = (event_T)0; (int)event < NUM_EVENTS; event = (event_T)((int)event + 1)) { if (event_arg != NUM_EVENTS && event != event_arg) continue; event_name = event_nr2name(event); // iterate through all the patterns for this autocmd event FOR_ALL_AUTOCMD_PATTERNS(event, ap) { char_u *group_name; if (group != AUGROUP_ALL && group != ap->group) continue; if (pat != NULL && STRCMP(pat, ap->pat) != 0) continue; group_name = get_augroup_name(NULL, ap->group); // iterate through all the commands for this pattern and add one // item for each cmd. for (ac = ap->cmds; ac != NULL; ac = ac->next) { event_dict = dict_alloc(); if (event_dict == NULL || list_append_dict(event_list, event_dict) == FAIL) return; if (dict_add_string(event_dict, "event", event_name) == FAIL || dict_add_string(event_dict, "group", group_name == NULL ? (char_u *)"" : group_name) == FAIL || (ap->buflocal_nr != 0 && (dict_add_number(event_dict, "bufnr", ap->buflocal_nr) == FAIL)) || dict_add_string(event_dict, "pattern", ap->pat) == FAIL || dict_add_string(event_dict, "cmd", ac->cmd) == FAIL || dict_add_bool(event_dict, "once", ac->once) == FAIL || dict_add_bool(event_dict, "nested", ac->nested) == FAIL) return; } } } vim_free(pat); } #endif