diff options
author | Bram Moolenaar <Bram@vim.org> | 2019-01-26 16:21:07 +0100 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2019-01-26 16:21:07 +0100 |
commit | 3e460fd8b72db905fbf9f01b00371384ffc415b8 (patch) | |
tree | b02002682babdf9d7ef513fb3b33b06fcf585c70 | |
parent | 1ecc5e4a995ade68ae216bb56f6ac9bd5c0b7e4b (diff) | |
download | vim-git-3e460fd8b72db905fbf9f01b00371384ffc415b8.tar.gz |
patch 8.1.0825: code for autocommands is mixed with file I/O codev8.1.0825
Problem: Code for autocommands is mixed with file I/O code.
Solution: Move autocommand code to a separate file. (Yegappan Lakshmanan,
closes #3863)
-rw-r--r-- | Filelist | 2 | ||||
-rw-r--r-- | src/Make_bc5.mak | 1 | ||||
-rw-r--r-- | src/Make_cyg_ming.mak | 1 | ||||
-rw-r--r-- | src/Make_dice.mak | 4 | ||||
-rw-r--r-- | src/Make_ivc.mak | 5 | ||||
-rw-r--r-- | src/Make_manx.mak | 6 | ||||
-rw-r--r-- | src/Make_morph.mak | 1 | ||||
-rw-r--r-- | src/Make_mvc.mak | 4 | ||||
-rw-r--r-- | src/Make_sas.mak | 5 | ||||
-rw-r--r-- | src/Make_vms.mms | 5 | ||||
-rw-r--r-- | src/Makefile | 10 | ||||
-rw-r--r-- | src/README.txt | 1 | ||||
-rw-r--r-- | src/autocmd.c | 2579 | ||||
-rw-r--r-- | src/fileio.c | 2588 | ||||
-rw-r--r-- | src/globals.h | 1 | ||||
-rw-r--r-- | src/proto.h | 1 | ||||
-rw-r--r-- | src/proto/autocmd.pro | 39 | ||||
-rw-r--r-- | src/proto/fileio.pro | 37 | ||||
-rw-r--r-- | src/version.c | 2 |
19 files changed, 2672 insertions, 2620 deletions
@@ -14,6 +14,7 @@ SRC_ALL = \ src/arabic.c \ src/arabic.h \ src/ascii.h \ + src/autocmd.c \ src/beval.c \ src/beval.h \ src/blob.c \ @@ -146,6 +147,7 @@ SRC_ALL = \ src/proto.h \ src/protodef.h \ src/proto/arabic.pro \ + src/proto/autocmd.pro \ src/proto/beval.pro \ src/proto/blob.pro \ src/proto/blowfish.pro \ diff --git a/src/Make_bc5.mak b/src/Make_bc5.mak index 9760d8f7b..d0d31d4eb 100644 --- a/src/Make_bc5.mak +++ b/src/Make_bc5.mak @@ -525,6 +525,7 @@ vimwinmain = \ vimobj = \ $(OBJDIR)\arabic.obj \ + $(OBJDIR)\autocmd.obj \ $(OBJDIR)\blowfish.obj \ $(OBJDIR)\buffer.obj \ $(OBJDIR)\charset.obj \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak index 7aef61ac3..8d8666ffb 100644 --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -695,6 +695,7 @@ GUIOBJ = $(OUTDIR)/gui.o $(OUTDIR)/gui_w32.o $(OUTDIR)/gui_beval.o $(OUTDIR)/os CUIOBJ = $(OUTDIR)/iscygpty.o OBJ = \ $(OUTDIR)/arabic.o \ + $(OUTDIR)/autocmd.o \ $(OUTDIR)/beval.o \ $(OUTDIR)/blob.o \ $(OUTDIR)/blowfish.o \ diff --git a/src/Make_dice.mak b/src/Make_dice.mak index 2daa8d72b..83614ca91 100644 --- a/src/Make_dice.mak +++ b/src/Make_dice.mak @@ -27,6 +27,7 @@ LD = dcc SRC = \ arabic.c \ + autocmd.c \ blowfish.c \ buffer.c \ charset.c \ @@ -84,6 +85,7 @@ SRC = \ version.c OBJ = o/arabic.o \ + o/autocmd.o \ o/blowfish.o \ o/buffer.o \ o/charset.o \ @@ -161,6 +163,8 @@ $(SYMS) : vim.h globals.h keymap.h macros.h ascii.h term.h os_amiga.h structs.h o/arabic.o: arabic.c $(SYMS) +o/autocmd.o: autocmd.c $(SYMS) + o/blowfish.o: blowfish.c $(SYMS) o/buffer.o: buffer.c $(SYMS) diff --git a/src/Make_ivc.mak b/src/Make_ivc.mak index 784cab931..0459984da 100644 --- a/src/Make_ivc.mak +++ b/src/Make_ivc.mak @@ -211,6 +211,7 @@ ALL : .\$(VIM).exe vimrun.exe install.exe uninstal.exe xxd/xxd.exe GvimExt/gvime LINK32_OBJS= \ $(EXTRAS) \ "$(INTDIR)/arabic.obj" \ + "$(INTDIR)/autocmd.obj" \ "$(INTDIR)/blowfish.obj" \ "$(INTDIR)/buffer.obj" \ "$(INTDIR)/charset.obj" \ @@ -341,6 +342,10 @@ GvimExt/gvimext.dll: GvimExt/gvimext.cpp GvimExt/gvimext.rc GvimExt/gvimext.h SOURCE=.\arabic.c # End Source File # Begin Source File +# +SOURCE=.\autocmd.c +# End Source File +# Begin Source File SOURCE=.\blowfish.c # End Source File diff --git a/src/Make_manx.mak b/src/Make_manx.mak index 04560d403..b71b923de 100644 --- a/src/Make_manx.mak +++ b/src/Make_manx.mak @@ -37,6 +37,7 @@ REN = $(SHELL) -c mv -f DEL = $(SHELL) -c rm -f SRC = arabic.c \ + autocmd.c \ blowfish.c \ buffer.c \ charset.c \ @@ -96,6 +97,7 @@ SRC = arabic.c \ INCL = vim.h feature.h keymap.h macros.h ascii.h term.h structs.h os_amiga.h OBJ = obj/arabic.o \ + obj/autocmd.o \ obj/blowfish.o \ obj/buffer.o \ obj/charset.o \ @@ -153,6 +155,7 @@ OBJ = obj/arabic.o \ $(TERMLIB) PRO = proto/arabic.pro \ + proto/autocmd.pro \ proto/blowfish.pro \ proto/buffer.pro \ proto/charset.pro \ @@ -256,6 +259,9 @@ $(OBJ): $(SYMS) obj/arabic.o: arabic.c $(CCSYM) $@ arabic.c +obj/autocmd.o: autocmd.c + $(CCSYM) $@ autocmd.c + obj/blowfish.o: blowfish.c $(CCSYM) $@ blowfish.c diff --git a/src/Make_morph.mak b/src/Make_morph.mak index 70ab5377a..ae490416f 100644 --- a/src/Make_morph.mak +++ b/src/Make_morph.mak @@ -25,6 +25,7 @@ RM = rm ${CC} ${CFLAGS} $< -o $@ SRC = arabic.c \ + autocmd.c \ blowfish.c \ buffer.c \ charset.c \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index a3c66819a..3f8c8a038 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -700,6 +700,7 @@ INCL = vim.h alloc.h arabic.h ascii.h ex_cmds.h farsi.h feature.h globals.h \ OBJ = \ $(OUTDIR)\arabic.obj \ + $(OUTDIR)\autocmd.obj \ $(OUTDIR)\beval.obj \ $(OUTDIR)\blob.obj \ $(OUTDIR)\blowfish.obj \ @@ -1345,6 +1346,8 @@ $(NEW_TESTS): $(OUTDIR)/arabic.obj: $(OUTDIR) arabic.c $(INCL) +$(OUTDIR)/autocmd.obj: $(OUTDIR) autocmd.c $(INCL) + $(OUTDIR)/beval.obj: $(OUTDIR) beval.c $(INCL) $(OUTDIR)/blob.obj: $(OUTDIR) blob.c $(INCL) @@ -1619,6 +1622,7 @@ auto: # End Custom Build proto.h: \ proto/arabic.pro \ + proto/autocmd.pro \ proto/blob.pro \ proto/blowfish.pro \ proto/buffer.pro \ diff --git a/src/Make_sas.mak b/src/Make_sas.mak index a16908e9c..c621360ea 100644 --- a/src/Make_sas.mak +++ b/src/Make_sas.mak @@ -90,6 +90,7 @@ PROPT = DEF=PROTO GPROTO GPPARM MAXIMUMERRORS=999 GENPROTOSTATICS GENPROTOPARAME SRC = \ arabic.c \ + autocmd.c \ blowfish.c \ buffer.c \ charset.c \ @@ -148,6 +149,7 @@ SRC = \ OBJ = \ arabic.o \ + autocmd.o \ blowfish.o \ buffer.o \ charset.o \ @@ -206,6 +208,7 @@ OBJ = \ PRO = \ proto/arabic.pro \ + proto/autocmd.pro \ proto/blowfish.pro \ proto/buffer.pro \ proto/charset.pro \ @@ -319,6 +322,8 @@ $(PRO): $(GST) vim.h # dependencies arabic.o: arabic.c proto/arabic.pro: arabic.c +autocmd.o: autocmd.c +proto/autocmd.pro: autocmd.c blowfish.o: blowfish.c proto/blowfish.pro: blowfish.c buffer.o: buffer.c diff --git a/src/Make_vms.mms b/src/Make_vms.mms index 18c68142d..6a3508980 100644 --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -312,7 +312,7 @@ ALL_CFLAGS_VER = /def=($(MODEL_DEF)$(DEFS)$(DEBUG_DEF)$(PERL_DEF)$(PYTHON_DEF) - ALL_LIBS = $(LIBS) $(GUI_LIB_DIR) $(GUI_LIB) \ $(PERL_LIB) $(PYTHON_LIB) $(TCL_LIB) $(RUBY_LIB) -SRC = arabic.c beval.obj blob.c blowfish.c buffer.c charset.c crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c \ +SRC = arabic.c autocmd.c beval.c blob.c blowfish.c buffer.c charset.c crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c \ evalfunc.c ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c if_cscope.c if_xcmdsrv.c farsi.c fileio.c fold.c \ getchar.c hardcopy.c hashtab.c json.c list.c main.c mark.c menu.c mbyte.c memfile.c memline.c message.c misc1.c \ misc2.c move.c normal.c ops.c option.c popupmnu.c quickfix.c regexp.c search.c sha256.c sign.c \ @@ -321,7 +321,7 @@ SRC = arabic.c beval.obj blob.c blowfish.c buffer.c charset.c crypt.c crypt_zip. $(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \ $(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC) -OBJ = arabic.obj beval.obj blob.obj blowfish.obj buffer.obj charset.obj crypt.obj crypt_zip.obj dict.obj diff.obj digraph.obj \ +OBJ = arabic.obj autocmd.obj beval.obj blob.obj blowfish.obj buffer.obj charset.obj crypt.obj crypt_zip.obj dict.obj diff.obj digraph.obj \ edit.obj eval.obj evalfunc.obj ex_cmds.obj ex_cmds2.obj ex_docmd.obj ex_eval.obj ex_getln.obj if_cscope.obj \ if_xcmdsrv.obj farsi.obj fileio.obj fold.obj getchar.obj hardcopy.obj hashtab.obj json.obj list.obj main.obj mark.obj \ menu.obj memfile.obj memline.obj message.obj misc1.obj misc2.obj \ @@ -500,6 +500,7 @@ ruby_env : .ENDIF arabic.obj : arabic.c vim.h +autocmd.obj : autocmd.c vim.h [.auto]config.h feature.h os_unix.h blowfish.obj : blowfish.c vim.h [.auto]config.h feature.h os_unix.h blob.obj : blob.c vim.h [.auto]config.h feature.h os_unix.h buffer.obj : buffer.c vim.h [.auto]config.h feature.h os_unix.h \ diff --git a/src/Makefile b/src/Makefile index 23f01c38b..2c409f0a6 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1572,6 +1572,7 @@ include testdir/Make_all.mak BASIC_SRC = \ arabic.c \ + autocmd.c \ beval.c \ blob.c \ blowfish.c \ @@ -1684,6 +1685,7 @@ LINT_SRC = $(BASIC_SRC) $(GUI_SRC) $(HANGULIN_SRC) \ OBJ_COMMON = \ objects/arabic.o \ + objects/autocmd.o \ objects/beval.o \ objects/buffer.o \ objects/blob.o \ @@ -1809,6 +1811,7 @@ ALL_OBJ = $(OBJ_COMMON) \ PRO_AUTO = \ arabic.pro \ + autocmd.pro \ blowfish.pro \ buffer.pro \ charset.pro \ @@ -2934,6 +2937,9 @@ $(ALL_OBJ): objects/.dirstamp objects/arabic.o: arabic.c $(CCC) -o $@ arabic.c +objects/autocmd.o: autocmd.c + $(CCC) -o $@ autocmd.c + objects/blob.o: blob.c $(CCC) -o $@ blob.c @@ -3376,6 +3382,10 @@ objects/arabic.o: arabic.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h farsi.h arabic.h +objects/autocmd.o: autocmd.c vim.h protodef.h auto/config.h feature.h \ + os_unix.h os_mac.h ascii.h keymap.h term.h macros.h option.h beval.h \ + structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h proto.h globals.h \ + farsi.h arabic.h objects/beval.o: beval.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/README.txt b/src/README.txt index 6e7094cec..e1dc36f69 100644 --- a/src/README.txt +++ b/src/README.txt @@ -17,6 +17,7 @@ use the CTRL-] command. Use CTRL-T or CTRL-O to jump back. To jump to a file, move the cursor on its name and use the "gf" command. Most code can be found in a file with an obvious name (incomplete list): + autocmd.c autocommands buffer.c manipulating buffers (loaded files) diff.c diff mode (vimdiff) eval.c expression evaluation diff --git a/src/autocmd.c b/src/autocmd.c new file mode 100644 index 000000000..55650b46f --- /dev/null +++ b/src/autocmd.c @@ -0,0 +1,2579 @@ +/* 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 nested; // If autocommands nest here. + char last; // last command in list +#ifdef FEAT_EVAL + sctx_T script_ctx; // script context where defined +#endif + 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}, + {"CompleteDone", EVENT_COMPLETEDONE}, + {"CursorHold", EVENT_CURSORHOLD}, + {"CursorHoldI", EVENT_CURSORHOLDI}, + {"CursorMoved", EVENT_CURSORMOVED}, + {"CursorMovedI", EVENT_CURSORMOVEDI}, + {"DiffUpdated", EVENT_DIFFUPDATED}, + {"DirChanged", EVENT_DIRCHANGED}, + {"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}, + {"InsertCharPre", EVENT_INSERTCHARPRE}, + {"MenuPopup", EVENT_MENUPOPUP}, + {"OptionSet", EVENT_OPTIONSET}, + {"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST}, + {"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE}, + {"QuitPre", EVENT_QUITPRE}, + {"RemoteReply", EVENT_REMOTEREPLY}, + {"SessionLoadPost", EVENT_SESSIONLOADPOST}, + {"ShellCmdPost", EVENT_SHELLCMDPOST}, + {"ShellFilterPost", EVENT_SHELLFILTERPOST}, + {"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}, + {"TermResponse", EVENT_TERMRESPONSE}, + {"TextChanged", EVENT_TEXTCHANGED}, + {"TextChangedI", EVENT_TEXTCHANGEDI}, + {"TextChangedP", EVENT_TEXTCHANGEDP}, + {"User", EVENT_USER}, + {"VimEnter", EVENT_VIMENTER}, + {"VimLeave", EVENT_VIMLEAVE}, + {"VimLeavePre", EVENT_VIMLEAVEPRE}, + {"WinNew", EVENT_WINNEW}, + {"WinEnter", EVENT_WINENTER}, + {"WinLeave", EVENT_WINLEAVE}, + {"VimResized", EVENT_VIMRESIZED}, + {"TextYankPost", EVENT_TEXTYANKPOST}, + {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. + */ +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; + +static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ + +/* + * 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; + +/* + * 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. + */ +int au_did_filetype INIT(= FALSE); + +/* + * 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 nested, char_u *cmd, int forceit, int group); +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 *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; + + 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((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) + return; + } + msg_col = 4; + msg_outtrans(ap->pat); + + for (ac = ap->cmds; ac != NULL; ac = ac->next) + { + if (ac->cmd != NULL) // skip removed commands + { + if (msg_col >= 14) + msg_putchar('\n'); + msg_col = 14; + if (got_int) + return; + msg_outtrans(ac->cmd); +#ifdef FEAT_EVAL + if (p_verbose > 0) + last_set_msg(ac->script_ctx); +#endif + if (got_int) + return; + if (ac->next != NULL) + { + msg_putchar('\n'); + if (got_int) + return; + } + } + } +} + +/* + * 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; +} + +/* + * 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 < (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) + { + // 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 + prev_ac = &(ac->next); + } + + // 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 *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; + + 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) == 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; + + i = au_find_group(name); + if (i == AUGROUP_ERROR) // the group doesn't exist + semsg(_("E367: No such group: \"%s\""), name); + else if (i == current_augroup) + emsg(_("E936: Cannot delete the current group")); + else + { + event_T event; + AutoPat *ap; + int in_use = FALSE; + + for (event = (event_T)0; (int)event < (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; + } + } + 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_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 + { + 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(); + } +} + +#if defined(EXITFREE) || defined(PROTO) + void +free_all_autocmds(void) +{ + int i; + char_u *s; + + for (current_augroup = -1; current_augroup < augroups.ga_len; + ++current_augroup) + do_autocmd((char_u *)"", TRUE); + + for (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); +} +#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(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(_("E215: Illegal character after *: %s"), arg); + return NULL; + } + pat = arg + 1; + } + else + { + for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p) + { + if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) + { + if (have_group) + semsg(_("E216: No such event: %s"), pat); + else + semsg(_("E216: No such group or event: %s"), pat); + return NULL; + } + } + } + return pat; +} + +/* + * Return TRUE if "event" is included in 'eventignore'. + */ + 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) + { + new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what))); + if (new_ei != 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 <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; + event_T event; + int need_free = FALSE; + int nested = 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); + 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 == '|') + { + 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; + } + + /* + * Check for "nested" flag. + */ + cmd = skipwhite(cmd); + if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 + && VIM_ISWHITE(cmd[6])) + { + 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 = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) + if (do_autocmd_event(event, pat, + nested, cmd, forceit, group) == FAIL) + break; + } + else + { + while (*arg && *arg != '|' && !VIM_ISWHITE(*arg)) + if (do_autocmd_event(event_name2nr(arg, &arg), pat, + nested, cmd, forceit, group) == FAIL) + break; + } + + if (need_free) + vim_free(cmd); + 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) + { + group_name = vim_strnsave(arg, (int)(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 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\}". + */ + 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 <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)) + { + semsg(_("E680: <buffer=%d>: invalid buffer number "), + buflocal_nr); + return FAIL; + } + + ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat)); + if (ap == NULL) + return FAIL; + ap->pat = vim_strnsave(pat, patlen); + ap->patlen = patlen; + if (ap->pat == NULL) + { + vim_free(ap); + return FAIL; + } + + 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 = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd)); + if (ac == NULL) + return FAIL; + ac->cmd = vim_strsave(cmd); +#ifdef FEAT_EVAL + ac->script_ctx = current_sctx; + ac->script_ctx.sc_lnum += sourcing_lnum; +#endif + if (ac->cmd == NULL) + { + vim_free(ac); + return FAIL; + } + ac->next = NULL; + *prev_ac = ac; + 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? + int *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 == NULL) // out of memory + return FAIL; + + 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) && !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) + msg(_("No matching autocommands")); + 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; + 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) + { + if (buf->b_ml.ml_mfp != NULL) + { + // find a window for this buffer and save some values + aucmd_prepbuf(&aco, buf); + 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(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 return TRUE when do_modelines() should be called. + */ + int +check_nomodeline(char_u **argp) +{ + if (STRNCMP(*argp, "<nomodeline>", 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 "aucmd_win". + * Set "curbuf" and "curwin" to match "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 "aucmd_win" when needed. If this fails (out of memory) fall + // back to using the current window. + if (win == NULL && aucmd_win == NULL) + { + win_alloc_aucmd_win(); + if (aucmd_win == NULL) + win = curwin; + } + 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_curbuf = curbuf; + aco->save_prevwin = prevwin; + 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; +#if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) + aucmd_win->w_s = &buf->b_s; +#endif + ++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(). + VIM_CLEAR(aucmd_win->w_localdir); + aco->globaldir = globaldir; + globaldir = NULL; + + + // Split the current window, put the aucmd_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 + + (void)win_split_ins(0, WSP_TOP, aucmd_win, 0); + (void)win_comp_pos(); // recompute window positions + p_ea = save_ea; +#ifdef FEAT_AUTOCHDIR + p_acd = save_acd; +#endif + 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). + */ + void +aucmd_restbuf( + aco_save_T *aco) // structure holding saved values +{ + int dummy; + + 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) + { + tabpage_T *tp; + win_T *wp; + + 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: + + // Remove the window and frame from the tree of frames. + (void)winframe_remove(curwin, &dummy, NULL); + win_remove(curwin, NULL); + 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); + + restore_snapshot(SNAP_AUCMD_IDX, FALSE); + (void)win_comp_pos(); // recompute window positions + unblock_autocmds(); + + if (win_valid(aco->save_curwin)) + curwin = aco->save_curwin; + else + // Hmm, original window disappeared. Just use the first one. + curwin = firstwin; + if (win_valid(aco->save_prevwin)) + prevwin = aco->save_prevwin; +#ifdef FEAT_EVAL + vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables + hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab +#endif + curbuf = curwin->w_buffer; + + vim_free(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; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + } +#if defined(FEAT_GUI) + // Hide the scrollbars from the aucmd_win and update. + gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_LEFT], FALSE); + gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_RIGHT], FALSE); + gui_may_update_scrollbars(); +#endif + } + 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 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 = aco->save_curwin; + curbuf = curwin->w_buffer; + if (win_valid(aco->save_prevwin)) + prevwin = aco->save_prevwin; + // In case the autocommand move the cursor to a position that that + // not exist in curbuf. + check_cursor(); + } + } +} + +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 <afile> on cmdline + int force, // when TRUE, ignore autocmd_busy + buf_T *buf) // buffer for <abuf> +{ + 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 <afile> on cmdline + int force, // when TRUE, ignore autocmd_busy + buf_T *buf, // buffer for <abuf> + 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. + */ + int +has_cursorhold(void) +{ + return (first_autopat[(int)(get_real_state() == 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 +#ifdef FEAT_INS_EXPAND + && !ins_compl_active() +#endif + ) + { + state = get_real_state(); + if (state == NORMAL_BUSY || (state & INSERT) != 0) + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE when there is a CursorMoved autocommand defined. + */ + int +has_cursormoved(void) +{ + return (first_autopat[(int)EVENT_CURSORMOVED] != NULL); +} + +#if defined(FEAT_CONCEAL) || defined(PROTO) +/* + * Return TRUE when there is a CursorMovedI autocommand defined. + */ + int +has_cursormovedI(void) +{ + return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL); +} +#endif + +/* + * 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); +} + +#if defined(FEAT_INS_EXPAND) || defined(PROTO) +/* + * Return TRUE when there is a TextChangedP autocommand defined. + */ + int +has_textchangedP(void) +{ + return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL); +} +#endif + +/* + * 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); +} + +/* + * Return TRUE when there is an FuncUndefined autocommand defined. + */ + int +has_funcundefined(void) +{ + return (first_autopat[(int)EVENT_FUNCUNDEFINED] != 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 + +/* + * 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 <afile> 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 <abuf> + 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_sourcing_name; + linenr_T save_sourcing_lnum; + 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 patcmd; + AutoPat *ap; +#ifdef FEAT_EVAL + sctx_T save_current_sctx; + 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; + + /* + * 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(_("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_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 <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) + autocmd_fname = vim_strsave(autocmd_fname); + autocmd_fname_full = FALSE; // call FullName_save() later + + /* + * 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 + { +#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* or DirChanged + 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) + { + 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 <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 + +#ifdef FEAT_EVAL + save_current_sctx = current_sctx; + +# 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(); +#ifdef FEAT_INS_EXPAND + if (!ins_compl_active()) +#endif + { + 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 + 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; + +#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; + check_lnums(TRUE); // make sure cursor and topline are valid + do_cmdline(NULL, getnextac, (void *)&patcmd, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); +#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); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + 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; +#ifdef FEAT_EVAL + current_sctx = save_current_sctx; + 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)) + { +#ifdef FEAT_TITLE + if (curbuf->b_changed != save_changed) + need_maketitle = TRUE; +#endif + 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 +} + +#if defined(FEAT_EVAL) && (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM)) \ + || defined(PROTO) + int +is_autocmd_blocked(void) +{ + return autocmd_blocked != 0; +} +#endif + +/* + * 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_u *name; + char *s; + + VIM_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) + { + name = event_nr2name(apc->event); + s = _("%s Autocommands for \"%s\""); + sourcing_name = alloc((unsigned)(STRLEN(s) + + STRLEN(name) + ap->patlen + 1)); + if (sourcing_name != NULL) + { + sprintf((char *)sourcing_name, s, + (char *)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". + * Returns allocated string, or NULL for end of autocommands. + */ + char_u * +getnextac(int c UNUSED, void *cookie, int indent UNUSED) +{ + 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); + autocmd_nested = ac->nested; +#ifdef FEAT_EVAL + current_sctx = ac->script_ctx; +#endif + 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 (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; + } + + vim_free(fname); +#ifdef BACKSLASH_IN_FILENAME + vim_free(sfname); +#endif + + return retval; +} + +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) +/* + * 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 >= 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; +} + +#endif // FEAT_CMDL_COMPL + +#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 <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") + */ + 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 "<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, pattern) == 0 + : ap->buflocal_nr == buflocal_buf->b_fnum))) + { + retval = TRUE; + break; + } + +theend: + vim_free(arg_save); + return retval; +} +#endif diff --git a/src/fileio.c b/src/fileio.c index 4cb13f27a..bf724f642 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -42,12 +42,6 @@ static int msg_add_fileformat(int eol_type); static void msg_add_eol(void); static int check_mtime(buf_T *buf, stat_T *s); static int time_differs(long t1, long t2); -static int apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap); -static int au_find_group(char_u *name); - -#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 */ @@ -120,16 +114,6 @@ static int get_mac_fio_flags(char_u *ptr); #endif 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 int au_did_filetype INIT(= FALSE); - void filemess( buf_T *buf, @@ -6866,6 +6850,11 @@ buf_check_timestamp( reason = "deleted"; else if (bufIsChanged(buf)) reason = "conflict"; + /* + * Check if the file contents really changed to avoid giving a + * warning when only the timestamp was set (e.g., checked out of + * CVS). Always warn when the buffer was changed. + */ else if (orig_size != buf->b_orig_size || buf_contents_changed(buf)) reason = "changed"; else if (orig_mode != buf->b_orig_mode) @@ -6912,12 +6901,6 @@ buf_check_timestamp( #if defined(FEAT_CON_DIALOG) || defined(FEAT_GUI_DIALOG) can_reload = TRUE; #endif - /* - * Check if the file contents really changed to avoid - * giving a warning when only the timestamp was set (e.g., - * checked out of CVS). Always warn when the buffer was - * changed. - */ if (reason[2] == 'n') { mesg = _("W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well"); @@ -7552,2565 +7535,6 @@ forward_slash(char_u *fname) } #endif - -/* - * Code for automatic commands. - */ - -/* - * 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 nested; /* If autocommands nest here */ - char last; /* last command in list */ -#ifdef FEAT_EVAL - sctx_T script_ctx; /* script context where defined */ -#endif - 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}, - {"CompleteDone", EVENT_COMPLETEDONE}, - {"CursorHold", EVENT_CURSORHOLD}, - {"CursorHoldI", EVENT_CURSORHOLDI}, - {"CursorMoved", EVENT_CURSORMOVED}, - {"CursorMovedI", EVENT_CURSORMOVEDI}, - {"DiffUpdated", EVENT_DIFFUPDATED}, - {"DirChanged", EVENT_DIRCHANGED}, - {"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}, - {"InsertCharPre", EVENT_INSERTCHARPRE}, - {"MenuPopup", EVENT_MENUPOPUP}, - {"OptionSet", EVENT_OPTIONSET}, - {"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST}, - {"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE}, - {"QuitPre", EVENT_QUITPRE}, - {"RemoteReply", EVENT_REMOTEREPLY}, - {"SessionLoadPost", EVENT_SESSIONLOADPOST}, - {"ShellCmdPost", EVENT_SHELLCMDPOST}, - {"ShellFilterPost", EVENT_SHELLFILTERPOST}, - {"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}, - {"TermResponse", EVENT_TERMRESPONSE}, - {"TextChanged", EVENT_TEXTCHANGED}, - {"TextChangedI", EVENT_TEXTCHANGEDI}, - {"TextChangedP", EVENT_TEXTCHANGEDP}, - {"User", EVENT_USER}, - {"VimEnter", EVENT_VIMENTER}, - {"VimLeave", EVENT_VIMLEAVE}, - {"VimLeavePre", EVENT_VIMLEAVEPRE}, - {"WinNew", EVENT_WINNEW}, - {"WinEnter", EVENT_WINENTER}, - {"WinLeave", EVENT_WINLEAVE}, - {"VimResized", EVENT_VIMRESIZED}, - {"TextYankPost", EVENT_TEXTYANKPOST}, - {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 -}; - -/* - * 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; - -static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ - -/* - * 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 nested, char_u *cmd, int forceit, int group); -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 *apc, int stop_at_last); -static int match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, char_u *sfname, char_u *tail, int allow_dirs); - - -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; - - 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((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) - return; - } - msg_col = 4; - msg_outtrans(ap->pat); - - for (ac = ap->cmds; ac != NULL; ac = ac->next) - { - if (ac->cmd != NULL) /* skip removed commands */ - { - if (msg_col >= 14) - msg_putchar('\n'); - msg_col = 14; - if (got_int) - return; - msg_outtrans(ac->cmd); -#ifdef FEAT_EVAL - if (p_verbose > 0) - last_set_msg(ac->script_ctx); -#endif - if (got_int) - return; - if (ac->next != NULL) - { - msg_putchar('\n'); - if (got_int) - return; - } - } - } -} - -/* - * 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; -} - -/* - * 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 < (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) - { - /* 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 - prev_ac = &(ac->next); - } - - /* 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 *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; - - 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) == 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; - - i = au_find_group(name); - if (i == AUGROUP_ERROR) /* the group doesn't exist */ - semsg(_("E367: No such group: \"%s\""), name); - else if (i == current_augroup) - emsg(_("E936: Cannot delete the current group")); - else - { - event_T event; - AutoPat *ap; - int in_use = FALSE; - - for (event = (event_T)0; (int)event < (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; - } - } - 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_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 */ - { - 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(); - } -} - -#if defined(EXITFREE) || defined(PROTO) - void -free_all_autocmds(void) -{ - int i; - char_u *s; - - for (current_augroup = -1; current_augroup < augroups.ga_len; - ++current_augroup) - do_autocmd((char_u *)"", TRUE); - - for (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); -} -#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(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(_("E215: Illegal character after *: %s"), arg); - return NULL; - } - pat = arg + 1; - } - else - { - for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p) - { - if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) - { - if (have_group) - semsg(_("E216: No such event: %s"), pat); - else - semsg(_("E216: No such group or event: %s"), pat); - return NULL; - } - } - } - return pat; -} - -/* - * Return TRUE if "event" is included in 'eventignore'. - */ - 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) - { - new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what))); - if (new_ei != 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 <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; - event_T event; - int need_free = FALSE; - int nested = 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); - 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 == '|') - { - 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; - } - - /* - * Check for "nested" flag. - */ - cmd = skipwhite(cmd); - if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6])) - { - 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 = (event_T)0; (int)event < (int)NUM_EVENTS; - event = (event_T)((int)event + 1)) - if (do_autocmd_event(event, pat, - nested, cmd, forceit, group) == FAIL) - break; - } - else - { - while (*arg && *arg != '|' && !VIM_ISWHITE(*arg)) - if (do_autocmd_event(event_name2nr(arg, &arg), pat, - nested, cmd, forceit, group) == FAIL) - break; - } - - if (need_free) - vim_free(cmd); - 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) - { - group_name = vim_strnsave(arg, (int)(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 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\}". - */ - 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 <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)) - { - semsg(_("E680: <buffer=%d>: invalid buffer number "), - buflocal_nr); - return FAIL; - } - - ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat)); - if (ap == NULL) - return FAIL; - ap->pat = vim_strnsave(pat, patlen); - ap->patlen = patlen; - if (ap->pat == NULL) - { - vim_free(ap); - return FAIL; - } - - 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 = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd)); - if (ac == NULL) - return FAIL; - ac->cmd = vim_strsave(cmd); -#ifdef FEAT_EVAL - ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; -#endif - if (ac->cmd == NULL) - { - vim_free(ac); - return FAIL; - } - ac->next = NULL; - *prev_ac = ac; - 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? */ - int *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 == NULL) /* out of memory */ - return FAIL; - - 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) && !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) - msg(_("No matching autocommands")); - 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; - 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) - { - if (buf->b_ml.ml_mfp != NULL) - { - /* find a window for this buffer and save some values */ - aucmd_prepbuf(&aco, buf); - 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(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 return TRUE when do_modelines() should be called. - */ - int -check_nomodeline(char_u **argp) -{ - if (STRNCMP(*argp, "<nomodeline>", 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 "aucmd_win". - * Set "curbuf" and "curwin" to match "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 "aucmd_win" when needed. If this fails (out of memory) fall - * back to using the current window. */ - if (win == NULL && aucmd_win == NULL) - { - win_alloc_aucmd_win(); - if (aucmd_win == NULL) - win = curwin; - } - 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_curbuf = curbuf; - aco->save_prevwin = prevwin; - 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; -#if defined(FEAT_SYN_HL) || defined(FEAT_SPELL) - aucmd_win->w_s = &buf->b_s; -#endif - ++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(). */ - VIM_CLEAR(aucmd_win->w_localdir); - aco->globaldir = globaldir; - globaldir = NULL; - - - /* Split the current window, put the aucmd_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 - - (void)win_split_ins(0, WSP_TOP, aucmd_win, 0); - (void)win_comp_pos(); /* recompute window positions */ - p_ea = save_ea; -#ifdef FEAT_AUTOCHDIR - p_acd = save_acd; -#endif - 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). - */ - void -aucmd_restbuf( - aco_save_T *aco) /* structure holding saved values */ -{ - int dummy; - - 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) - { - tabpage_T *tp; - win_T *wp; - - 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: - - /* Remove the window and frame from the tree of frames. */ - (void)winframe_remove(curwin, &dummy, NULL); - win_remove(curwin, NULL); - 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); - - restore_snapshot(SNAP_AUCMD_IDX, FALSE); - (void)win_comp_pos(); /* recompute window positions */ - unblock_autocmds(); - - if (win_valid(aco->save_curwin)) - curwin = aco->save_curwin; - else - /* Hmm, original window disappeared. Just use the first one. */ - curwin = firstwin; - if (win_valid(aco->save_prevwin)) - prevwin = aco->save_prevwin; -#ifdef FEAT_EVAL - vars_clear(&aucmd_win->w_vars->dv_hashtab); /* free all w: variables */ - hash_init(&aucmd_win->w_vars->dv_hashtab); /* re-use the hashtab */ -#endif - curbuf = curwin->w_buffer; - - vim_free(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; -#ifdef FEAT_DIFF - curwin->w_topfill = 0; -#endif - } -#if defined(FEAT_GUI) - /* Hide the scrollbars from the aucmd_win and update. */ - gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_LEFT], FALSE); - gui_mch_enable_scrollbar(&aucmd_win->w_scrollbars[SBAR_RIGHT], FALSE); - gui_may_update_scrollbars(); -#endif - } - 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 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 = aco->save_curwin; - curbuf = curwin->w_buffer; - if (win_valid(aco->save_prevwin)) - prevwin = aco->save_prevwin; - /* In case the autocommand move the cursor to a position that that - * not exist in curbuf. */ - check_cursor(); - } - } -} - -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 <afile> on cmdline */ - int force, /* when TRUE, ignore autocmd_busy */ - buf_T *buf) /* buffer for <abuf> */ -{ - 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. - */ - static 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 <afile> on cmdline */ - int force, /* when TRUE, ignore autocmd_busy */ - buf_T *buf, /* buffer for <abuf> */ - 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. - */ - int -has_cursorhold(void) -{ - return (first_autopat[(int)(get_real_state() == 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 -#ifdef FEAT_INS_EXPAND - && !ins_compl_active() -#endif - ) - { - state = get_real_state(); - if (state == NORMAL_BUSY || (state & INSERT) != 0) - return TRUE; - } - return FALSE; -} - -/* - * Return TRUE when there is a CursorMoved autocommand defined. - */ - int -has_cursormoved(void) -{ - return (first_autopat[(int)EVENT_CURSORMOVED] != NULL); -} - -#if defined(FEAT_CONCEAL) || defined(PROTO) -/* - * Return TRUE when there is a CursorMovedI autocommand defined. - */ - int -has_cursormovedI(void) -{ - return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL); -} -#endif - -/* - * 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); -} - -#if defined(FEAT_INS_EXPAND) || defined(PROTO) -/* - * Return TRUE when there is a TextChangedP autocommand defined. - */ - int -has_textchangedP(void) -{ - return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL); -} -#endif - -/* - * 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); -} - -/* - * Return TRUE when there is an FuncUndefined autocommand defined. - */ - int -has_funcundefined(void) -{ - return (first_autopat[(int)EVENT_FUNCUNDEFINED] != 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 - -/* - * 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 <afile> 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 <abuf> */ - 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_sourcing_name; - linenr_T save_sourcing_lnum; - 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 patcmd; - AutoPat *ap; -#ifdef FEAT_EVAL - sctx_T save_current_sctx; - 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; - - /* - * 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(_("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_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 <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) - autocmd_fname = vim_strsave(autocmd_fname); - autocmd_fname_full = FALSE; /* call FullName_save() later */ - - /* - * 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 - { -#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* or DirChanged */ - 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) - { - 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 <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 */ - -#ifdef FEAT_EVAL - save_current_sctx = current_sctx; - -# 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(); -#ifdef FEAT_INS_EXPAND - if (!ins_compl_active()) -#endif - { - 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 */ - 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; - -#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; - check_lnums(TRUE); /* make sure cursor and topline are valid */ - do_cmdline(NULL, getnextac, (void *)&patcmd, - DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); -#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); - sourcing_name = save_sourcing_name; - sourcing_lnum = save_sourcing_lnum; - 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; -#ifdef FEAT_EVAL - current_sctx = save_current_sctx; - 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)) - { -#ifdef FEAT_TITLE - if (curbuf->b_changed != save_changed) - need_maketitle = TRUE; -#endif - 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 -} - -#if defined(FEAT_EVAL) && (defined(FEAT_XIM) || defined(IME_WITHOUT_XIM)) \ - || defined(PROTO) - int -is_autocmd_blocked(void) -{ - return autocmd_blocked != 0; -} -#endif - -/* - * 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_u *name; - char *s; - - VIM_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) - { - name = event_nr2name(apc->event); - s = _("%s Autocommands for \"%s\""); - sourcing_name = alloc((unsigned)(STRLEN(s) - + STRLEN(name) + ap->patlen + 1)); - if (sourcing_name != NULL) - { - sprintf((char *)sourcing_name, s, - (char *)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". - * Returns allocated string, or NULL for end of autocommands. - */ - char_u * -getnextac(int c UNUSED, void *cookie, int indent UNUSED) -{ - 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); - autocmd_nested = ac->nested; -#ifdef FEAT_EVAL - current_sctx = ac->script_ctx; -#endif - 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 (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; - } - - vim_free(fname); -#ifdef BACKSLASH_IN_FILENAME - vim_free(sfname); -#endif - - return retval; -} - -#if defined(FEAT_CMDL_COMPL) || defined(PROTO) -/* - * 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 >= 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; -} - -#endif /* FEAT_CMDL_COMPL */ - -#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 <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") - */ - 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 "<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, pattern) == 0 - : ap->buflocal_nr == buflocal_buf->b_fnum))) - { - retval = TRUE; - break; - } - -theend: - vim_free(arg_save); - return retval; -} -#endif - - /* * Try matching a filename with a "pattern" ("prog" is NULL), or use the * precompiled regprog "prog" ("pattern" is NULL). That avoids calling @@ -10118,7 +7542,7 @@ theend: * Used for autocommands and 'wildignore'. * Returns TRUE if there is a match, FALSE otherwise. */ - static int + int match_file_pat( char_u *pattern, /* pattern to match with */ regprog_T **prog, /* pre-compiled regprog or NULL */ diff --git a/src/globals.h b/src/globals.h index 355b4bb34..c6fd710f1 100644 --- a/src/globals.h +++ b/src/globals.h @@ -397,6 +397,7 @@ EXTERN int autocmd_no_enter INIT(= FALSE); /* *Enter autocmds disabled */ EXTERN int autocmd_no_leave INIT(= FALSE); /* *Leave autocmds disabled */ EXTERN int modified_was_set; /* did ":set modified" */ EXTERN int did_filetype INIT(= FALSE); /* FileType event found */ +EXTERN int au_did_filetype INIT(= FALSE); EXTERN int keep_filetype INIT(= FALSE); /* value for did_filetype when starting to execute autocommands */ diff --git a/src/proto.h b/src/proto.h index cb4add002..e4153ebef 100644 --- a/src/proto.h +++ b/src/proto.h @@ -62,6 +62,7 @@ extern int _stricoll(char *a, char *b); # include "crypt.pro" # include "crypt_zip.pro" # endif +# include "autocmd.pro" # include "buffer.pro" # include "charset.pro" # ifdef FEAT_CSCOPE diff --git a/src/proto/autocmd.pro b/src/proto/autocmd.pro new file mode 100644 index 000000000..8c6d18f75 --- /dev/null +++ b/src/proto/autocmd.pro @@ -0,0 +1,39 @@ +/* autocmd.c */ +void aubuflocal_remove(buf_T *buf); +int au_has_group(char_u *name); +void do_augroup(char_u *arg, int del_group); +void free_all_autocmds(void); +int check_ei(void); +char_u *au_event_disable(char *what); +void au_event_restore(char_u *old_ei); +void do_autocmd(char_u *arg_in, int forceit); +int do_doautocmd(char_u *arg, int do_msg, int *did_something); +void ex_doautoall(exarg_T *eap); +int check_nomodeline(char_u **argp); +void aucmd_prepbuf(aco_save_T *aco, buf_T *buf); +void aucmd_restbuf(aco_save_T *aco); +int apply_autocmds(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf); +int apply_autocmds_exarg(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, exarg_T *eap); +int apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, int *retval); +int has_cursorhold(void); +int trigger_cursorhold(void); +int has_cursormoved(void); +int has_cursormovedI(void); +int has_textchanged(void); +int has_textchangedI(void); +int has_textchangedP(void); +int has_insertcharpre(void); +int has_cmdundefined(void); +int has_funcundefined(void); +int has_textyankpost(void); +void block_autocmds(void); +void unblock_autocmds(void); +int is_autocmd_blocked(void); +char_u *getnextac(int c, void *cookie, int indent); +int has_autocmd(event_T event, char_u *sfname, buf_T *buf); +char_u *get_augroup_name(expand_T *xp, int idx); +char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd); +char_u *get_event_name(expand_T *xp, int idx); +int autocmd_supported(char_u *name); +int au_exists(char_u *arg); +/* vim: set ft=c : */ diff --git a/src/proto/fileio.pro b/src/proto/fileio.pro index 990348143..21ac55c9d 100644 --- a/src/proto/fileio.pro +++ b/src/proto/fileio.pro @@ -28,42 +28,7 @@ int delete_recursive(char_u *name); void vim_deltempdir(void); char_u *vim_tempname(int extra_char, int keep); void forward_slash(char_u *fname); -void aubuflocal_remove(buf_T *buf); -int au_has_group(char_u *name); -void do_augroup(char_u *arg, int del_group); -void free_all_autocmds(void); -int check_ei(void); -char_u *au_event_disable(char *what); -void au_event_restore(char_u *old_ei); -void do_autocmd(char_u *arg_in, int forceit); -int do_doautocmd(char_u *arg, int do_msg, int *did_something); -void ex_doautoall(exarg_T *eap); -int check_nomodeline(char_u **argp); -void aucmd_prepbuf(aco_save_T *aco, buf_T *buf); -void aucmd_restbuf(aco_save_T *aco); -int apply_autocmds(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf); -int apply_autocmds_retval(event_T event, char_u *fname, char_u *fname_io, int force, buf_T *buf, int *retval); -int has_cursorhold(void); -int trigger_cursorhold(void); -int has_cursormoved(void); -int has_cursormovedI(void); -int has_textchanged(void); -int has_textchangedI(void); -int has_textchangedP(void); -int has_insertcharpre(void); -int has_cmdundefined(void); -int has_funcundefined(void); -int has_textyankpost(void); -void block_autocmds(void); -void unblock_autocmds(void); -int is_autocmd_blocked(void); -char_u *getnextac(int c, void *cookie, int indent); -int has_autocmd(event_T event, char_u *sfname, buf_T *buf); -char_u *get_augroup_name(expand_T *xp, int idx); -char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd); -char_u *get_event_name(expand_T *xp, int idx); -int autocmd_supported(char_u *name); -int au_exists(char_u *arg); +int match_file_pat(char_u *pattern, regprog_T **prog, char_u *fname, char_u *sfname, char_u *tail, int allow_dirs); int match_file_list(char_u *list, char_u *sfname, char_u *ffname); char_u *file_pat_to_reg_pat(char_u *pat, char_u *pat_end, char *allow_dirs, int no_bslash); long read_eintr(int fd, void *buf, size_t bufsize); diff --git a/src/version.c b/src/version.c index ddf9b1496..af0adc276 100644 --- a/src/version.c +++ b/src/version.c @@ -788,6 +788,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 825, +/**/ 824, /**/ 823, |