3468 lines
87 KiB
C
3468 lines
87 KiB
C
/* vi:set ts=8 sts=4 sw=4 noet:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
/*
|
|
* autocmd.c: Autocommand related functions
|
|
*/
|
|
|
|
#include "vim.h"
|
|
|
|
/*
|
|
* The autocommands are stored in a list for each event.
|
|
* Autocommands for the same pattern, that are consecutive, are joined
|
|
* together, to avoid having to match the pattern too often.
|
|
* The result is an array of Autopat lists, which point to AutoCmd lists:
|
|
*
|
|
* last_autopat[0] -----------------------------+
|
|
* V
|
|
* first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
|
|
* Autopat.cmds Autopat.cmds
|
|
* | |
|
|
* V V
|
|
* AutoCmd.next AutoCmd.next
|
|
* | |
|
|
* V V
|
|
* AutoCmd.next NULL
|
|
* |
|
|
* V
|
|
* NULL
|
|
*
|
|
* last_autopat[1] --------+
|
|
* V
|
|
* first_autopat[1] --> Autopat.next --> NULL
|
|
* Autopat.cmds
|
|
* |
|
|
* V
|
|
* AutoCmd.next
|
|
* |
|
|
* V
|
|
* NULL
|
|
* etc.
|
|
*
|
|
* The order of AutoCmds is important, this is the order in which they were
|
|
* defined and will have to be executed.
|
|
*/
|
|
typedef struct AutoCmd
|
|
{
|
|
char_u *cmd; // The command to be executed (NULL
|
|
// when command has been removed).
|
|
char once; // "One shot": removed after execution
|
|
char nested; // If autocommands nest here.
|
|
char last; // last command in list
|
|
sctx_T script_ctx; // script context where it is defined
|
|
struct AutoCmd *next; // next AutoCmd in list
|
|
} AutoCmd;
|
|
|
|
typedef struct AutoPat
|
|
{
|
|
struct AutoPat *next; // Next AutoPat in AutoPat list; MUST
|
|
// be the first entry.
|
|
char_u *pat; // pattern as typed (NULL when pattern
|
|
// has been removed)
|
|
regprog_T *reg_prog; // compiled regprog for pattern
|
|
AutoCmd *cmds; // list of commands to do
|
|
int group; // group ID
|
|
int patlen; // strlen() of pat
|
|
int buflocal_nr; // !=0 for buffer-local AutoPat
|
|
char allow_dirs; // Pattern may match whole path
|
|
char last; // last pattern for apply_autocmds()
|
|
} AutoPat;
|
|
|
|
//
|
|
// special cases:
|
|
// BufNewFile and BufRead are searched for ALOT (especially at startup)
|
|
// so we pre-determine their index into the event_tab[] table for fast access.
|
|
// Keep these values in sync with event_tab[]!
|
|
#define BUFNEWFILE_INDEX 9
|
|
#define BUFREAD_INDEX 10
|
|
|
|
// Must be sorted by the 'value' field because it is used by bsearch()!
|
|
// Events with positive keys aren't allowed in 'eventignorewin'.
|
|
static keyvalue_T event_tab[NUM_EVENTS] = {
|
|
KEYVALUE_ENTRY(-EVENT_BUFADD, "BufAdd"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFADD, "BufCreate"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFDELETE, "BufDelete"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFENTER, "BufEnter"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFFILEPOST, "BufFilePost"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFFILEPRE, "BufFilePre"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFHIDDEN, "BufHidden"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFLEAVE, "BufLeave"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFNEW, "BufNew"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFNEWFILE, "BufNewFile"), // BUFNEWFILE_INDEX
|
|
KEYVALUE_ENTRY(-EVENT_BUFREADPOST, "BufRead"), // BUFREAD_INDEX
|
|
KEYVALUE_ENTRY(-EVENT_BUFREADCMD, "BufReadCmd"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFREADPOST, "BufReadPost"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFREADPRE, "BufReadPre"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFUNLOAD, "BufUnload"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWINENTER, "BufWinEnter"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWINLEAVE, "BufWinLeave"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWIPEOUT, "BufWipeout"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWRITEPRE, "BufWrite"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWRITECMD, "BufWriteCmd"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWRITEPOST, "BufWritePost"),
|
|
KEYVALUE_ENTRY(-EVENT_BUFWRITEPRE, "BufWritePre"),
|
|
KEYVALUE_ENTRY(EVENT_CMDLINECHANGED, "CmdlineChanged"),
|
|
KEYVALUE_ENTRY(EVENT_CMDLINEENTER, "CmdlineEnter"),
|
|
KEYVALUE_ENTRY(EVENT_CMDLINELEAVE, "CmdlineLeave"),
|
|
KEYVALUE_ENTRY(EVENT_CMDUNDEFINED, "CmdUndefined"),
|
|
KEYVALUE_ENTRY(EVENT_CMDWINENTER, "CmdwinEnter"),
|
|
KEYVALUE_ENTRY(EVENT_CMDWINLEAVE, "CmdwinLeave"),
|
|
KEYVALUE_ENTRY(EVENT_COLORSCHEME, "ColorScheme"),
|
|
KEYVALUE_ENTRY(EVENT_COLORSCHEMEPRE, "ColorSchemePre"),
|
|
KEYVALUE_ENTRY(EVENT_COMPLETECHANGED, "CompleteChanged"),
|
|
KEYVALUE_ENTRY(EVENT_COMPLETEDONE, "CompleteDone"),
|
|
KEYVALUE_ENTRY(EVENT_COMPLETEDONEPRE, "CompleteDonePre"),
|
|
KEYVALUE_ENTRY(-EVENT_CURSORHOLD, "CursorHold"),
|
|
KEYVALUE_ENTRY(-EVENT_CURSORHOLDI, "CursorHoldI"),
|
|
KEYVALUE_ENTRY(-EVENT_CURSORMOVED, "CursorMoved"),
|
|
KEYVALUE_ENTRY(-EVENT_CURSORMOVEDC, "CursorMovedC"),
|
|
KEYVALUE_ENTRY(-EVENT_CURSORMOVEDI, "CursorMovedI"),
|
|
KEYVALUE_ENTRY(EVENT_DIFFUPDATED, "DiffUpdated"),
|
|
KEYVALUE_ENTRY(EVENT_DIRCHANGED, "DirChanged"),
|
|
KEYVALUE_ENTRY(EVENT_DIRCHANGEDPRE, "DirChangedPre"),
|
|
KEYVALUE_ENTRY(EVENT_ENCODINGCHANGED, "EncodingChanged"),
|
|
KEYVALUE_ENTRY(EVENT_EXITPRE, "ExitPre"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEAPPENDCMD, "FileAppendCmd"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEAPPENDPOST, "FileAppendPost"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEAPPENDPRE, "FileAppendPre"),
|
|
KEYVALUE_ENTRY(-EVENT_FILECHANGEDRO, "FileChangedRO"),
|
|
KEYVALUE_ENTRY(-EVENT_FILECHANGEDSHELL, "FileChangedShell"),
|
|
KEYVALUE_ENTRY(-EVENT_FILECHANGEDSHELLPOST, "FileChangedShellPost"),
|
|
KEYVALUE_ENTRY(EVENT_ENCODINGCHANGED, "FileEncoding"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEREADCMD, "FileReadCmd"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEREADPOST, "FileReadPost"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEREADPRE, "FileReadPre"),
|
|
KEYVALUE_ENTRY(-EVENT_FILETYPE, "FileType"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEWRITECMD, "FileWriteCmd"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEWRITEPOST, "FileWritePost"),
|
|
KEYVALUE_ENTRY(-EVENT_FILEWRITEPRE, "FileWritePre"),
|
|
KEYVALUE_ENTRY(-EVENT_FILTERREADPOST, "FilterReadPost"),
|
|
KEYVALUE_ENTRY(-EVENT_FILTERREADPRE, "FilterReadPre"),
|
|
KEYVALUE_ENTRY(-EVENT_FILTERWRITEPOST, "FilterWritePost"),
|
|
KEYVALUE_ENTRY(-EVENT_FILTERWRITEPRE, "FilterWritePre"),
|
|
KEYVALUE_ENTRY(EVENT_FOCUSGAINED, "FocusGained"),
|
|
KEYVALUE_ENTRY(EVENT_FOCUSLOST, "FocusLost"),
|
|
KEYVALUE_ENTRY(EVENT_FUNCUNDEFINED, "FuncUndefined"),
|
|
KEYVALUE_ENTRY(EVENT_GUIENTER, "GUIEnter"),
|
|
KEYVALUE_ENTRY(EVENT_GUIFAILED, "GUIFailed"),
|
|
KEYVALUE_ENTRY(-EVENT_INSERTCHANGE, "InsertChange"),
|
|
KEYVALUE_ENTRY(-EVENT_INSERTCHARPRE, "InsertCharPre"),
|
|
KEYVALUE_ENTRY(-EVENT_INSERTENTER, "InsertEnter"),
|
|
KEYVALUE_ENTRY(-EVENT_INSERTLEAVE, "InsertLeave"),
|
|
KEYVALUE_ENTRY(-EVENT_INSERTLEAVEPRE, "InsertLeavePre"),
|
|
KEYVALUE_ENTRY(EVENT_KEYINPUTPRE, "KeyInputPre"),
|
|
KEYVALUE_ENTRY(EVENT_MENUPOPUP, "MenuPopup"),
|
|
KEYVALUE_ENTRY(EVENT_MODECHANGED, "ModeChanged"),
|
|
KEYVALUE_ENTRY(EVENT_OPTIONSET, "OptionSet"),
|
|
KEYVALUE_ENTRY(EVENT_QUICKFIXCMDPOST, "QuickFixCmdPost"),
|
|
KEYVALUE_ENTRY(EVENT_QUICKFIXCMDPRE, "QuickFixCmdPre"),
|
|
KEYVALUE_ENTRY(EVENT_QUITPRE, "QuitPre"),
|
|
KEYVALUE_ENTRY(EVENT_REMOTEREPLY, "RemoteReply"),
|
|
KEYVALUE_ENTRY(EVENT_SAFESTATE, "SafeState"),
|
|
KEYVALUE_ENTRY(EVENT_SAFESTATEAGAIN, "SafeStateAgain"),
|
|
KEYVALUE_ENTRY(EVENT_SESSIONLOADPOST, "SessionLoadPost"),
|
|
KEYVALUE_ENTRY(EVENT_SESSIONWRITEPOST, "SessionWritePost"),
|
|
KEYVALUE_ENTRY(EVENT_SHELLCMDPOST, "ShellCmdPost"),
|
|
KEYVALUE_ENTRY(-EVENT_SHELLFILTERPOST, "ShellFilterPost"),
|
|
KEYVALUE_ENTRY(EVENT_SIGUSR1, "SigUSR1"),
|
|
KEYVALUE_ENTRY(EVENT_SOURCECMD, "SourceCmd"),
|
|
KEYVALUE_ENTRY(EVENT_SOURCEPOST, "SourcePost"),
|
|
KEYVALUE_ENTRY(EVENT_SOURCEPRE, "SourcePre"),
|
|
KEYVALUE_ENTRY(EVENT_SPELLFILEMISSING, "SpellFileMissing"),
|
|
KEYVALUE_ENTRY(EVENT_STDINREADPOST, "StdinReadPost"),
|
|
KEYVALUE_ENTRY(EVENT_STDINREADPRE, "StdinReadPre"),
|
|
KEYVALUE_ENTRY(EVENT_SWAPEXISTS, "SwapExists"),
|
|
KEYVALUE_ENTRY(EVENT_SYNTAX, "Syntax"),
|
|
KEYVALUE_ENTRY(EVENT_TABCLOSED, "TabClosed"),
|
|
KEYVALUE_ENTRY(EVENT_TABCLOSEDPRE, "TabClosedPre"),
|
|
KEYVALUE_ENTRY(EVENT_TABENTER, "TabEnter"),
|
|
KEYVALUE_ENTRY(EVENT_TABLEAVE, "TabLeave"),
|
|
KEYVALUE_ENTRY(EVENT_TABNEW, "TabNew"),
|
|
KEYVALUE_ENTRY(EVENT_TERMCHANGED, "TermChanged"),
|
|
KEYVALUE_ENTRY(EVENT_TERMINALOPEN, "TerminalOpen"),
|
|
KEYVALUE_ENTRY(EVENT_TERMINALWINOPEN, "TerminalWinOpen"),
|
|
KEYVALUE_ENTRY(EVENT_TERMRESPONSE, "TermResponse"),
|
|
KEYVALUE_ENTRY(EVENT_TERMRESPONSEALL, "TermResponseAll"),
|
|
KEYVALUE_ENTRY(-EVENT_TEXTCHANGED, "TextChanged"),
|
|
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDI, "TextChangedI"),
|
|
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDP, "TextChangedP"),
|
|
KEYVALUE_ENTRY(-EVENT_TEXTCHANGEDT, "TextChangedT"),
|
|
KEYVALUE_ENTRY(-EVENT_TEXTYANKPOST, "TextYankPost"),
|
|
KEYVALUE_ENTRY(EVENT_USER, "User"),
|
|
KEYVALUE_ENTRY(EVENT_VIMENTER, "VimEnter"),
|
|
KEYVALUE_ENTRY(EVENT_VIMLEAVE, "VimLeave"),
|
|
KEYVALUE_ENTRY(EVENT_VIMLEAVEPRE, "VimLeavePre"),
|
|
KEYVALUE_ENTRY(EVENT_VIMRESIZED, "VimResized"),
|
|
KEYVALUE_ENTRY(EVENT_VIMRESUME, "VimResume"),
|
|
KEYVALUE_ENTRY(EVENT_VIMSUSPEND, "VimSuspend"),
|
|
KEYVALUE_ENTRY(-EVENT_WINCLOSED, "WinClosed"),
|
|
KEYVALUE_ENTRY(-EVENT_WINENTER, "WinEnter"),
|
|
KEYVALUE_ENTRY(-EVENT_WINLEAVE, "WinLeave"),
|
|
KEYVALUE_ENTRY(EVENT_WINNEW, "WinNew"),
|
|
KEYVALUE_ENTRY(EVENT_WINNEWPRE, "WinNewPre"),
|
|
KEYVALUE_ENTRY(-EVENT_WINRESIZED, "WinResized"),
|
|
KEYVALUE_ENTRY(-EVENT_WINSCROLLED, "WinScrolled"),
|
|
};
|
|
|
|
static AutoPat *first_autopat[NUM_EVENTS] = { NULL };
|
|
static AutoPat *last_autopat[NUM_EVENTS] = { NULL };
|
|
|
|
#define AUGROUP_DEFAULT (-1) // default autocmd group
|
|
#define AUGROUP_ERROR (-2) // erroneous autocmd group
|
|
#define AUGROUP_ALL (-3) // all autocmd groups
|
|
|
|
/*
|
|
* struct used to keep status while executing autocommands for an event.
|
|
*/
|
|
struct AutoPatCmd_S
|
|
{
|
|
AutoPat *curpat; // next AutoPat to examine
|
|
AutoCmd *nextcmd; // next AutoCmd to execute
|
|
int group; // group being used
|
|
char_u *fname; // fname to match with
|
|
char_u *sfname; // sfname to match with
|
|
char_u *tail; // tail of fname
|
|
event_T event; // current event
|
|
sctx_T script_ctx; // script context where it is defined
|
|
int arg_bufnr; // Initially equal to <abuf>, set to zero when
|
|
// buf is deleted.
|
|
AutoPatCmd_T *next; // chain of active apc-s for auto-invalidation
|
|
};
|
|
|
|
static AutoPatCmd_T *active_apc_list = NULL; // stack of active autocommands
|
|
|
|
// Macro to loop over all the patterns for an autocmd event
|
|
#define FOR_ALL_AUTOCMD_PATTERNS(event, ap) \
|
|
for ((ap) = first_autopat[(int)(event)]; (ap) != NULL; (ap) = (ap)->next)
|
|
|
|
/*
|
|
* augroups stores a list of autocmd group names.
|
|
*/
|
|
static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL};
|
|
#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i])
|
|
// use get_deleted_augroup() to get this
|
|
static char_u *deleted_augroup = NULL;
|
|
|
|
/*
|
|
* The ID of the current group. Group 0 is the default one.
|
|
*/
|
|
static int current_augroup = AUGROUP_DEFAULT;
|
|
|
|
static int au_need_clean = FALSE; // need to delete marked patterns
|
|
|
|
static event_T event_name2nr(char_u *start, char_u **end);
|
|
static char_u *event_nr2name(event_T event);
|
|
static int au_get_grouparg(char_u **argp);
|
|
static int do_autocmd_event(event_T event, char_u *pat, int once, int nested, char_u *cmd, int forceit, int group, int flags);
|
|
static int apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, int force, int group, buf_T *buf, exarg_T *eap);
|
|
static void auto_next_pat(AutoPatCmd_T *apc, int stop_at_last);
|
|
static int au_find_group(char_u *name);
|
|
|
|
static event_T last_event;
|
|
static int last_group;
|
|
static int autocmd_blocked = 0; // block all autocmds
|
|
|
|
static char_u *
|
|
get_deleted_augroup(void)
|
|
{
|
|
if (deleted_augroup == NULL)
|
|
deleted_augroup = (char_u *)_("--Deleted--");
|
|
return deleted_augroup;
|
|
}
|
|
|
|
/*
|
|
* Show the autocommands for one AutoPat.
|
|
*/
|
|
static void
|
|
show_autocmd(AutoPat *ap, event_T event)
|
|
{
|
|
AutoCmd *ac;
|
|
|
|
// Check for "got_int" (here and at various places below), which is set
|
|
// when "q" has been hit for the "--more--" prompt
|
|
if (got_int)
|
|
return;
|
|
if (ap->pat == NULL) // pattern has been removed
|
|
return;
|
|
|
|
// Make sure no info referenced by "ap" is cleared, e.g. when a timer
|
|
// clears an augroup. Jump to "theend" after this!
|
|
// "ap->pat" may be cleared anyway.
|
|
++autocmd_busy;
|
|
|
|
msg_putchar('\n');
|
|
if (got_int)
|
|
goto theend;
|
|
if (event != last_event || ap->group != last_group)
|
|
{
|
|
if (ap->group != AUGROUP_DEFAULT)
|
|
{
|
|
if (AUGROUP_NAME(ap->group) == NULL)
|
|
msg_puts_attr((char *)get_deleted_augroup(), HL_ATTR(HLF_E));
|
|
else
|
|
msg_puts_attr((char *)AUGROUP_NAME(ap->group), HL_ATTR(HLF_T));
|
|
msg_puts(" ");
|
|
}
|
|
msg_puts_attr((char *)event_nr2name(event), HL_ATTR(HLF_T));
|
|
last_event = event;
|
|
last_group = ap->group;
|
|
msg_putchar('\n');
|
|
if (got_int)
|
|
goto theend;
|
|
}
|
|
|
|
if (ap->pat == NULL)
|
|
goto theend; // timer might have cleared the pattern or group
|
|
|
|
msg_col = 4;
|
|
msg_outtrans(ap->pat);
|
|
|
|
for (ac = ap->cmds; ac != NULL; ac = ac->next)
|
|
{
|
|
if (ac->cmd == NULL) // skip removed commands
|
|
continue;
|
|
|
|
if (msg_col >= 14)
|
|
msg_putchar('\n');
|
|
msg_col = 14;
|
|
if (got_int)
|
|
goto theend;
|
|
msg_outtrans(ac->cmd);
|
|
#ifdef FEAT_EVAL
|
|
if (p_verbose > 0)
|
|
last_set_msg(ac->script_ctx);
|
|
#endif
|
|
if (got_int)
|
|
goto theend;
|
|
if (ac->next != NULL)
|
|
{
|
|
msg_putchar('\n');
|
|
if (got_int)
|
|
goto theend;
|
|
}
|
|
}
|
|
|
|
theend:
|
|
--autocmd_busy;
|
|
}
|
|
|
|
/*
|
|
* Mark an autocommand pattern for deletion.
|
|
*/
|
|
static void
|
|
au_remove_pat(AutoPat *ap)
|
|
{
|
|
VIM_CLEAR(ap->pat);
|
|
ap->buflocal_nr = -1;
|
|
au_need_clean = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Mark all commands for a pattern for deletion.
|
|
*/
|
|
static void
|
|
au_remove_cmds(AutoPat *ap)
|
|
{
|
|
AutoCmd *ac;
|
|
|
|
for (ac = ap->cmds; ac != NULL; ac = ac->next)
|
|
VIM_CLEAR(ac->cmd);
|
|
au_need_clean = TRUE;
|
|
}
|
|
|
|
// Delete one command from an autocmd pattern.
|
|
static void au_del_cmd(AutoCmd *ac)
|
|
{
|
|
VIM_CLEAR(ac->cmd);
|
|
au_need_clean = TRUE;
|
|
}
|
|
|
|
/*
|
|
* Cleanup autocommands and patterns that have been deleted.
|
|
* This is only done when not executing autocommands.
|
|
*/
|
|
static void
|
|
au_cleanup(void)
|
|
{
|
|
AutoPat *ap, **prev_ap;
|
|
AutoCmd *ac, **prev_ac;
|
|
event_T event;
|
|
|
|
if (autocmd_busy || !au_need_clean)
|
|
return;
|
|
|
|
// loop over all events
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
{
|
|
// loop over all autocommand patterns
|
|
prev_ap = &(first_autopat[(int)event]);
|
|
for (ap = *prev_ap; ap != NULL; ap = *prev_ap)
|
|
{
|
|
int has_cmd = FALSE;
|
|
|
|
// loop over all commands for this pattern
|
|
prev_ac = &(ap->cmds);
|
|
for (ac = *prev_ac; ac != NULL; ac = *prev_ac)
|
|
{
|
|
// remove the command if the pattern is to be deleted or when
|
|
// the command has been marked for deletion
|
|
if (ap->pat == NULL || ac->cmd == NULL)
|
|
{
|
|
*prev_ac = ac->next;
|
|
vim_free(ac->cmd);
|
|
vim_free(ac);
|
|
}
|
|
else
|
|
{
|
|
has_cmd = TRUE;
|
|
prev_ac = &(ac->next);
|
|
}
|
|
}
|
|
|
|
if (ap->pat != NULL && !has_cmd)
|
|
// Pattern was not marked for deletion, but all of its
|
|
// commands were. So mark the pattern for deletion.
|
|
au_remove_pat(ap);
|
|
|
|
// remove the pattern if it has been marked for deletion
|
|
if (ap->pat == NULL)
|
|
{
|
|
if (ap->next == NULL)
|
|
{
|
|
if (prev_ap == &(first_autopat[(int)event]))
|
|
last_autopat[(int)event] = NULL;
|
|
else
|
|
// this depends on the "next" field being the first in
|
|
// the struct
|
|
last_autopat[(int)event] = (AutoPat *)prev_ap;
|
|
}
|
|
*prev_ap = ap->next;
|
|
vim_regfree(ap->reg_prog);
|
|
vim_free(ap);
|
|
}
|
|
else
|
|
prev_ap = &(ap->next);
|
|
}
|
|
}
|
|
|
|
au_need_clean = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Called when buffer is freed, to remove/invalidate related buffer-local
|
|
* autocmds.
|
|
*/
|
|
void
|
|
aubuflocal_remove(buf_T *buf)
|
|
{
|
|
AutoPat *ap;
|
|
event_T event;
|
|
AutoPatCmd_T *apc;
|
|
|
|
// invalidate currently executing autocommands
|
|
for (apc = active_apc_list; apc; apc = apc->next)
|
|
if (buf->b_fnum == apc->arg_bufnr)
|
|
apc->arg_bufnr = 0;
|
|
|
|
// invalidate buflocals looping through events
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
// loop over all autocommand patterns
|
|
FOR_ALL_AUTOCMD_PATTERNS(event, ap)
|
|
if (ap->buflocal_nr == buf->b_fnum)
|
|
{
|
|
au_remove_pat(ap);
|
|
if (p_verbose >= 6)
|
|
{
|
|
verbose_enter();
|
|
smsg(_("auto-removing autocommand: %s <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)
|
|
return i;
|
|
|
|
// the group doesn't exist yet, add it. First try using a free entry.
|
|
for (i = 0; i < augroups.ga_len; ++i)
|
|
if (AUGROUP_NAME(i) == NULL)
|
|
break;
|
|
if (i == augroups.ga_len && ga_grow(&augroups, 1) == FAIL)
|
|
return AUGROUP_ERROR;
|
|
|
|
AUGROUP_NAME(i) = vim_strsave(name);
|
|
if (AUGROUP_NAME(i) == NULL)
|
|
return AUGROUP_ERROR;
|
|
if (i == augroups.ga_len)
|
|
++augroups.ga_len;
|
|
|
|
return i;
|
|
}
|
|
|
|
static void
|
|
au_del_group(char_u *name)
|
|
{
|
|
int i;
|
|
event_T event;
|
|
AutoPat *ap;
|
|
int in_use = FALSE;
|
|
|
|
|
|
i = au_find_group(name);
|
|
if (i == AUGROUP_ERROR) // the group doesn't exist
|
|
{
|
|
semsg(_(e_no_such_group_str), name);
|
|
return;
|
|
}
|
|
if (i == current_augroup)
|
|
{
|
|
emsg(_(e_cannot_delete_current_group));
|
|
return;
|
|
}
|
|
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
{
|
|
FOR_ALL_AUTOCMD_PATTERNS(event, ap)
|
|
if (ap->group == i && ap->pat != NULL)
|
|
{
|
|
give_warning((char_u *)_("W19: Deleting augroup that is still in use"), TRUE);
|
|
in_use = TRUE;
|
|
event = NUM_EVENTS;
|
|
break;
|
|
}
|
|
}
|
|
vim_free(AUGROUP_NAME(i));
|
|
if (in_use)
|
|
AUGROUP_NAME(i) = get_deleted_augroup();
|
|
else
|
|
AUGROUP_NAME(i) = NULL;
|
|
}
|
|
|
|
/*
|
|
* Find the ID of an autocmd group name.
|
|
* Return its ID. Returns AUGROUP_ERROR (< 0) for error.
|
|
*/
|
|
static int
|
|
au_find_group(char_u *name)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < augroups.ga_len; ++i)
|
|
if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup()
|
|
&& STRCMP(AUGROUP_NAME(i), name) == 0)
|
|
return i;
|
|
return AUGROUP_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if augroup "name" exists.
|
|
*/
|
|
int
|
|
au_has_group(char_u *name)
|
|
{
|
|
return au_find_group(name) != AUGROUP_ERROR;
|
|
}
|
|
|
|
/*
|
|
* ":augroup {name}".
|
|
*/
|
|
void
|
|
do_augroup(char_u *arg, int del_group)
|
|
{
|
|
int i;
|
|
|
|
if (del_group)
|
|
{
|
|
if (*arg == NUL)
|
|
emsg(_(e_argument_required));
|
|
else
|
|
au_del_group(arg);
|
|
}
|
|
else if (STRICMP(arg, "end") == 0) // ":aug end": back to group 0
|
|
current_augroup = AUGROUP_DEFAULT;
|
|
else if (*arg) // ":aug xxx": switch to group xxx
|
|
{
|
|
i = au_new_group(arg);
|
|
if (i != AUGROUP_ERROR)
|
|
current_augroup = i;
|
|
}
|
|
else // ":aug": list the group names
|
|
{
|
|
msg_start();
|
|
for (i = 0; i < augroups.ga_len; ++i)
|
|
{
|
|
if (AUGROUP_NAME(i) != NULL)
|
|
{
|
|
msg_puts((char *)AUGROUP_NAME(i));
|
|
msg_puts(" ");
|
|
}
|
|
}
|
|
msg_clr_eos();
|
|
msg_end();
|
|
}
|
|
}
|
|
|
|
void
|
|
autocmd_init(void)
|
|
{
|
|
CLEAR_FIELD(aucmd_win);
|
|
}
|
|
|
|
#if defined(EXITFREE) || defined(PROTO)
|
|
void
|
|
free_all_autocmds(void)
|
|
{
|
|
char_u *s;
|
|
|
|
for (current_augroup = -1; current_augroup < augroups.ga_len;
|
|
++current_augroup)
|
|
do_autocmd(NULL, (char_u *)"", TRUE);
|
|
|
|
for (int i = 0; i < augroups.ga_len; ++i)
|
|
{
|
|
s = ((char_u **)(augroups.ga_data))[i];
|
|
if (s != get_deleted_augroup())
|
|
vim_free(s);
|
|
}
|
|
ga_clear(&augroups);
|
|
|
|
// aucmd_win[] is freed in win_free_all()
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Return TRUE if "win" is an active entry in aucmd_win[].
|
|
*/
|
|
int
|
|
is_aucmd_win(win_T *win)
|
|
{
|
|
for (int i = 0; i < AUCMD_WIN_COUNT; ++i)
|
|
if (aucmd_win[i].auc_win_used && aucmd_win[i].auc_win == win)
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return the event number for event name "start".
|
|
* Return NUM_EVENTS if the event name was not found.
|
|
* Return a pointer to the next event name in "end".
|
|
*/
|
|
static event_T
|
|
event_name2nr(char_u *start, char_u **end)
|
|
{
|
|
char_u *p;
|
|
keyvalue_T target;
|
|
keyvalue_T *entry;
|
|
static keyvalue_T *bufnewfile = &event_tab[BUFNEWFILE_INDEX];
|
|
static keyvalue_T *bufread = &event_tab[BUFREAD_INDEX];
|
|
|
|
// the event name ends with end of line, '|', a blank or a comma
|
|
for (p = start; *p && !VIM_ISWHITE(*p) && *p != ',' && *p != '|'; ++p)
|
|
;
|
|
|
|
target.key = 0;
|
|
target.value.string = start;
|
|
target.value.length = (size_t)(p - start);
|
|
|
|
// special cases:
|
|
// BufNewFile and BufRead are searched for ALOT (especially at startup)
|
|
// so we check for them first.
|
|
if (cmp_keyvalue_value_ni(&target, bufnewfile) == 0)
|
|
entry = bufnewfile;
|
|
else if (cmp_keyvalue_value_ni(&target, bufread) == 0)
|
|
entry = bufread;
|
|
else
|
|
entry = (keyvalue_T *)bsearch(&target, &event_tab, NUM_EVENTS,
|
|
sizeof(event_tab[0]), cmp_keyvalue_value_ni);
|
|
|
|
if (*p == ',')
|
|
++p;
|
|
*end = p;
|
|
|
|
return (entry == NULL) ? NUM_EVENTS : (event_T)abs(entry->key);
|
|
}
|
|
|
|
/*
|
|
* Return the name for event "event".
|
|
*/
|
|
static char_u *
|
|
event_nr2name(event_T event)
|
|
{
|
|
int i;
|
|
#define CACHE_SIZE 12
|
|
static int cache_tab[CACHE_SIZE];
|
|
static int cache_last_index = -1;
|
|
|
|
if (cache_last_index < 0)
|
|
{
|
|
for (i = 0; i < CACHE_SIZE; ++i)
|
|
cache_tab[i] = -1;
|
|
cache_last_index = CACHE_SIZE - 1;
|
|
}
|
|
|
|
// first look in the cache
|
|
// the cache is circular. to search it we start at the most recent entry
|
|
// and go backwards wrapping around when we get to index 0.
|
|
for (i = cache_last_index; cache_tab[i] >= 0; )
|
|
{
|
|
if ((event_T)abs(event_tab[cache_tab[i]].key) == event)
|
|
return event_tab[cache_tab[i]].value.string;
|
|
|
|
if (i == 0)
|
|
i = CACHE_SIZE - 1;
|
|
else
|
|
--i;
|
|
|
|
// are we back at the start?
|
|
if (i == cache_last_index)
|
|
break;
|
|
}
|
|
|
|
// look in the event table itself
|
|
for (i = 0; i < NUM_EVENTS; ++i)
|
|
{
|
|
if ((event_T)abs(event_tab[i].key) == event)
|
|
{
|
|
// store the found entry in the next position in the cache,
|
|
// wrapping around when we get to the maximum index.
|
|
if (cache_last_index == CACHE_SIZE - 1)
|
|
cache_last_index = 0;
|
|
else
|
|
++cache_last_index;
|
|
cache_tab[cache_last_index] = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (i == NUM_EVENTS) ? (char_u *)"Unknown" :
|
|
event_tab[i].value.string;
|
|
}
|
|
|
|
/*
|
|
* Scan over the events. "*" stands for all events.
|
|
*/
|
|
static char_u *
|
|
find_end_event(
|
|
char_u *arg,
|
|
int have_group) // TRUE when group name was found
|
|
{
|
|
char_u *pat;
|
|
char_u *p;
|
|
|
|
if (*arg == '*')
|
|
{
|
|
if (arg[1] && !VIM_ISWHITE(arg[1]))
|
|
{
|
|
semsg(_(e_illegal_character_after_star_str), arg);
|
|
return NULL;
|
|
}
|
|
pat = arg + 1;
|
|
}
|
|
else
|
|
{
|
|
for (pat = arg; *pat && *pat != '|' && !VIM_ISWHITE(*pat); pat = p)
|
|
{
|
|
if ((int)event_name2nr(pat, &p) >= NUM_EVENTS)
|
|
{
|
|
if (have_group)
|
|
semsg(_(e_no_such_event_str), pat);
|
|
else
|
|
semsg(_(e_no_such_group_or_event_str), pat);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return pat;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if "event" is included in 'eventignore(win)'.
|
|
*/
|
|
int
|
|
event_ignored(event_T event, char_u *ei)
|
|
{
|
|
while (*ei != NUL)
|
|
{
|
|
if (STRNICMP(ei, "all", 3) == 0 && (ei[3] == NUL || ei[3] == ',')
|
|
&& (ei == p_ei || (event_tab[event].key <= 0)))
|
|
return TRUE;
|
|
if (event_name2nr(ei, &ei) == event)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return OK when the contents of 'eventignore' or 'eventignorewin' is valid,
|
|
* FAIL otherwise.
|
|
*/
|
|
int
|
|
check_ei(char_u *ei)
|
|
{
|
|
int win = ei != p_ei;
|
|
|
|
while (*ei)
|
|
{
|
|
if (STRNICMP(ei, "all", 3) == 0 && (ei[3] == NUL || ei[3] == ','))
|
|
{
|
|
ei += 3;
|
|
if (*ei == ',')
|
|
++ei;
|
|
}
|
|
else
|
|
{
|
|
event_T event = event_name2nr(ei, &ei);
|
|
if (event == NUM_EVENTS || (win && event_tab[event].key > 0))
|
|
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;
|
|
size_t p_ei_len;
|
|
|
|
p_ei_len = STRLEN(p_ei);
|
|
save_ei = vim_strnsave(p_ei, p_ei_len);
|
|
if (save_ei == NULL)
|
|
return NULL;
|
|
|
|
new_ei = vim_strnsave(p_ei, p_ei_len + STRLEN(what));
|
|
if (new_ei == NULL)
|
|
{
|
|
vim_free(save_ei);
|
|
return NULL;
|
|
}
|
|
|
|
if (*what == ',' && *p_ei == NUL)
|
|
STRCPY(new_ei, what + 1);
|
|
else
|
|
STRCPY(new_ei + p_ei_len, 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>.
|
|
* "eap" can be NULL.
|
|
*/
|
|
void
|
|
do_autocmd(exarg_T *eap, char_u *arg_in, int forceit)
|
|
{
|
|
char_u *arg = arg_in;
|
|
char_u *pat;
|
|
char_u *envpat = NULL;
|
|
char_u *cmd;
|
|
int cmd_need_free = FALSE;
|
|
event_T event;
|
|
char_u *tofree = NULL;
|
|
int nested = FALSE;
|
|
int once = FALSE;
|
|
int group;
|
|
int i;
|
|
int flags = 0;
|
|
|
|
if (*arg == '|')
|
|
{
|
|
eap->nextcmd = arg + 1;
|
|
arg = (char_u *)"";
|
|
group = AUGROUP_ALL; // no argument, use all groups
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Check for a legal group name. If not, use AUGROUP_ALL.
|
|
*/
|
|
group = au_get_grouparg(&arg);
|
|
if (arg == NULL) // out of memory
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Scan over the events.
|
|
* If we find an illegal name, return here, don't do anything.
|
|
*/
|
|
pat = find_end_event(arg, group != AUGROUP_ALL);
|
|
if (pat == NULL)
|
|
return;
|
|
|
|
pat = skipwhite(pat);
|
|
if (*pat == '|')
|
|
{
|
|
eap->nextcmd = pat + 1;
|
|
pat = (char_u *)"";
|
|
cmd = (char_u *)"";
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Scan over the pattern. Put a NUL at the end.
|
|
*/
|
|
cmd = pat;
|
|
while (*cmd && (!VIM_ISWHITE(*cmd) || cmd[-1] == '\\'))
|
|
cmd++;
|
|
if (*cmd)
|
|
*cmd++ = NUL;
|
|
|
|
// Expand environment variables in the pattern. Set 'shellslash', we
|
|
// want forward slashes here.
|
|
if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL)
|
|
{
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
int p_ssl_save = p_ssl;
|
|
|
|
p_ssl = TRUE;
|
|
#endif
|
|
envpat = expand_env_save(pat);
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
p_ssl = p_ssl_save;
|
|
#endif
|
|
if (envpat != NULL)
|
|
pat = envpat;
|
|
}
|
|
|
|
cmd = skipwhite(cmd);
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
if (*cmd == NUL)
|
|
continue;
|
|
|
|
// Check for "++once" flag.
|
|
if (STRNCMP(cmd, "++once", 6) == 0 && VIM_ISWHITE(cmd[6]))
|
|
{
|
|
if (once)
|
|
semsg(_(e_duplicate_argument_str), "++once");
|
|
once = TRUE;
|
|
cmd = skipwhite(cmd + 6);
|
|
}
|
|
|
|
// Check for "++nested" flag.
|
|
if ((STRNCMP(cmd, "++nested", 8) == 0 && VIM_ISWHITE(cmd[8])))
|
|
{
|
|
if (nested)
|
|
{
|
|
semsg(_(e_duplicate_argument_str), "++nested");
|
|
return;
|
|
}
|
|
nested = TRUE;
|
|
cmd = skipwhite(cmd + 8);
|
|
}
|
|
|
|
// Check for the old "nested" flag in legacy script.
|
|
if (STRNCMP(cmd, "nested", 6) == 0 && VIM_ISWHITE(cmd[6]))
|
|
{
|
|
if (in_vim9script())
|
|
{
|
|
// If there ever is a :nested command this error should
|
|
// be removed and "nested" accepted as the start of the
|
|
// command.
|
|
emsg(_(e_invalid_command_nested_did_you_mean_plusplus_nested));
|
|
return;
|
|
}
|
|
if (nested)
|
|
{
|
|
semsg(_(e_duplicate_argument_str), "nested");
|
|
return;
|
|
}
|
|
nested = TRUE;
|
|
cmd = skipwhite(cmd + 6);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Find the start of the commands.
|
|
* Expand <sfile> in it.
|
|
*/
|
|
if (*cmd != NUL)
|
|
{
|
|
if (eap != NULL)
|
|
// Read a {} block if it follows.
|
|
cmd = may_get_cmd_block(eap, cmd, &tofree, &flags);
|
|
|
|
cmd = expand_sfile(cmd);
|
|
if (cmd == NULL) // some error
|
|
return;
|
|
cmd_need_free = TRUE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print header when showing autocommands.
|
|
*/
|
|
if (!forceit && *cmd == NUL)
|
|
// Highlight title
|
|
msg_puts_title(_("\n--- Autocommands ---"));
|
|
|
|
/*
|
|
* Loop over the events.
|
|
*/
|
|
last_event = (event_T)-1; // for listing the event name
|
|
last_group = AUGROUP_ERROR; // for listing the group name
|
|
if (*arg == '*' || *arg == NUL || *arg == '|')
|
|
{
|
|
if (*cmd != NUL)
|
|
emsg(_(e_cannot_define_autocommands_for_all_events));
|
|
else
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
if (do_autocmd_event(event, pat,
|
|
once, nested, cmd, forceit, group, flags) == FAIL)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
while (*arg && *arg != '|' && !VIM_ISWHITE(*arg))
|
|
if (do_autocmd_event(event_name2nr(arg, &arg), pat,
|
|
once, nested, cmd, forceit, group, flags) == FAIL)
|
|
break;
|
|
}
|
|
|
|
if (cmd_need_free)
|
|
vim_free(cmd);
|
|
vim_free(tofree);
|
|
vim_free(envpat);
|
|
}
|
|
|
|
/*
|
|
* Find the group ID in a ":autocmd" or ":doautocmd" argument.
|
|
* The "argp" argument is advanced to the following argument.
|
|
*
|
|
* Returns the group ID, AUGROUP_ERROR for error (out of memory).
|
|
*/
|
|
static int
|
|
au_get_grouparg(char_u **argp)
|
|
{
|
|
char_u *group_name;
|
|
char_u *p;
|
|
char_u *arg = *argp;
|
|
int group = AUGROUP_ALL;
|
|
|
|
for (p = arg; *p && !VIM_ISWHITE(*p) && *p != '|'; ++p)
|
|
;
|
|
if (p <= arg)
|
|
return AUGROUP_ALL;
|
|
|
|
group_name = vim_strnsave(arg, p - arg);
|
|
if (group_name == NULL) // out of memory
|
|
return AUGROUP_ERROR;
|
|
group = au_find_group(group_name);
|
|
if (group == AUGROUP_ERROR)
|
|
group = AUGROUP_ALL; // no match, use all groups
|
|
else
|
|
*argp = skipwhite(p); // match, skip over group name
|
|
vim_free(group_name);
|
|
return group;
|
|
}
|
|
|
|
/*
|
|
* do_autocmd() for one event.
|
|
* If *pat == NUL do for all patterns.
|
|
* If *cmd == NUL show entries.
|
|
* If forceit == TRUE delete entries.
|
|
* If group is not AUGROUP_ALL, only use this group.
|
|
*/
|
|
static int
|
|
do_autocmd_event(
|
|
event_T event,
|
|
char_u *pat,
|
|
int once,
|
|
int nested,
|
|
char_u *cmd,
|
|
int forceit,
|
|
int group,
|
|
int flags)
|
|
{
|
|
AutoPat *ap;
|
|
AutoPat **prev_ap;
|
|
AutoCmd *ac;
|
|
AutoCmd **prev_ac;
|
|
int brace_level;
|
|
char_u *endpat;
|
|
int findgroup;
|
|
int allgroups;
|
|
int patlen;
|
|
int is_buflocal;
|
|
int buflocal_nr;
|
|
char_u buflocal_pat[25]; // for "<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_ALL_AUTOCMD_PATTERNS(event, ap)
|
|
{
|
|
if (forceit) // delete the AutoPat, if it's in the current group
|
|
{
|
|
if (ap->group == findgroup)
|
|
au_remove_pat(ap);
|
|
}
|
|
else if (group == AUGROUP_ALL || ap->group == group)
|
|
show_autocmd(ap, event);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Loop through all the specified patterns.
|
|
*/
|
|
for ( ; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat))
|
|
{
|
|
/*
|
|
* Find end of the pattern.
|
|
* Watch out for a comma in braces, like "*.\{obj,o\}".
|
|
*/
|
|
brace_level = 0;
|
|
for (endpat = pat; *endpat && (*endpat != ',' || brace_level
|
|
|| (endpat > pat && endpat[-1] == '\\')); ++endpat)
|
|
{
|
|
if (*endpat == '{')
|
|
brace_level++;
|
|
else if (*endpat == '}')
|
|
brace_level--;
|
|
}
|
|
if (pat == endpat) // ignore single comma
|
|
continue;
|
|
patlen = (int)(endpat - pat);
|
|
|
|
/*
|
|
* detect special <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(_(e_buffer_nr_invalid_buffer_number), buflocal_nr);
|
|
return FAIL;
|
|
}
|
|
|
|
ap = ALLOC_ONE(AutoPat);
|
|
if (ap == NULL)
|
|
return FAIL;
|
|
ap->pat = vim_strnsave(pat, patlen);
|
|
ap->patlen = patlen;
|
|
if (ap->pat == NULL)
|
|
{
|
|
vim_free(ap);
|
|
return FAIL;
|
|
}
|
|
|
|
#ifdef FEAT_EVAL
|
|
// need to initialize last_mode for the first ModeChanged
|
|
// autocmd
|
|
if (event == EVENT_MODECHANGED && !has_modechanged())
|
|
get_mode(last_mode);
|
|
#endif
|
|
// Initialize the fields checked by the WinScrolled and
|
|
// WinResized trigger to prevent them from firing right after
|
|
// the first autocmd is defined.
|
|
if ((event == EVENT_WINSCROLLED || event == EVENT_WINRESIZED)
|
|
&& !(has_winscrolled() || has_winresized()))
|
|
{
|
|
tabpage_T *save_curtab = curtab;
|
|
tabpage_T *tp;
|
|
FOR_ALL_TABPAGES(tp)
|
|
{
|
|
unuse_tabpage(curtab);
|
|
use_tabpage(tp);
|
|
snapshot_windows_scroll_size();
|
|
}
|
|
unuse_tabpage(curtab);
|
|
use_tabpage(save_curtab);
|
|
}
|
|
|
|
if (is_buflocal)
|
|
{
|
|
ap->buflocal_nr = buflocal_nr;
|
|
ap->reg_prog = NULL;
|
|
}
|
|
else
|
|
{
|
|
char_u *reg_pat;
|
|
|
|
ap->buflocal_nr = 0;
|
|
reg_pat = file_pat_to_reg_pat(pat, endpat,
|
|
&ap->allow_dirs, TRUE);
|
|
if (reg_pat != NULL)
|
|
ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
|
|
vim_free(reg_pat);
|
|
if (reg_pat == NULL || ap->reg_prog == NULL)
|
|
{
|
|
vim_free(ap->pat);
|
|
vim_free(ap);
|
|
return FAIL;
|
|
}
|
|
}
|
|
ap->cmds = NULL;
|
|
*prev_ap = ap;
|
|
last_autopat[(int)event] = ap;
|
|
ap->next = NULL;
|
|
if (group == AUGROUP_ALL)
|
|
ap->group = current_augroup;
|
|
else
|
|
ap->group = group;
|
|
}
|
|
|
|
/*
|
|
* Add the autocmd at the end of the AutoCmd list.
|
|
*/
|
|
prev_ac = &(ap->cmds);
|
|
while ((ac = *prev_ac) != NULL)
|
|
prev_ac = &ac->next;
|
|
ac = ALLOC_ONE(AutoCmd);
|
|
if (ac == NULL)
|
|
return FAIL;
|
|
ac->cmd = vim_strsave(cmd);
|
|
ac->script_ctx = current_sctx;
|
|
if (flags & UC_VIM9)
|
|
ac->script_ctx.sc_version = SCRIPT_VERSION_VIM9;
|
|
#ifdef FEAT_EVAL
|
|
ac->script_ctx.sc_lnum += SOURCING_LNUM;
|
|
#endif
|
|
if (ac->cmd == NULL)
|
|
{
|
|
vim_free(ac);
|
|
return FAIL;
|
|
}
|
|
ac->next = NULL;
|
|
*prev_ac = ac;
|
|
ac->once = once;
|
|
ac->nested = nested;
|
|
}
|
|
}
|
|
|
|
au_cleanup(); // may really delete removed patterns/commands now
|
|
return OK;
|
|
}
|
|
|
|
/*
|
|
* Implementation of ":doautocmd [group] event [fname]".
|
|
* Return OK for success, FAIL for failure;
|
|
*/
|
|
int
|
|
do_doautocmd(
|
|
char_u *arg_start,
|
|
int do_msg, // give message for no matching autocmds?
|
|
int *did_something)
|
|
{
|
|
char_u *arg = arg_start;
|
|
char_u *fname;
|
|
int nothing_done = TRUE;
|
|
int group;
|
|
|
|
if (did_something != NULL)
|
|
*did_something = FALSE;
|
|
|
|
/*
|
|
* Check for a legal group name. If not, use AUGROUP_ALL.
|
|
*/
|
|
group = au_get_grouparg(&arg);
|
|
if (arg == NULL) // out of memory
|
|
return FAIL;
|
|
|
|
if (*arg == '*')
|
|
{
|
|
emsg(_(e_cant_execute_autocommands_for_all_events));
|
|
return FAIL;
|
|
}
|
|
|
|
/*
|
|
* Scan over the events.
|
|
* If we find an illegal name, return here, don't do anything.
|
|
*/
|
|
fname = find_end_event(arg, group != AUGROUP_ALL);
|
|
if (fname == NULL)
|
|
return FAIL;
|
|
|
|
fname = skipwhite(fname);
|
|
|
|
/*
|
|
* Loop over the events.
|
|
*/
|
|
while (*arg && !ends_excmd(*arg) && !VIM_ISWHITE(*arg))
|
|
if (apply_autocmds_group(event_name2nr(arg, &arg),
|
|
fname, NULL, TRUE, group, curbuf, NULL))
|
|
nothing_done = FALSE;
|
|
|
|
if (nothing_done && do_msg
|
|
#ifdef FEAT_EVAL
|
|
&& !aborting()
|
|
#endif
|
|
)
|
|
smsg(_("No matching autocommands: %s"), arg_start);
|
|
if (did_something != NULL)
|
|
*did_something = !nothing_done;
|
|
|
|
#ifdef FEAT_EVAL
|
|
return aborting() ? FAIL : OK;
|
|
#else
|
|
return OK;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* ":doautoall": execute autocommands for each loaded buffer.
|
|
*/
|
|
void
|
|
ex_doautoall(exarg_T *eap)
|
|
{
|
|
int retval = OK;
|
|
aco_save_T aco;
|
|
buf_T *buf;
|
|
bufref_T bufref;
|
|
char_u *arg = eap->arg;
|
|
int call_do_modelines = check_nomodeline(&arg);
|
|
int did_aucmd;
|
|
|
|
/*
|
|
* This is a bit tricky: For some commands curwin->w_buffer needs to be
|
|
* equal to curbuf, but for some buffers there may not be a window.
|
|
* So we change the buffer for the current window for a moment. This
|
|
* gives problems when the autocommands make changes to the list of
|
|
* buffers or windows...
|
|
*/
|
|
FOR_ALL_BUFFERS(buf)
|
|
{
|
|
// Only do loaded buffers and skip the current buffer, it's done last.
|
|
if (buf->b_ml.ml_mfp == NULL || buf == curbuf)
|
|
continue;
|
|
|
|
// Find a window for this buffer and save some values.
|
|
aucmd_prepbuf(&aco, buf);
|
|
if (curbuf != buf)
|
|
{
|
|
// Failed to find a window for this buffer. Better not execute
|
|
// autocommands then.
|
|
retval = FAIL;
|
|
break;
|
|
}
|
|
|
|
set_bufref(&bufref, buf);
|
|
|
|
// execute the autocommands for this buffer
|
|
retval = do_doautocmd(arg, FALSE, &did_aucmd);
|
|
|
|
if (call_do_modelines && did_aucmd)
|
|
// Execute the modeline settings, but don't set window-local
|
|
// options if we are using the current window for another
|
|
// buffer.
|
|
do_modelines(is_aucmd_win(curwin) ? OPT_NOWIN : 0);
|
|
|
|
// restore the current window
|
|
aucmd_restbuf(&aco);
|
|
|
|
// stop if there is some error or buffer was deleted
|
|
if (retval == FAIL || !bufref_valid(&bufref))
|
|
{
|
|
retval = FAIL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Execute autocommands for the current buffer last.
|
|
if (retval == OK)
|
|
{
|
|
do_doautocmd(arg, FALSE, &did_aucmd);
|
|
if (call_do_modelines && did_aucmd)
|
|
do_modelines(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check *argp for <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 an entry in "aucmd_win[]".
|
|
* Set "curbuf" and "curwin" to match "buf".
|
|
* When this fails "curbuf" is not equal "buf".
|
|
*/
|
|
void
|
|
aucmd_prepbuf(
|
|
aco_save_T *aco, // structure to save values in
|
|
buf_T *buf) // new curbuf
|
|
{
|
|
win_T *win;
|
|
int save_ea;
|
|
#ifdef FEAT_AUTOCHDIR
|
|
int save_acd;
|
|
#endif
|
|
|
|
// Find a window that is for the new buffer
|
|
if (buf == curbuf) // be quick when buf is curbuf
|
|
win = curwin;
|
|
else
|
|
FOR_ALL_WINDOWS(win)
|
|
if (win->w_buffer == buf)
|
|
break;
|
|
|
|
// Allocate a window when needed.
|
|
win_T *auc_win = NULL;
|
|
int auc_idx = AUCMD_WIN_COUNT;
|
|
if (win == NULL)
|
|
{
|
|
for (auc_idx = 0; auc_idx < AUCMD_WIN_COUNT; ++auc_idx)
|
|
if (!aucmd_win[auc_idx].auc_win_used)
|
|
{
|
|
if (aucmd_win[auc_idx].auc_win == NULL)
|
|
aucmd_win[auc_idx].auc_win = win_alloc_popup_win();
|
|
auc_win = aucmd_win[auc_idx].auc_win;
|
|
if (auc_win != NULL)
|
|
aucmd_win[auc_idx].auc_win_used = TRUE;
|
|
break;
|
|
}
|
|
|
|
// If this fails (out of memory or using all AUCMD_WIN_COUNT
|
|
// entries) then we can't reliable execute the autocmd, return with
|
|
// "curbuf" unequal "buf".
|
|
if (auc_win == NULL)
|
|
return;
|
|
}
|
|
|
|
aco->save_curwin_id = curwin->w_id;
|
|
aco->save_prevwin_id = prevwin == NULL ? 0 : prevwin->w_id;
|
|
aco->save_State = State;
|
|
#ifdef FEAT_JOB_CHANNEL
|
|
if (bt_prompt(curbuf))
|
|
aco->save_prompt_insert = curbuf->b_prompt_insert;
|
|
#endif
|
|
|
|
if (win != NULL)
|
|
{
|
|
// There is a window for "buf" in the current tab page, make it the
|
|
// curwin. This is preferred, it has the least side effects (esp. if
|
|
// "buf" is curbuf).
|
|
aco->use_aucmd_win_idx = -1;
|
|
curwin = win;
|
|
}
|
|
else
|
|
{
|
|
// There is no window for "buf", use "auc_win". To minimize the side
|
|
// effects, insert it in the current tab page.
|
|
// Anything related to a window (e.g., setting folds) may have
|
|
// unexpected results.
|
|
aco->use_aucmd_win_idx = auc_idx;
|
|
|
|
win_init_popup_win(auc_win, buf);
|
|
|
|
// Make sure tp_localdir and globaldir are NULL to avoid a
|
|
// chdir() in win_enter_ext().
|
|
// win_init_popup_win() has already set w_localdir to NULL.
|
|
aco->tp_localdir = curtab->tp_localdir;
|
|
curtab->tp_localdir = NULL;
|
|
aco->globaldir = globaldir;
|
|
globaldir = NULL;
|
|
|
|
// Split the current window, put the auc_win in the upper half.
|
|
// We don't want the BufEnter or WinEnter autocommands.
|
|
block_autocmds();
|
|
make_snapshot(SNAP_AUCMD_IDX);
|
|
save_ea = p_ea;
|
|
p_ea = FALSE;
|
|
|
|
#ifdef FEAT_AUTOCHDIR
|
|
// Prevent chdir() call in win_enter_ext(), through do_autochdir().
|
|
save_acd = p_acd;
|
|
p_acd = FALSE;
|
|
#endif
|
|
|
|
(void)win_split_ins(0, WSP_TOP | WSP_FORCE_ROOM, auc_win, 0, NULL);
|
|
(void)win_comp_pos(); // recompute window positions
|
|
p_ea = save_ea;
|
|
#ifdef FEAT_AUTOCHDIR
|
|
p_acd = save_acd;
|
|
#endif
|
|
unblock_autocmds();
|
|
curwin = auc_win;
|
|
}
|
|
curbuf = buf;
|
|
aco->new_curwin_id = curwin->w_id;
|
|
set_bufref(&aco->new_curbuf, curbuf);
|
|
|
|
// disable the Visual area, the position may be invalid in another buffer
|
|
aco->save_VIsual_active = VIsual_active;
|
|
VIsual_active = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Cleanup after executing autocommands for a (hidden) buffer.
|
|
* Restore the window as it was (if possible).
|
|
*/
|
|
void
|
|
aucmd_restbuf(
|
|
aco_save_T *aco) // structure holding saved values
|
|
{
|
|
int dummy;
|
|
win_T *save_curwin;
|
|
|
|
if (aco->use_aucmd_win_idx >= 0)
|
|
{
|
|
win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win;
|
|
|
|
// Find "awp", it can't be closed, but it may be in another tab
|
|
// page. Do not trigger autocommands here.
|
|
block_autocmds();
|
|
if (curwin != awp)
|
|
{
|
|
tabpage_T *tp;
|
|
win_T *wp;
|
|
|
|
FOR_ALL_TAB_WINDOWS(tp, wp)
|
|
{
|
|
if (wp == awp)
|
|
{
|
|
if (tp != curtab)
|
|
goto_tabpage_tp(tp, TRUE, TRUE);
|
|
win_goto(awp);
|
|
goto win_found;
|
|
}
|
|
}
|
|
}
|
|
win_found:
|
|
--curbuf->b_nwindows;
|
|
#ifdef FEAT_JOB_CHANNEL
|
|
int save_stop_insert_mode = stop_insert_mode;
|
|
// May need to stop Insert mode if we were in a prompt buffer.
|
|
leaving_window(curwin);
|
|
// Do not stop Insert mode when already in Insert mode before.
|
|
if (aco->save_State & MODE_INSERT)
|
|
stop_insert_mode = save_stop_insert_mode;
|
|
#endif
|
|
// Remove the window and frame from the tree of frames.
|
|
(void)winframe_remove(curwin, &dummy, NULL, NULL);
|
|
win_remove(curwin, NULL);
|
|
|
|
// The window is marked as not used, but it is not freed, it can be
|
|
// used again.
|
|
aucmd_win[aco->use_aucmd_win_idx].auc_win_used = FALSE;
|
|
last_status(FALSE); // may need to remove last status line
|
|
|
|
if (!valid_tabpage_win(curtab))
|
|
// no valid window in current tabpage
|
|
close_tabpage(curtab);
|
|
|
|
restore_snapshot(SNAP_AUCMD_IDX, FALSE);
|
|
(void)win_comp_pos(); // recompute window positions
|
|
unblock_autocmds();
|
|
|
|
save_curwin = win_find_by_id(aco->save_curwin_id);
|
|
if (save_curwin != NULL)
|
|
curwin = save_curwin;
|
|
else
|
|
// Hmm, original window disappeared. Just use the first one.
|
|
curwin = firstwin;
|
|
curbuf = curwin->w_buffer;
|
|
#ifdef FEAT_JOB_CHANNEL
|
|
// May need to restore insert mode for a prompt buffer.
|
|
entering_window(curwin);
|
|
if (bt_prompt(curbuf))
|
|
curbuf->b_prompt_insert = aco->save_prompt_insert;
|
|
#endif
|
|
prevwin = win_find_by_id(aco->save_prevwin_id);
|
|
#ifdef FEAT_EVAL
|
|
vars_clear(&awp->w_vars->dv_hashtab); // free all w: variables
|
|
hash_init(&awp->w_vars->dv_hashtab); // re-use the hashtab
|
|
#endif
|
|
// If :lcd has been used in the autocommand window, correct current
|
|
// directory before restoring tp_localdir and globaldir.
|
|
if (awp->w_localdir != NULL)
|
|
win_fix_current_dir();
|
|
vim_free(curtab->tp_localdir);
|
|
curtab->tp_localdir = aco->tp_localdir;
|
|
vim_free(globaldir);
|
|
globaldir = aco->globaldir;
|
|
|
|
// the buffer contents may have changed
|
|
VIsual_active = aco->save_VIsual_active;
|
|
check_cursor();
|
|
if (curwin->w_topline > curbuf->b_ml.ml_line_count)
|
|
{
|
|
curwin->w_topline = curbuf->b_ml.ml_line_count;
|
|
#ifdef FEAT_DIFF
|
|
curwin->w_topfill = 0;
|
|
#endif
|
|
}
|
|
#if defined(FEAT_GUI)
|
|
if (gui.in_use)
|
|
{
|
|
// Hide the scrollbars from the "awp" and update.
|
|
gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_LEFT], FALSE);
|
|
gui_mch_enable_scrollbar(&awp->w_scrollbars[SBAR_RIGHT], FALSE);
|
|
gui_may_update_scrollbars();
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Restore curwin. Use the window ID, a window may have been closed
|
|
// and the memory re-used for another one.
|
|
save_curwin = win_find_by_id(aco->save_curwin_id);
|
|
if (save_curwin != NULL)
|
|
{
|
|
// Restore the buffer which was previously edited by curwin, if
|
|
// it was changed, we are still the same window and the buffer is
|
|
// valid.
|
|
if (curwin->w_id == aco->new_curwin_id
|
|
&& curbuf != aco->new_curbuf.br_buf
|
|
&& bufref_valid(&aco->new_curbuf)
|
|
&& aco->new_curbuf.br_buf->b_ml.ml_mfp != NULL)
|
|
{
|
|
# if defined(FEAT_SYN_HL) || defined(FEAT_SPELL)
|
|
if (curwin->w_s == &curbuf->b_s)
|
|
curwin->w_s = &aco->new_curbuf.br_buf->b_s;
|
|
# endif
|
|
--curbuf->b_nwindows;
|
|
curbuf = aco->new_curbuf.br_buf;
|
|
curwin->w_buffer = curbuf;
|
|
++curbuf->b_nwindows;
|
|
}
|
|
|
|
curwin = save_curwin;
|
|
curbuf = curwin->w_buffer;
|
|
prevwin = win_find_by_id(aco->save_prevwin_id);
|
|
|
|
// In case the autocommand moves the cursor to a position that
|
|
// does not exist in curbuf.
|
|
VIsual_active = aco->save_VIsual_active;
|
|
check_cursor();
|
|
}
|
|
}
|
|
|
|
VIsual_active = aco->save_VIsual_active;
|
|
check_cursor(); // just in case lines got deleted
|
|
if (VIsual_active)
|
|
check_pos(curbuf, &VIsual);
|
|
}
|
|
|
|
static int autocmd_nested = FALSE;
|
|
|
|
/*
|
|
* Execute autocommands for "event" and file name "fname".
|
|
* Return TRUE if some commands were executed.
|
|
*/
|
|
int
|
|
apply_autocmds(
|
|
event_T event,
|
|
char_u *fname, // NULL or empty means use actual file name
|
|
char_u *fname_io, // fname to use for <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.
|
|
*/
|
|
static int
|
|
has_cursorhold(void)
|
|
{
|
|
return (first_autopat[(int)(get_real_state() == MODE_NORMAL_BUSY
|
|
? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if the CursorHold event can be triggered.
|
|
*/
|
|
int
|
|
trigger_cursorhold(void)
|
|
{
|
|
int state;
|
|
|
|
if (!did_cursorhold
|
|
&& has_cursorhold()
|
|
&& reg_recording == 0
|
|
&& typebuf.tb_len == 0
|
|
&& !ins_compl_active())
|
|
{
|
|
state = get_real_state();
|
|
if (state == MODE_NORMAL_BUSY || (state & MODE_INSERT) != 0)
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a WinResized autocommand defined.
|
|
*/
|
|
int
|
|
has_winresized(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_WINRESIZED] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a WinScrolled autocommand defined.
|
|
*/
|
|
int
|
|
has_winscrolled(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_WINSCROLLED] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a CursorMoved autocommand defined.
|
|
*/
|
|
int
|
|
has_cursormoved(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_CURSORMOVED] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a CursorMovedI autocommand defined.
|
|
*/
|
|
int
|
|
has_cursormovedI(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_CURSORMOVEDI] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a TextChanged autocommand defined.
|
|
*/
|
|
int
|
|
has_textchanged(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_TEXTCHANGED] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a TextChangedI autocommand defined.
|
|
*/
|
|
int
|
|
has_textchangedI(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a TextChangedP autocommand defined.
|
|
*/
|
|
int
|
|
has_textchangedP(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_TEXTCHANGEDP] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is an InsertCharPre autocommand defined.
|
|
*/
|
|
int
|
|
has_insertcharpre(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_INSERTCHARPRE] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is an KeyInputPre autocommand defined.
|
|
*/
|
|
int
|
|
has_keyinputpre(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_KEYINPUTPRE] != NULL);
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is an CmdUndefined autocommand defined.
|
|
*/
|
|
int
|
|
has_cmdundefined(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_CMDUNDEFINED] != NULL);
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Return TRUE when there is a TextYankPost autocommand defined.
|
|
*/
|
|
int
|
|
has_textyankpost(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_TEXTYANKPOST] != NULL);
|
|
}
|
|
#endif
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Return TRUE when there is a CompleteChanged autocommand defined.
|
|
*/
|
|
int
|
|
has_completechanged(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_COMPLETECHANGED] != NULL);
|
|
}
|
|
#endif
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Return TRUE when there is a ModeChanged autocommand defined.
|
|
*/
|
|
int
|
|
has_modechanged(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_MODECHANGED] != NULL);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Execute autocommands for "event" and file name "fname".
|
|
* Return TRUE if some commands were executed.
|
|
*/
|
|
static int
|
|
apply_autocmds_group(
|
|
event_T event,
|
|
char_u *fname, // NULL or empty means use actual file name
|
|
char_u *fname_io, // fname to use for <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_autocmd_fname;
|
|
int save_autocmd_fname_full;
|
|
int save_autocmd_bufnr;
|
|
char_u *save_autocmd_match;
|
|
int save_autocmd_busy;
|
|
int save_autocmd_nested;
|
|
static int nesting = 0;
|
|
AutoPatCmd_T patcmd;
|
|
AutoPat *ap;
|
|
sctx_T save_current_sctx;
|
|
#ifdef FEAT_EVAL
|
|
funccal_entry_T funccal_entry;
|
|
char_u *save_cmdarg;
|
|
long save_cmdbang;
|
|
#endif
|
|
static int filechangeshell_busy = FALSE;
|
|
#ifdef FEAT_PROFILE
|
|
proftime_T wait_time;
|
|
#endif
|
|
int did_save_redobuff = FALSE;
|
|
save_redo_T save_redo;
|
|
int save_KeyTyped = KeyTyped;
|
|
ESTACK_CHECK_DECLARATION;
|
|
|
|
/*
|
|
* Quickly return if there are no autocommands for this event or
|
|
* autocommands are blocked.
|
|
*/
|
|
if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
|
|
|| autocmd_blocked > 0)
|
|
goto BYPASS_AU;
|
|
|
|
/*
|
|
* When autocommands are busy, new autocommands are only executed when
|
|
* explicitly enabled with the "nested" flag.
|
|
*/
|
|
if (autocmd_busy && !(force || autocmd_nested))
|
|
goto BYPASS_AU;
|
|
|
|
#ifdef FEAT_EVAL
|
|
/*
|
|
* Quickly return when immediately aborting on error, or when an interrupt
|
|
* occurred or an exception was thrown but not caught.
|
|
*/
|
|
if (aborting())
|
|
goto BYPASS_AU;
|
|
#endif
|
|
|
|
/*
|
|
* FileChangedShell never nests, because it can create an endless loop.
|
|
*/
|
|
if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL
|
|
|| event == EVENT_FILECHANGEDSHELLPOST))
|
|
goto BYPASS_AU;
|
|
|
|
/*
|
|
* Ignore events in 'eventignore'.
|
|
*/
|
|
if (event_ignored(event, p_ei))
|
|
goto BYPASS_AU;
|
|
|
|
wininfo_T *wip;
|
|
int win_ignore = FALSE;
|
|
// If event is allowed in 'eventignorewin', check if curwin or all windows
|
|
// into "buf" are ignoring the event.
|
|
if (buf == curbuf && event_tab[event].key <= 0)
|
|
win_ignore = event_ignored(event, curwin->w_p_eiw);
|
|
else if (buf != NULL && event_tab[event].key <= 0)
|
|
FOR_ALL_BUF_WININFO(buf, wip)
|
|
if (wip->wi_win != NULL && wip->wi_win->w_buffer == buf)
|
|
win_ignore = event_ignored(event, wip->wi_win->w_p_eiw);
|
|
if (win_ignore)
|
|
goto BYPASS_AU;
|
|
|
|
/*
|
|
* Allow nesting of autocommands, but restrict the depth, because it's
|
|
* possible to create an endless loop.
|
|
*/
|
|
if (nesting == 10)
|
|
{
|
|
emsg(_(e_autocommand_nesting_too_deep));
|
|
goto BYPASS_AU;
|
|
}
|
|
|
|
/*
|
|
* Check if these autocommands are disabled. Used when doing ":all" or
|
|
* ":ball".
|
|
*/
|
|
if ( (autocmd_no_enter
|
|
&& (event == EVENT_WINENTER || event == EVENT_BUFENTER))
|
|
|| (autocmd_no_leave
|
|
&& (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE)))
|
|
goto BYPASS_AU;
|
|
|
|
if (event == EVENT_CMDLINECHANGED)
|
|
++aucmd_cmdline_changed_count;
|
|
|
|
/*
|
|
* Save the autocmd_* variables and info about the current buffer.
|
|
*/
|
|
save_autocmd_fname = autocmd_fname;
|
|
save_autocmd_fname_full = autocmd_fname_full;
|
|
save_autocmd_bufnr = autocmd_bufnr;
|
|
save_autocmd_match = autocmd_match;
|
|
save_autocmd_busy = autocmd_busy;
|
|
save_autocmd_nested = autocmd_nested;
|
|
save_changed = curbuf->b_changed;
|
|
old_curbuf = curbuf;
|
|
|
|
/*
|
|
* Set the file name to be used for <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
|
|
|| event == EVENT_MODECHANGED
|
|
|| event == EVENT_TERMRESPONSEALL)
|
|
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*, DirChanged and similar.
|
|
if (event == EVENT_FILETYPE
|
|
|| event == EVENT_SYNTAX
|
|
|| event == EVENT_CMDLINECHANGED
|
|
|| event == EVENT_CMDLINEENTER
|
|
|| event == EVENT_CMDLINELEAVE
|
|
|| event == EVENT_CURSORMOVEDC
|
|
|| event == EVENT_CMDWINENTER
|
|
|| event == EVENT_CMDWINLEAVE
|
|
|| event == EVENT_CMDUNDEFINED
|
|
|| event == EVENT_FUNCUNDEFINED
|
|
|| event == EVENT_KEYINPUTPRE
|
|
|| event == EVENT_REMOTEREPLY
|
|
|| event == EVENT_SPELLFILEMISSING
|
|
|| event == EVENT_QUICKFIXCMDPRE
|
|
|| event == EVENT_COLORSCHEME
|
|
|| event == EVENT_COLORSCHEMEPRE
|
|
|| event == EVENT_OPTIONSET
|
|
|| event == EVENT_QUICKFIXCMDPOST
|
|
|| event == EVENT_DIRCHANGED
|
|
|| event == EVENT_DIRCHANGEDPRE
|
|
|| event == EVENT_MODECHANGED
|
|
|| event == EVENT_MENUPOPUP
|
|
|| event == EVENT_USER
|
|
|| event == EVENT_WINCLOSED
|
|
|| event == EVENT_WINRESIZED
|
|
|| event == EVENT_WINSCROLLED
|
|
|| event == EVENT_TERMRESPONSEALL)
|
|
{
|
|
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;
|
|
|
|
// name and lnum are filled in later
|
|
estack_push(ETYPE_AUCMD, NULL, 0);
|
|
ESTACK_CHECK_SETUP;
|
|
|
|
save_current_sctx = current_sctx;
|
|
|
|
#ifdef FEAT_EVAL
|
|
# ifdef FEAT_PROFILE
|
|
if (do_profiling == PROF_YES)
|
|
prof_child_enter(&wait_time); // doesn't count for the caller itself
|
|
# endif
|
|
|
|
// Don't use local function variables, if called from a function.
|
|
save_funccal(&funccal_entry);
|
|
#endif
|
|
|
|
/*
|
|
* When starting to execute autocommands, save the search patterns.
|
|
*/
|
|
if (!autocmd_busy)
|
|
{
|
|
save_search_patterns();
|
|
if (!ins_compl_active())
|
|
{
|
|
saveRedobuff(&save_redo);
|
|
did_save_redobuff = TRUE;
|
|
}
|
|
curbuf->b_did_filetype = curbuf->b_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)
|
|
curbuf->b_did_filetype = TRUE;
|
|
|
|
tail = gettail(fname);
|
|
|
|
// Find first autocommand that matches
|
|
CLEAR_FIELD(patcmd);
|
|
patcmd.curpat = first_autopat[(int)event];
|
|
patcmd.group = group;
|
|
patcmd.fname = fname;
|
|
patcmd.sfname = sfname;
|
|
patcmd.tail = tail;
|
|
patcmd.event = event;
|
|
patcmd.arg_bufnr = autocmd_bufnr;
|
|
auto_next_pat(&patcmd, FALSE);
|
|
|
|
// found one, start executing the autocommands
|
|
if (patcmd.curpat != NULL)
|
|
{
|
|
// add to active_apc_list
|
|
patcmd.next = active_apc_list;
|
|
active_apc_list = &patcmd;
|
|
|
|
#ifdef FEAT_EVAL
|
|
// set v:cmdarg (only when there is a matching pattern)
|
|
save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
|
|
if (eap != NULL)
|
|
{
|
|
save_cmdarg = set_cmdarg(eap, NULL);
|
|
set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
|
|
}
|
|
else
|
|
save_cmdarg = NULL; // avoid gcc warning
|
|
#endif
|
|
retval = TRUE;
|
|
// mark the last pattern, to avoid an endless loop when more patterns
|
|
// are added when executing autocommands
|
|
for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next)
|
|
ap->last = FALSE;
|
|
ap->last = TRUE;
|
|
|
|
// Make sure cursor and topline are valid. The first time the current
|
|
// values are saved, restored by reset_lnums(). When nested only the
|
|
// values are corrected when needed.
|
|
if (nesting == 1)
|
|
check_lnums(TRUE);
|
|
else
|
|
check_lnums_nested(TRUE);
|
|
|
|
int save_did_emsg = did_emsg;
|
|
int save_ex_pressedreturn = get_pressedreturn();
|
|
|
|
do_cmdline(NULL, getnextac, (void *)&patcmd,
|
|
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
|
|
|
|
did_emsg += save_did_emsg;
|
|
set_pressedreturn(save_ex_pressedreturn);
|
|
|
|
if (nesting == 1)
|
|
// restore cursor and topline, unless they were changed
|
|
reset_lnums();
|
|
|
|
#ifdef FEAT_EVAL
|
|
if (eap != NULL)
|
|
{
|
|
(void)set_cmdarg(NULL, save_cmdarg);
|
|
set_vim_var_nr(VV_CMDBANG, save_cmdbang);
|
|
}
|
|
#endif
|
|
// delete from active_apc_list
|
|
if (active_apc_list == &patcmd) // just in case
|
|
active_apc_list = patcmd.next;
|
|
}
|
|
|
|
if (RedrawingDisabled > 0)
|
|
--RedrawingDisabled;
|
|
autocmd_busy = save_autocmd_busy;
|
|
filechangeshell_busy = FALSE;
|
|
autocmd_nested = save_autocmd_nested;
|
|
vim_free(SOURCING_NAME);
|
|
ESTACK_CHECK_NOW;
|
|
estack_pop();
|
|
vim_free(autocmd_fname);
|
|
autocmd_fname = save_autocmd_fname;
|
|
autocmd_fname_full = save_autocmd_fname_full;
|
|
autocmd_bufnr = save_autocmd_bufnr;
|
|
autocmd_match = save_autocmd_match;
|
|
current_sctx = save_current_sctx;
|
|
#ifdef FEAT_EVAL
|
|
restore_funccal();
|
|
# ifdef FEAT_PROFILE
|
|
if (do_profiling == PROF_YES)
|
|
prof_child_exit(&wait_time);
|
|
# endif
|
|
#endif
|
|
KeyTyped = save_KeyTyped;
|
|
vim_free(fname);
|
|
vim_free(sfname);
|
|
--nesting; // see matching increment above
|
|
|
|
/*
|
|
* When stopping to execute autocommands, restore the search patterns and
|
|
* the redo buffer. Free any buffers in the au_pending_free_buf list and
|
|
* free any windows in the au_pending_free_win list.
|
|
*/
|
|
if (!autocmd_busy)
|
|
{
|
|
restore_search_patterns();
|
|
if (did_save_redobuff)
|
|
restoreRedobuff(&save_redo);
|
|
curbuf->b_did_filetype = FALSE;
|
|
while (au_pending_free_buf != NULL)
|
|
{
|
|
buf_T *b = au_pending_free_buf->b_next;
|
|
|
|
vim_free(au_pending_free_buf);
|
|
au_pending_free_buf = b;
|
|
}
|
|
while (au_pending_free_win != NULL)
|
|
{
|
|
win_T *w = au_pending_free_win->w_next;
|
|
|
|
vim_free(au_pending_free_win);
|
|
au_pending_free_win = w;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Some events don't set or reset the Changed flag.
|
|
* Check if still in the same buffer!
|
|
*/
|
|
if (curbuf == old_curbuf
|
|
&& (event == EVENT_BUFREADPOST
|
|
|| event == EVENT_BUFWRITEPOST
|
|
|| event == EVENT_FILEAPPENDPOST
|
|
|| event == EVENT_VIMLEAVE
|
|
|| event == EVENT_VIMLEAVEPRE))
|
|
{
|
|
if (curbuf->b_changed != save_changed)
|
|
need_maketitle = TRUE;
|
|
curbuf->b_changed = save_changed;
|
|
}
|
|
|
|
au_cleanup(); // may really delete removed patterns/commands now
|
|
|
|
BYPASS_AU:
|
|
// When wiping out a buffer make sure all its buffer-local autocommands
|
|
// are deleted.
|
|
if (event == EVENT_BUFWIPEOUT && buf != NULL)
|
|
aubuflocal_remove(buf);
|
|
|
|
if (retval == OK && event == EVENT_FILETYPE)
|
|
curbuf->b_au_did_filetype = TRUE;
|
|
|
|
return retval;
|
|
}
|
|
|
|
# ifdef FEAT_EVAL
|
|
static char_u *old_termresponse = NULL;
|
|
static char_u *old_termu7resp = NULL;
|
|
static char_u *old_termblinkresp = NULL;
|
|
static char_u *old_termrbgresp = NULL;
|
|
static char_u *old_termrfgresp = NULL;
|
|
static char_u *old_termstyleresp = 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);
|
|
old_termu7resp = get_vim_var_str(VV_TERMU7RESP);
|
|
old_termblinkresp = get_vim_var_str(VV_TERMBLINKRESP);
|
|
old_termrbgresp = get_vim_var_str(VV_TERMRBGRESP);
|
|
old_termrfgresp = get_vim_var_str(VV_TERMRFGRESP);
|
|
old_termstyleresp = get_vim_var_str(VV_TERMSTYLERESP);
|
|
}
|
|
# endif
|
|
++autocmd_blocked;
|
|
}
|
|
|
|
void
|
|
unblock_autocmds(void)
|
|
{
|
|
--autocmd_blocked;
|
|
|
|
# ifdef FEAT_EVAL
|
|
// When v:termresponse, etc, were set while autocommands were blocked,
|
|
// trigger the autocommands now. Esp. useful when executing a shell
|
|
// command during startup (vimdiff).
|
|
if (autocmd_blocked == 0)
|
|
{
|
|
if (get_vim_var_str(VV_TERMRESPONSE) != old_termresponse)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf);
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"version", NULL, FALSE, curbuf);
|
|
}
|
|
if (get_vim_var_str(VV_TERMU7RESP) != old_termu7resp)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"ambiguouswidth", NULL, FALSE, curbuf);
|
|
}
|
|
if (get_vim_var_str(VV_TERMBLINKRESP) != old_termblinkresp)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"cursorblink", NULL, FALSE, curbuf);
|
|
}
|
|
if (get_vim_var_str(VV_TERMRBGRESP) != old_termrbgresp)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"background", NULL, FALSE, curbuf);
|
|
}
|
|
if (get_vim_var_str(VV_TERMRFGRESP) != old_termrfgresp)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"foreground", NULL, FALSE, curbuf);
|
|
}
|
|
if (get_vim_var_str(VV_TERMSTYLERESP) != old_termstyleresp)
|
|
{
|
|
apply_autocmds(EVENT_TERMRESPONSEALL, (char_u *)"cursorshape", NULL, FALSE, curbuf);
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
int
|
|
is_autocmd_blocked(void)
|
|
{
|
|
return autocmd_blocked != 0;
|
|
}
|
|
|
|
/*
|
|
* Find next autocommand pattern that matches.
|
|
*/
|
|
static void
|
|
auto_next_pat(
|
|
AutoPatCmd_T *apc,
|
|
int stop_at_last) // stop when 'last' flag is set
|
|
{
|
|
AutoPat *ap;
|
|
AutoCmd *cp;
|
|
char_u *name;
|
|
char *s;
|
|
estack_T *entry;
|
|
char_u *namep;
|
|
|
|
entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
|
|
|
|
// Clear the exestack entry for this ETYPE_AUCMD entry.
|
|
VIM_CLEAR(entry->es_name);
|
|
entry->es_info.aucmd = NULL;
|
|
|
|
for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next)
|
|
{
|
|
apc->curpat = NULL;
|
|
|
|
// Only use a pattern when it has not been removed, has commands and
|
|
// the group matches. For buffer-local autocommands only check the
|
|
// buffer number.
|
|
if (ap->pat != NULL && ap->cmds != NULL
|
|
&& (apc->group == AUGROUP_ALL || apc->group == ap->group))
|
|
{
|
|
// execution-condition
|
|
if (ap->buflocal_nr == 0
|
|
? (match_file_pat(NULL, &ap->reg_prog, apc->fname,
|
|
apc->sfname, apc->tail, ap->allow_dirs))
|
|
: ap->buflocal_nr == apc->arg_bufnr)
|
|
{
|
|
name = event_nr2name(apc->event);
|
|
s = _("%s Autocommands for \"%s\"");
|
|
namep = alloc(STRLEN(s) + STRLEN(name) + ap->patlen + 1);
|
|
if (namep != NULL)
|
|
{
|
|
sprintf((char *)namep, s, (char *)name, (char *)ap->pat);
|
|
if (p_verbose >= 8)
|
|
{
|
|
verbose_enter();
|
|
smsg(_("Executing %s"), namep);
|
|
verbose_leave();
|
|
}
|
|
}
|
|
|
|
// Update the exestack entry for this autocmd.
|
|
entry->es_name = namep;
|
|
entry->es_info.aucmd = apc;
|
|
|
|
apc->curpat = ap;
|
|
apc->nextcmd = ap->cmds;
|
|
// mark last command
|
|
for (cp = ap->cmds; cp->next != NULL; cp = cp->next)
|
|
cp->last = FALSE;
|
|
cp->last = TRUE;
|
|
}
|
|
line_breakcheck();
|
|
if (apc->curpat != NULL) // found a match
|
|
break;
|
|
}
|
|
if (stop_at_last && ap->last)
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Get the script context where autocommand "acp" is defined.
|
|
*/
|
|
sctx_T *
|
|
acp_script_ctx(AutoPatCmd_T *acp)
|
|
{
|
|
return &acp->script_ctx;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Get next autocommand command.
|
|
* Called by do_cmdline() to get the next line for ":if".
|
|
* Returns allocated string, or NULL for end of autocommands.
|
|
*/
|
|
char_u *
|
|
getnextac(
|
|
int c UNUSED,
|
|
void *cookie,
|
|
int indent UNUSED,
|
|
getline_opt_T options UNUSED)
|
|
{
|
|
AutoPatCmd_T *acp = (AutoPatCmd_T *)cookie;
|
|
char_u *retval;
|
|
AutoCmd *ac;
|
|
|
|
// Can be called again after returning the last line.
|
|
if (acp->curpat == NULL)
|
|
return NULL;
|
|
|
|
// repeat until we find an autocommand to execute
|
|
for (;;)
|
|
{
|
|
// skip removed commands
|
|
while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL)
|
|
if (acp->nextcmd->last)
|
|
acp->nextcmd = NULL;
|
|
else
|
|
acp->nextcmd = acp->nextcmd->next;
|
|
|
|
if (acp->nextcmd != NULL)
|
|
break;
|
|
|
|
// at end of commands, find next pattern that matches
|
|
if (acp->curpat->last)
|
|
acp->curpat = NULL;
|
|
else
|
|
acp->curpat = acp->curpat->next;
|
|
if (acp->curpat != NULL)
|
|
auto_next_pat(acp, TRUE);
|
|
if (acp->curpat == NULL)
|
|
return NULL;
|
|
}
|
|
|
|
ac = acp->nextcmd;
|
|
|
|
if (p_verbose >= 9)
|
|
{
|
|
verbose_enter_scroll();
|
|
smsg(_("autocommand %s"), ac->cmd);
|
|
msg_puts("\n"); // don't overwrite this either
|
|
verbose_leave_scroll();
|
|
}
|
|
retval = vim_strsave(ac->cmd);
|
|
// Remove one-shot ("once") autocmd in anticipation of its execution.
|
|
if (ac->once)
|
|
au_del_cmd(ac);
|
|
autocmd_nested = ac->nested;
|
|
current_sctx = ac->script_ctx;
|
|
acp->script_ctx = current_sctx;
|
|
if (ac->last)
|
|
acp->nextcmd = NULL;
|
|
else
|
|
acp->nextcmd = ac->next;
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if there is a matching autocommand for "fname".
|
|
* To account for buffer-local autocommands, function needs to know
|
|
* in which buffer the file will be opened.
|
|
*/
|
|
int
|
|
has_autocmd(event_T event, char_u *sfname, buf_T *buf)
|
|
{
|
|
AutoPat *ap;
|
|
char_u *fname;
|
|
char_u *tail = gettail(sfname);
|
|
int retval = FALSE;
|
|
|
|
fname = FullName_save(sfname, FALSE);
|
|
if (fname == NULL)
|
|
return FALSE;
|
|
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
/*
|
|
* Replace all backslashes with forward slashes. This makes the
|
|
* autocommand patterns portable between Unix and MS-DOS.
|
|
*/
|
|
sfname = vim_strsave(sfname);
|
|
if (sfname != NULL)
|
|
forward_slash(sfname);
|
|
forward_slash(fname);
|
|
#endif
|
|
|
|
FOR_ALL_AUTOCMD_PATTERNS(event, ap)
|
|
if (ap->pat != NULL && ap->cmds != NULL
|
|
&& (ap->buflocal_nr == 0
|
|
? match_file_pat(NULL, &ap->reg_prog,
|
|
fname, sfname, tail, ap->allow_dirs)
|
|
: buf != NULL && ap->buflocal_nr == buf->b_fnum
|
|
))
|
|
{
|
|
retval = TRUE;
|
|
break;
|
|
}
|
|
|
|
vim_free(fname);
|
|
#ifdef BACKSLASH_IN_FILENAME
|
|
vim_free(sfname);
|
|
#endif
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Function given to ExpandGeneric() to obtain the list of autocommand group
|
|
* names.
|
|
*/
|
|
char_u *
|
|
get_augroup_name(expand_T *xp UNUSED, int idx)
|
|
{
|
|
if (idx == augroups.ga_len) // add "END" add the end
|
|
return (char_u *)"END";
|
|
if (idx < 0 || idx >= augroups.ga_len) // end of list
|
|
return NULL;
|
|
if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup())
|
|
// skip deleted entries
|
|
return (char_u *)"";
|
|
return AUGROUP_NAME(idx); // return a name
|
|
}
|
|
|
|
static int include_groups = FALSE;
|
|
|
|
char_u *
|
|
set_context_in_autocmd(
|
|
expand_T *xp,
|
|
char_u *arg,
|
|
int doautocmd) // TRUE for :doauto*, FALSE for :autocmd
|
|
{
|
|
char_u *p;
|
|
int group;
|
|
|
|
// check for a group name, skip it if present
|
|
include_groups = FALSE;
|
|
p = arg;
|
|
group = au_get_grouparg(&arg);
|
|
if (group == AUGROUP_ERROR)
|
|
return NULL;
|
|
// If there only is a group name that's what we expand.
|
|
if (*arg == NUL && group != AUGROUP_ALL && !VIM_ISWHITE(arg[-1]))
|
|
{
|
|
arg = p;
|
|
group = AUGROUP_ALL;
|
|
}
|
|
|
|
// skip over event name
|
|
for (p = arg; *p != NUL && !VIM_ISWHITE(*p); ++p)
|
|
if (*p == ',')
|
|
arg = p + 1;
|
|
if (*p == NUL)
|
|
{
|
|
if (group == AUGROUP_ALL)
|
|
include_groups = TRUE;
|
|
xp->xp_context = EXPAND_EVENTS; // expand event name
|
|
xp->xp_pattern = arg;
|
|
return NULL;
|
|
}
|
|
|
|
// skip over pattern
|
|
arg = skipwhite(p);
|
|
while (*arg && (!VIM_ISWHITE(*arg) || arg[-1] == '\\'))
|
|
arg++;
|
|
if (*arg)
|
|
return arg; // expand (next) command
|
|
|
|
if (doautocmd)
|
|
xp->xp_context = EXPAND_FILES; // expand file names
|
|
else
|
|
xp->xp_context = EXPAND_NOTHING; // pattern is not expanded
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Function given to ExpandGeneric() to obtain the list of event names.
|
|
*/
|
|
char_u *
|
|
get_event_name(expand_T *xp UNUSED, int idx)
|
|
{
|
|
int i;
|
|
|
|
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
|
|
}
|
|
|
|
i = idx - augroups.ga_len;
|
|
if (i < 0 || i >= NUM_EVENTS)
|
|
return NULL;
|
|
|
|
return event_tab[i].value.string;
|
|
}
|
|
|
|
/*
|
|
* Function given to ExpandGeneric() to obtain the list of event names. Don't
|
|
* include groups.
|
|
*/
|
|
char_u *
|
|
get_event_name_no_group(expand_T *xp UNUSED, int idx, int win)
|
|
{
|
|
if (idx < 0 || idx >= NUM_EVENTS)
|
|
return NULL;
|
|
|
|
if (!win)
|
|
return event_tab[idx].value.string;
|
|
|
|
// Need to check subset of allowed values for 'eventignorewin'.
|
|
int j = 0;
|
|
for (int i = 0; i < NUM_EVENTS; ++i)
|
|
{
|
|
j += event_tab[i].key <= 0;
|
|
if (j == idx + 1)
|
|
return event_tab[i].value.string;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE when there is a TabClosedPre autocommand defined.
|
|
*/
|
|
int
|
|
has_tabclosedpre(void)
|
|
{
|
|
return (first_autopat[(int)EVENT_TABCLOSEDPRE] != NULL);
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* autocmd_add() and autocmd_delete() functions
|
|
*/
|
|
static void
|
|
autocmd_add_or_delete(typval_T *argvars, typval_T *rettv, int delete)
|
|
{
|
|
list_T *aucmd_list;
|
|
listitem_T *li;
|
|
dict_T *event_dict;
|
|
dictitem_T *di;
|
|
char_u *event_name = NULL;
|
|
list_T *event_list;
|
|
listitem_T *eli;
|
|
event_T event;
|
|
char_u *group_name = NULL;
|
|
int group;
|
|
char_u *pat = NULL;
|
|
list_T *pat_list;
|
|
listitem_T *pli;
|
|
char_u *cmd = NULL;
|
|
char_u *end;
|
|
int once;
|
|
int nested;
|
|
int replace; // replace the cmd for a group/event
|
|
int retval = VVAL_TRUE;
|
|
int save_augroup = current_augroup;
|
|
|
|
rettv->v_type = VAR_BOOL;
|
|
rettv->vval.v_number = VVAL_FALSE;
|
|
|
|
if (check_for_list_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
aucmd_list = argvars[0].vval.v_list;
|
|
if (aucmd_list == NULL)
|
|
return;
|
|
|
|
FOR_ALL_LIST_ITEMS(aucmd_list, li)
|
|
{
|
|
VIM_CLEAR(group_name);
|
|
VIM_CLEAR(cmd);
|
|
event_name = NULL;
|
|
event_list = NULL;
|
|
pat = NULL;
|
|
pat_list = NULL;
|
|
|
|
if (li->li_tv.v_type != VAR_DICT)
|
|
continue;
|
|
|
|
event_dict = li->li_tv.vval.v_dict;
|
|
if (event_dict == NULL)
|
|
continue;
|
|
|
|
di = dict_find(event_dict, (char_u *)"event", -1);
|
|
if (di != NULL)
|
|
{
|
|
if (di->di_tv.v_type == VAR_STRING)
|
|
{
|
|
event_name = di->di_tv.vval.v_string;
|
|
if (event_name == NULL)
|
|
{
|
|
emsg(_(e_string_required));
|
|
continue;
|
|
}
|
|
}
|
|
else if (di->di_tv.v_type == VAR_LIST)
|
|
{
|
|
event_list = di->di_tv.vval.v_list;
|
|
if (event_list == NULL)
|
|
{
|
|
emsg(_(e_list_required));
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
emsg(_(e_string_or_list_expected));
|
|
continue;
|
|
}
|
|
}
|
|
|
|
group_name = dict_get_string(event_dict, "group", TRUE);
|
|
if (group_name == NULL || *group_name == NUL)
|
|
// if the autocmd group name is not specified, then use the current
|
|
// autocmd group
|
|
group = current_augroup;
|
|
else
|
|
{
|
|
group = au_find_group(group_name);
|
|
if (group == AUGROUP_ERROR)
|
|
{
|
|
if (delete)
|
|
{
|
|
semsg(_(e_no_such_group_str), group_name);
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
// group is not found, create it now
|
|
group = au_new_group(group_name);
|
|
if (group == AUGROUP_ERROR)
|
|
{
|
|
semsg(_(e_no_such_group_str), group_name);
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
|
|
current_augroup = group;
|
|
}
|
|
}
|
|
|
|
// if a buffer number is specified, then generate a pattern of the form
|
|
// "<buffer=n>. Otherwise, use the pattern supplied by the user.
|
|
if (dict_has_key(event_dict, "bufnr"))
|
|
{
|
|
varnumber_T bnum;
|
|
|
|
bnum = dict_get_number_def(event_dict, "bufnr", -1);
|
|
if (bnum == -1)
|
|
continue;
|
|
|
|
vim_snprintf((char *)IObuff, IOSIZE, "<buffer=%d>", (int)bnum);
|
|
pat = IObuff;
|
|
}
|
|
else
|
|
{
|
|
di = dict_find(event_dict, (char_u *)"pattern", -1);
|
|
if (di != NULL)
|
|
{
|
|
if (di->di_tv.v_type == VAR_STRING)
|
|
{
|
|
pat = di->di_tv.vval.v_string;
|
|
if (pat == NULL)
|
|
{
|
|
emsg(_(e_string_required));
|
|
continue;
|
|
}
|
|
}
|
|
else if (di->di_tv.v_type == VAR_LIST)
|
|
{
|
|
pat_list = di->di_tv.vval.v_list;
|
|
if (pat_list == NULL)
|
|
{
|
|
emsg(_(e_list_required));
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
emsg(_(e_string_or_list_expected));
|
|
continue;
|
|
}
|
|
}
|
|
else if (delete)
|
|
pat = (char_u *)"";
|
|
}
|
|
|
|
once = dict_get_bool(event_dict, "once", FALSE);
|
|
nested = dict_get_bool(event_dict, "nested", FALSE);
|
|
// if 'replace' is true, then remove all the commands associated with
|
|
// this autocmd event/group and add the new command.
|
|
replace = dict_get_bool(event_dict, "replace", FALSE);
|
|
|
|
cmd = dict_get_string(event_dict, "cmd", TRUE);
|
|
if (cmd == NULL)
|
|
{
|
|
if (delete)
|
|
cmd = vim_strsave((char_u *)"");
|
|
else
|
|
continue;
|
|
}
|
|
|
|
if (delete && (event_name == NULL
|
|
|| (event_name[0] == '*' && event_name[1] == NUL)))
|
|
{
|
|
// if the event name is not specified or '*', delete all the events
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
{
|
|
if (do_autocmd_event(event, pat, once, nested, cmd, delete,
|
|
group, 0) == FAIL)
|
|
{
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char_u *p = NULL;
|
|
|
|
eli = NULL;
|
|
end = NULL;
|
|
while (TRUE)
|
|
{
|
|
if (event_list != NULL)
|
|
{
|
|
if (eli == NULL)
|
|
eli = event_list->lv_first;
|
|
else
|
|
eli = eli->li_next;
|
|
if (eli == NULL)
|
|
break;
|
|
if (eli->li_tv.v_type != VAR_STRING
|
|
|| (p = eli->li_tv.vval.v_string) == NULL)
|
|
{
|
|
emsg(_(e_string_required));
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (p == NULL)
|
|
p = event_name;
|
|
if (p == NULL || *p == NUL)
|
|
break;
|
|
}
|
|
|
|
event = event_name2nr(p, &end);
|
|
if (event == NUM_EVENTS || *end != NUL)
|
|
{
|
|
// this also catches something following a valid event name
|
|
semsg(_(e_no_such_event_str), p);
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
if (pat != NULL)
|
|
{
|
|
if (do_autocmd_event(event, pat, once, nested, cmd,
|
|
delete | replace, group, 0) == FAIL)
|
|
{
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
else if (pat_list != NULL)
|
|
{
|
|
FOR_ALL_LIST_ITEMS(pat_list, pli)
|
|
{
|
|
if (pli->li_tv.v_type != VAR_STRING
|
|
|| pli->li_tv.vval.v_string == NULL)
|
|
{
|
|
emsg(_(e_string_required));
|
|
continue;
|
|
}
|
|
if (do_autocmd_event(event,
|
|
pli->li_tv.vval.v_string, once, nested,
|
|
cmd, delete | replace, group, 0) ==
|
|
FAIL)
|
|
{
|
|
retval = VVAL_FALSE;
|
|
break;
|
|
}
|
|
}
|
|
if (retval == VVAL_FALSE)
|
|
break;
|
|
}
|
|
if (event_name != NULL)
|
|
p = end;
|
|
}
|
|
}
|
|
|
|
// if only the autocmd group name is specified for delete and the
|
|
// autocmd event, pattern and cmd are not specified, then delete the
|
|
// autocmd group.
|
|
if (delete && group_name != NULL &&
|
|
(event_name == NULL || event_name[0] == NUL)
|
|
&& (pat == NULL || pat[0] == NUL)
|
|
&& (cmd == NULL || cmd[0] == NUL))
|
|
au_del_group(group_name);
|
|
}
|
|
|
|
VIM_CLEAR(group_name);
|
|
VIM_CLEAR(cmd);
|
|
|
|
current_augroup = save_augroup;
|
|
rettv->vval.v_number = retval;
|
|
}
|
|
|
|
/*
|
|
* autocmd_add() function
|
|
*/
|
|
void
|
|
f_autocmd_add(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
autocmd_add_or_delete(argvars, rettv, FALSE);
|
|
}
|
|
|
|
/*
|
|
* autocmd_delete() function
|
|
*/
|
|
void
|
|
f_autocmd_delete(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
autocmd_add_or_delete(argvars, rettv, TRUE);
|
|
}
|
|
|
|
/*
|
|
* autocmd_get() function
|
|
* Returns a List of autocmds.
|
|
*/
|
|
void
|
|
f_autocmd_get(typval_T *argvars, typval_T *rettv)
|
|
{
|
|
event_T event_arg = NUM_EVENTS;
|
|
event_T event;
|
|
AutoPat *ap;
|
|
AutoCmd *ac;
|
|
list_T *event_list;
|
|
dict_T *event_dict;
|
|
char_u *event_name = NULL;
|
|
char_u *pat = NULL;
|
|
char_u *name = NULL;
|
|
int group = AUGROUP_ALL;
|
|
|
|
if (rettv_list_alloc(rettv) == FAIL)
|
|
return;
|
|
if (check_for_opt_dict_arg(argvars, 0) == FAIL)
|
|
return;
|
|
|
|
if (argvars[0].v_type == VAR_DICT)
|
|
{
|
|
// return only the autocmds in the specified group
|
|
if (dict_has_key(argvars[0].vval.v_dict, "group"))
|
|
{
|
|
name = dict_get_string(argvars[0].vval.v_dict, "group", TRUE);
|
|
if (name == NULL)
|
|
return;
|
|
|
|
if (*name == NUL)
|
|
group = AUGROUP_DEFAULT;
|
|
else
|
|
{
|
|
group = au_find_group(name);
|
|
if (group == AUGROUP_ERROR)
|
|
{
|
|
semsg(_(e_no_such_group_str), name);
|
|
vim_free(name);
|
|
return;
|
|
}
|
|
}
|
|
vim_free(name);
|
|
}
|
|
|
|
// return only the autocmds for the specified event
|
|
if (dict_has_key(argvars[0].vval.v_dict, "event"))
|
|
{
|
|
name = dict_get_string(argvars[0].vval.v_dict, "event", TRUE);
|
|
if (name == NULL)
|
|
return;
|
|
|
|
if (name[0] == '*' && name[1] == NUL)
|
|
event_arg = NUM_EVENTS;
|
|
else
|
|
{
|
|
keyvalue_T target;
|
|
keyvalue_T *entry;
|
|
|
|
target.key = 0;
|
|
target.value.string = name;
|
|
target.value.length = STRLEN(target.value.string);
|
|
entry = (keyvalue_T *)bsearch(&target, &event_tab,
|
|
NUM_EVENTS, sizeof(event_tab[0]), cmp_keyvalue_value_ni);
|
|
if (entry == NULL)
|
|
{
|
|
semsg(_(e_no_such_event_str), name);
|
|
vim_free(name);
|
|
return;
|
|
}
|
|
event_arg = (event_T)abs(entry->key);
|
|
}
|
|
vim_free(name);
|
|
}
|
|
|
|
// return only the autocmds for the specified pattern
|
|
if (dict_has_key(argvars[0].vval.v_dict, "pattern"))
|
|
{
|
|
pat = dict_get_string(argvars[0].vval.v_dict, "pattern", TRUE);
|
|
if (pat == NULL)
|
|
return;
|
|
}
|
|
}
|
|
|
|
event_list = rettv->vval.v_list;
|
|
|
|
// iterate through all the autocmd events
|
|
for (event = (event_T)0; (int)event < NUM_EVENTS;
|
|
event = (event_T)((int)event + 1))
|
|
{
|
|
if (event_arg != NUM_EVENTS && event != event_arg)
|
|
continue;
|
|
|
|
event_name = event_nr2name(event);
|
|
|
|
// iterate through all the patterns for this autocmd event
|
|
FOR_ALL_AUTOCMD_PATTERNS(event, ap)
|
|
{
|
|
char_u *group_name;
|
|
|
|
if (ap->pat == NULL) // pattern has been removed
|
|
continue;
|
|
|
|
if (group != AUGROUP_ALL && group != ap->group)
|
|
continue;
|
|
|
|
if (pat != NULL && STRCMP(pat, ap->pat) != 0)
|
|
continue;
|
|
|
|
group_name = get_augroup_name(NULL, ap->group);
|
|
|
|
// iterate through all the commands for this pattern and add one
|
|
// item for each cmd.
|
|
for (ac = ap->cmds; ac != NULL; ac = ac->next)
|
|
{
|
|
event_dict = dict_alloc();
|
|
if (event_dict == NULL
|
|
|| list_append_dict(event_list, event_dict) == FAIL)
|
|
{
|
|
vim_free(pat);
|
|
return;
|
|
}
|
|
|
|
if (dict_add_string(event_dict, "event", event_name) == FAIL
|
|
|| dict_add_string(event_dict, "group",
|
|
group_name == NULL ? (char_u *)""
|
|
: group_name) == FAIL
|
|
|| (ap->buflocal_nr != 0
|
|
&& (dict_add_number(event_dict, "bufnr",
|
|
ap->buflocal_nr) == FAIL))
|
|
|| dict_add_string(event_dict, "pattern",
|
|
ap->pat) == FAIL
|
|
|| dict_add_string(event_dict, "cmd", ac->cmd) == FAIL
|
|
|| dict_add_bool(event_dict, "once", ac->once) == FAIL
|
|
|| dict_add_bool(event_dict, "nested",
|
|
ac->nested) == FAIL)
|
|
{
|
|
vim_free(pat);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
vim_free(pat);
|
|
}
|
|
|
|
#endif
|