Ai
12 Star 47 Fork 0

Gitee 极速下载/Vim

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
此仓库是为了提升国内下载速度的镜像仓库,每日同步一次。 原始仓库: https://github.com/vim/vim
克隆/下载
usercmd.c 46.71 KB
一键复制 编辑 原始数据 按行查看 历史
Yegappan Lakshmanan 提交于 2025-08-16 22:26 +08:00 . patch 9.1.1636: style issues
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100
/* 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.
*/
/*
* usercmd.c: User defined command support
*/
#include "vim.h"
typedef struct ucmd
{
char_u *uc_name; // The command name
size_t uc_namelen; // The length of the command name (excluding the NUL)
long_u uc_argt; // The argument type
char_u *uc_rep; // The command's replacement string
long uc_def; // The default value for a range/count
int uc_compl; // completion type
cmd_addr_T uc_addr_type; // The command's address type
sctx_T uc_script_ctx; // SCTX where the command was defined
int uc_flags; // some UC_ flags
# ifdef FEAT_EVAL
char_u *uc_compl_arg; // completion argument if any
# endif
} ucmd_T;
// List of all user commands.
static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
// When non-zero it is not allowed to add or remove user commands
static int ucmd_locked = 0;
#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
/*
* List of names for completion for ":command" with the EXPAND_ flag.
* Must be alphabetical on the 'value' field for completion and because
* it is used by bsearch()!
*/
static keyvalue_T command_complete_tab[] =
{
KEYVALUE_ENTRY(EXPAND_ARGLIST, "arglist"),
KEYVALUE_ENTRY(EXPAND_AUGROUP, "augroup"),
KEYVALUE_ENTRY(EXPAND_BEHAVE, "behave"),
#if defined(FEAT_EVAL)
KEYVALUE_ENTRY(EXPAND_BREAKPOINT, "breakpoint"),
#endif
KEYVALUE_ENTRY(EXPAND_BUFFERS, "buffer"),
KEYVALUE_ENTRY(EXPAND_COLORS, "color"),
KEYVALUE_ENTRY(EXPAND_COMMANDS, "command"),
KEYVALUE_ENTRY(EXPAND_COMPILER, "compiler"),
#if defined(FEAT_CSCOPE)
KEYVALUE_ENTRY(EXPAND_CSCOPE, "cscope"),
#endif
#if defined(FEAT_EVAL)
KEYVALUE_ENTRY(EXPAND_USER_DEFINED, "custom"),
KEYVALUE_ENTRY(EXPAND_USER_LIST, "customlist"),
#endif
KEYVALUE_ENTRY(EXPAND_DIFF_BUFFERS, "diff_buffer"),
KEYVALUE_ENTRY(EXPAND_DIRECTORIES, "dir"),
KEYVALUE_ENTRY(EXPAND_DIRS_IN_CDPATH, "dir_in_path"),
KEYVALUE_ENTRY(EXPAND_ENV_VARS, "environment"),
KEYVALUE_ENTRY(EXPAND_EVENTS, "event"),
KEYVALUE_ENTRY(EXPAND_EXPRESSION, "expression"),
KEYVALUE_ENTRY(EXPAND_FILES, "file"),
KEYVALUE_ENTRY(EXPAND_FILES_IN_PATH, "file_in_path"),
KEYVALUE_ENTRY(EXPAND_FILETYPE, "filetype"),
KEYVALUE_ENTRY(EXPAND_FILETYPECMD, "filetypecmd"),
KEYVALUE_ENTRY(EXPAND_FUNCTIONS, "function"),
KEYVALUE_ENTRY(EXPAND_HELP, "help"),
KEYVALUE_ENTRY(EXPAND_HIGHLIGHT, "highlight"),
KEYVALUE_ENTRY(EXPAND_HISTORY, "history"),
#if defined(FEAT_KEYMAP)
KEYVALUE_ENTRY(EXPAND_KEYMAP, "keymap"),
#endif
#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
KEYVALUE_ENTRY(EXPAND_LOCALES, "locale"),
#endif
KEYVALUE_ENTRY(EXPAND_MAPCLEAR, "mapclear"),
KEYVALUE_ENTRY(EXPAND_MAPPINGS, "mapping"),
KEYVALUE_ENTRY(EXPAND_MENUS, "menu"),
KEYVALUE_ENTRY(EXPAND_MESSAGES, "messages"),
KEYVALUE_ENTRY(EXPAND_SETTINGS, "option"),
KEYVALUE_ENTRY(EXPAND_PACKADD, "packadd"),
KEYVALUE_ENTRY(EXPAND_RETAB, "retab"),
KEYVALUE_ENTRY(EXPAND_RUNTIME, "runtime"),
#if defined(FEAT_EVAL)
KEYVALUE_ENTRY(EXPAND_SCRIPTNAMES, "scriptnames"),
#endif
KEYVALUE_ENTRY(EXPAND_SHELLCMD, "shellcmd"),
KEYVALUE_ENTRY(EXPAND_SHELLCMDLINE, "shellcmdline"),
#if defined(FEAT_SIGNS)
KEYVALUE_ENTRY(EXPAND_SIGN, "sign"),
#endif
KEYVALUE_ENTRY(EXPAND_OWNSYNTAX, "syntax"),
#if defined(FEAT_PROFILE)
KEYVALUE_ENTRY(EXPAND_SYNTIME, "syntime"),
#endif
KEYVALUE_ENTRY(EXPAND_TAGS, "tag"),
KEYVALUE_ENTRY(EXPAND_TAGS_LISTFILES, "tag_listfiles"),
KEYVALUE_ENTRY(EXPAND_USER, "user"),
KEYVALUE_ENTRY(EXPAND_USER_VARS, "var")
};
typedef struct
{
cmd_addr_T key;
char *fullname;
size_t fullnamelen;
char *shortname;
size_t shortnamelen;
} addrtype_T;
/*
* List of names of address types. Must be alphabetical for completion.
* Must be sorted by the 'fullname' field because it is used by bsearch()!
*/
#define ADDRTYPE_ENTRY(k, fn, sn) \
{(k), (fn), STRLEN_LITERAL(fn), (sn), STRLEN_LITERAL(sn)}
static addrtype_T addr_type_complete_tab[] =
{
ADDRTYPE_ENTRY(ADDR_ARGUMENTS, "arguments", "arg"),
ADDRTYPE_ENTRY(ADDR_BUFFERS, "buffers", "buf"),
ADDRTYPE_ENTRY(ADDR_LINES, "lines", "line"),
ADDRTYPE_ENTRY(ADDR_LOADED_BUFFERS, "loaded_buffers", "load"),
ADDRTYPE_ENTRY(ADDR_OTHER, "other", "?"),
ADDRTYPE_ENTRY(ADDR_QUICKFIX, "quickfix", "qf"),
ADDRTYPE_ENTRY(ADDR_TABS, "tabs", "tab"),
ADDRTYPE_ENTRY(ADDR_WINDOWS, "windows", "win")
};
static int cmp_addr_type(const void *a, const void *b);
/*
* Search for a user command that matches "eap->cmd".
* Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
* Return a pointer to just after the command.
* Return NULL if there is no matching command.
*/
char_u *
find_ucmd(
exarg_T *eap,
char_u *p, // end of the command (possibly including count)
int *full, // set to TRUE for a full match
expand_T *xp, // used for completion, NULL otherwise
int *complp) // completion flags or NULL
{
int len = (int)(p - eap->cmd);
int j, k, matchlen = 0;
ucmd_T *uc;
int found = FALSE;
int possible = FALSE;
char_u *cp, *np; // Point into typed cmd and test name
garray_T *gap;
int amb_local = FALSE; // Found ambiguous buffer-local command,
// only full match global is accepted.
/*
* Look for buffer-local user commands first, then global ones.
*/
gap = &prevwin_curwin()->w_buffer->b_ucmds;
for (;;)
{
for (j = 0; j < gap->ga_len; ++j)
{
uc = USER_CMD_GA(gap, j);
cp = eap->cmd;
np = uc->uc_name;
k = 0;
while (k < len && *np != NUL && *cp++ == *np++)
k++;
if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
{
// If finding a second match, the command is ambiguous. But
// not if a buffer-local command wasn't a full match and a
// global command is a full match.
if (k == len && found && *np != NUL)
{
if (gap == &ucmds)
return NULL;
amb_local = TRUE;
}
if (!found || (k == len && *np == NUL))
{
// If we matched up to a digit, then there could
// be another command including the digit that we
// should use instead.
if (k == len)
found = TRUE;
else
possible = TRUE;
if (gap == &ucmds)
eap->cmdidx = CMD_USER;
else
eap->cmdidx = CMD_USER_BUF;
eap->argt = (long)uc->uc_argt;
eap->useridx = j;
eap->addr_type = uc->uc_addr_type;
if (complp != NULL)
*complp = uc->uc_compl;
# ifdef FEAT_EVAL
if (xp != NULL)
{
xp->xp_arg = uc->uc_compl_arg;
xp->xp_script_ctx = uc->uc_script_ctx;
xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
}
# endif
// Do not search for further abbreviations
// if this is an exact match.
matchlen = k;
if (k == len && *np == NUL)
{
if (full != NULL)
*full = TRUE;
amb_local = FALSE;
break;
}
}
}
}
// Stop if we found a full match or searched all.
if (j < gap->ga_len || gap == &ucmds)
break;
gap = &ucmds;
}
// Only found ambiguous matches.
if (amb_local)
{
if (xp != NULL)
xp->xp_context = EXPAND_UNSUCCESSFUL;
return NULL;
}
// The match we found may be followed immediately by a number. Move "p"
// back to point to it.
if (found || possible)
return p + (matchlen - len);
return p;
}
/*
* Set completion context for :command
*/
char_u *
set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
{
char_u *arg = arg_in;
char_u *p;
// Check for attributes
while (*arg == '-')
{
arg++; // Skip "-"
p = skiptowhite(arg);
if (*p == NUL)
{
// Cursor is still in the attribute
p = vim_strchr(arg, '=');
if (p == NULL)
{
// No "=", so complete attribute names
xp->xp_context = EXPAND_USER_CMD_FLAGS;
xp->xp_pattern = arg;
return NULL;
}
// For the -complete, -nargs and -addr attributes, we complete
// their arguments as well.
if (STRNICMP(arg, "complete", p - arg) == 0)
{
xp->xp_context = EXPAND_USER_COMPLETE;
xp->xp_pattern = p + 1;
}
else if (STRNICMP(arg, "nargs", p - arg) == 0)
{
xp->xp_context = EXPAND_USER_NARGS;
xp->xp_pattern = p + 1;
}
else if (STRNICMP(arg, "addr", p - arg) == 0)
{
xp->xp_context = EXPAND_USER_ADDR_TYPE;
xp->xp_pattern = p + 1;
}
return NULL;
}
arg = skipwhite(p);
}
// After the attributes comes the new command name
p = skiptowhite(arg);
if (*p == NUL)
{
xp->xp_context = EXPAND_USER_COMMANDS;
xp->xp_pattern = arg;
return NULL;
}
// And finally comes a normal command
return skipwhite(p);
}
/*
* Set the completion context for the argument of a user defined command.
*/
char_u *
set_context_in_user_cmdarg(
char_u *cmd UNUSED,
char_u *arg,
long argt,
int context,
expand_T *xp,
int forceit)
{
char_u *p;
if (context == EXPAND_NOTHING)
return NULL;
if (argt & EX_XFILE)
{
// EX_XFILE: file names are handled before this call
return NULL;
}
#ifdef FEAT_MENU
if (context == EXPAND_MENUS)
return set_context_in_menu_cmd(xp, cmd, arg, forceit);
#endif
if (context == EXPAND_COMMANDS)
return arg;
if (context == EXPAND_MAPPINGS)
return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
FALSE, CMD_map);
// Find start of last argument.
p = arg;
while (*p)
{
if (*p == ' ')
// argument starts after a space
arg = p + 1;
else if (*p == '\\' && *(p + 1) != NUL)
++p; // skip over escaped character
MB_PTR_ADV(p);
}
xp->xp_pattern = arg;
xp->xp_context = context;
return NULL;
}
char_u *
expand_user_command_name(int idx)
{
return get_user_commands(NULL, idx - (int)CMD_SIZE);
}
/*
* Function given to ExpandGeneric() to obtain the list of user command names.
*/
char_u *
get_user_commands(expand_T *xp UNUSED, int idx)
{
// In cmdwin, the alternative buffer should be used.
buf_T *buf = prevwin_curwin()->w_buffer;
if (idx < buf->b_ucmds.ga_len)
return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
idx -= buf->b_ucmds.ga_len;
if (idx < ucmds.ga_len)
{
int i;
char_u *name = USER_CMD(idx)->uc_name;
for (i = 0; i < buf->b_ucmds.ga_len; ++i)
if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
// global command is overruled by buffer-local one
return (char_u *)"";
return name;
}
return NULL;
}
#ifdef FEAT_EVAL
/*
* Get the name of user command "idx". "cmdidx" can be CMD_USER or
* CMD_USER_BUF.
* Returns NULL if the command is not found.
*/
char_u *
get_user_command_name(int idx, int cmdidx)
{
if (cmdidx == CMD_USER && idx < ucmds.ga_len)
return USER_CMD(idx)->uc_name;
if (cmdidx == CMD_USER_BUF)
{
// In cmdwin, the alternative buffer should be used.
buf_T *buf = prevwin_curwin()->w_buffer;
if (idx < buf->b_ucmds.ga_len)
return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
}
return NULL;
}
#endif
/*
* Function given to ExpandGeneric() to obtain the list of user address type
* names.
*/
char_u *
get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
{
if (idx < 0 || idx >= (int)ARRAY_LENGTH(addr_type_complete_tab))
return NULL;
return (char_u *)addr_type_complete_tab[idx].fullname;
}
/*
* Function given to ExpandGeneric() to obtain the list of user command
* attributes.
*/
char_u *
get_user_cmd_flags(expand_T *xp UNUSED, int idx)
{
static char *user_cmd_flags[] = {
"addr", "bang", "bar", "buffer", "complete",
"count", "nargs", "range", "register", "keepscript"
};
if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_flags))
return NULL;
return (char_u *)user_cmd_flags[idx];
}
/*
* Function given to ExpandGeneric() to obtain the list of values for -nargs.
*/
char_u *
get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
{
static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
return NULL;
return (char_u *)user_cmd_nargs[idx];
}
/*
* Function given to ExpandGeneric() to obtain the list of values for
* -complete.
*/
char_u *
get_user_cmd_complete(expand_T *xp UNUSED, int idx)
{
if (idx < 0 || idx >= (int)ARRAY_LENGTH(command_complete_tab))
return NULL;
return command_complete_tab[idx].value.string;
}
/*
* Return the row in the command_complete_tab table that contains the given key.
*/
static keyvalue_T *
get_commandtype(int expand)
{
int i;
for (i = 0; i < (int)ARRAY_LENGTH(command_complete_tab); ++i)
if (command_complete_tab[i].key == expand)
return &command_complete_tab[i];
return NULL;
}
#ifdef FEAT_EVAL
/*
* Get the name of completion type "expand" as an allocated string.
* "compl_arg" is the function name for "custom" and "customlist" types.
* Returns NULL if no completion is available or on allocation failure.
*/
char_u *
cmdcomplete_type_to_str(int expand, char_u *compl_arg)
{
keyvalue_T *kv;
char_u *cmd_compl;
kv = get_commandtype(expand);
if (kv == NULL || kv->value.string == NULL)
return NULL;
cmd_compl = kv->value.string;
if (expand == EXPAND_USER_LIST || expand == EXPAND_USER_DEFINED)
{
char_u *buffer;
buffer = alloc(STRLEN(cmd_compl) + STRLEN(compl_arg) + 2);
if (buffer == NULL)
return NULL;
sprintf((char *)buffer, "%s,%s", cmd_compl, compl_arg);
return buffer;
}
return vim_strsave(cmd_compl);
}
/*
* Get the index of completion type "complete_str".
* Returns EXPAND_NOTHING if no match found.
*/
int
cmdcomplete_str_to_type(char_u *complete_str)
{
keyvalue_T target;
keyvalue_T *entry;
static keyvalue_T *last_entry = NULL; // cached result
if (STRNCMP(complete_str, "custom,", 7) == 0)
return EXPAND_USER_DEFINED;
if (STRNCMP(complete_str, "customlist,", 11) == 0)
return EXPAND_USER_LIST;
target.key = 0;
target.value.string = complete_str;
target.value.length = 0; // not used, see cmp_keyvalue_value()
if (last_entry != NULL && cmp_keyvalue_value(&target, last_entry) == 0)
entry = last_entry;
else
{
entry = (keyvalue_T *)bsearch(&target,
&command_complete_tab,
ARRAY_LENGTH(command_complete_tab),
sizeof(command_complete_tab[0]),
cmp_keyvalue_value);
if (entry == NULL)
return EXPAND_NOTHING;
last_entry = entry;
}
return entry->key;
}
#endif
/*
* List user commands starting with "name[name_len]".
*/
static void
uc_list(char_u *name, size_t name_len)
{
int i, j;
int found = FALSE;
ucmd_T *cmd;
int len;
int over;
long a;
garray_T *gap;
keyvalue_T *entry;
// don't allow for adding or removing user commands here
++ucmd_locked;
// In cmdwin, the alternative buffer should be used.
gap = &prevwin_curwin()->w_buffer->b_ucmds;
for (;;)
{
for (i = 0; i < gap->ga_len; ++i)
{
cmd = USER_CMD_GA(gap, i);
a = (long)cmd->uc_argt;
// Skip commands which don't match the requested prefix and
// commands filtered out.
if (STRNCMP(name, cmd->uc_name, name_len) != 0
|| message_filtered(cmd->uc_name))
continue;
// Put out the title first time
if (!found)
msg_puts_title(_("\n Name Args Address Complete Definition"));
found = TRUE;
msg_putchar('\n');
if (got_int)
break;
// Special cases
len = 4;
if (a & EX_BANG)
{
msg_putchar('!');
--len;
}
if (a & EX_REGSTR)
{
msg_putchar('"');
--len;
}
if (gap != &ucmds)
{
msg_putchar('b');
--len;
}
if (a & EX_TRLBAR)
{
msg_putchar('|');
--len;
}
if (len != 0)
msg_puts(&" "[4 - len]);
msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
len = (int)cmd->uc_namelen + 4;
if (len < 21)
{
// Field padding spaces 12345678901234567
static char spaces[18] = " ";
msg_puts(&spaces[len - 4]);
len = 21;
}
msg_putchar(' ');
++len;
// "over" is how much longer the name is than the column width for
// the name, we'll try to align what comes after.
over = len - 22;
len = 0;
// Arguments
switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
{
case 0: IObuff[len++] = '0'; break;
case (EX_EXTRA): IObuff[len++] = '*'; break;
case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
}
do
{
IObuff[len++] = ' ';
} while (len < 5 - over);
// Address / Range
if (a & (EX_RANGE|EX_COUNT))
{
if (a & EX_COUNT)
{
// -count=N
len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
}
else if (a & EX_DFLALL)
IObuff[len++] = '%';
else if (cmd->uc_def >= 0)
{
// -range=N
len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
}
else
IObuff[len++] = '.';
}
do
{
IObuff[len++] = ' ';
} while (len < 8 - over);
// Address Type
for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
if (addr_type_complete_tab[j].key != ADDR_LINES
&& addr_type_complete_tab[j].key == cmd->uc_addr_type)
{
STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
len += (int)addr_type_complete_tab[j].shortnamelen;
break;
}
do
{
IObuff[len++] = ' ';
} while (len < 13 - over);
// Completion
entry = get_commandtype(cmd->uc_compl);
if (entry != NULL)
{
STRCPY(IObuff + len, entry->value.string);
len += (int)entry->value.length;
#ifdef FEAT_EVAL
if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
{
size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
if (uc_compl_arglen < 200)
{
IObuff[len++] = ',';
STRCPY(IObuff + len, cmd->uc_compl_arg);
len += (int)uc_compl_arglen;
}
}
#endif
}
do
{
IObuff[len++] = ' ';
} while (len < 25 - over);
IObuff[len] = NUL;
msg_outtrans(IObuff);
msg_outtrans_special(cmd->uc_rep, FALSE,
name_len == 0 ? Columns - 47 : 0);
#ifdef FEAT_EVAL
if (p_verbose > 0)
last_set_msg(cmd->uc_script_ctx);
#endif
out_flush();
ui_breakcheck();
if (got_int)
break;
}
if (gap == &ucmds || i < gap->ga_len)
break;
gap = &ucmds;
}
if (!found)
msg(_("No user-defined commands found"));
--ucmd_locked;
}
char *
uc_fun_cmd(void)
{
static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
0xb9, 0x7f, 0};
int i;
for (i = 0; fcmd[i]; ++i)
IObuff[i] = fcmd[i] - 0x40;
IObuff[i] = NUL;
return (char *)IObuff;
}
/*
* Parse address type argument
*/
static int
parse_addr_type_arg(
char_u *value,
int vallen,
cmd_addr_T *addr_type_arg)
{
addrtype_T target;
addrtype_T *entry;
static addrtype_T *last_entry; // cached result
target.key = 0;
target.fullname = (char *)value;
target.fullnamelen = vallen;
if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
entry = last_entry;
else
{
entry = (addrtype_T *)bsearch(&target,
&addr_type_complete_tab,
ARRAY_LENGTH(addr_type_complete_tab),
sizeof(addr_type_complete_tab[0]),
cmp_addr_type);
if (entry == NULL)
{
int i;
char_u *err = value;
for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
;
err[i] = NUL;
semsg(_(e_invalid_address_type_value_str), err);
return FAIL;
}
last_entry = entry;
}
*addr_type_arg = entry->key;
return OK;
}
static int
cmp_addr_type(const void *a, const void *b)
{
addrtype_T *at1 = (addrtype_T *)a;
addrtype_T *at2 = (addrtype_T *)b;
return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
}
/*
* Parse a completion argument "value[vallen]".
* The detected completion goes in "*complp", argument type in "*argt".
* When there is an argument, for function and user defined completion, it's
* copied to allocated memory and stored in "*compl_arg".
* Returns FAIL if something is wrong.
*/
int
parse_compl_arg(
char_u *value,
int vallen,
int *complp,
long *argt,
char_u **compl_arg UNUSED)
{
char_u *arg = NULL;
# if defined(FEAT_EVAL)
size_t arglen = 0;
# endif
int i;
int valend = vallen;
keyvalue_T target;
keyvalue_T *entry;
static keyvalue_T *last_entry = NULL; // cached result
// Look for any argument part - which is the part after any ','
for (i = 0; i < vallen; ++i)
{
if (value[i] == ',')
{
arg = &value[i + 1];
# if defined(FEAT_EVAL)
arglen = vallen - i - 1;
# endif
valend = i;
break;
}
}
target.key = 0;
target.value.string = value;
target.value.length = valend;
if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
entry = last_entry;
else
{
entry = (keyvalue_T *)bsearch(&target,
&command_complete_tab,
ARRAY_LENGTH(command_complete_tab),
sizeof(command_complete_tab[0]),
cmp_keyvalue_value_n);
if (entry == NULL)
{
semsg(_(e_invalid_complete_value_str), value);
return FAIL;
}
last_entry = entry;
}
*complp = entry->key;
if (*complp == EXPAND_BUFFERS)
*argt |= EX_BUFNAME;
else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES || *complp == EXPAND_SHELLCMDLINE)
*argt |= EX_XFILE;
if (
# if defined(FEAT_EVAL)
*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
&&
# endif
arg != NULL)
{
emsg(_(e_completion_argument_only_allowed_for_custom_completion));
return FAIL;
}
# if defined(FEAT_EVAL)
if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
&& arg == NULL)
{
emsg(_(e_custom_completion_requires_function_argument));
return FAIL;
}
if (arg != NULL)
*compl_arg = vim_strnsave(arg, arglen);
# endif
return OK;
}
/*
* Scan attributes in the ":command" command.
* Return FAIL when something is wrong.
*/
static int
uc_scan_attr(
char_u *attr,
size_t len,
long *argt,
long *def,
int *flags,
int *complp,
char_u **compl_arg,
cmd_addr_T *addr_type_arg)
{
char_u *p;
if (len == 0)
{
emsg(_(e_no_attribute_specified));
return FAIL;
}
// First, try the simple attributes (no arguments)
if (STRNICMP(attr, "bang", len) == 0)
*argt |= EX_BANG;
else if (STRNICMP(attr, "buffer", len) == 0)
*flags |= UC_BUFFER;
else if (STRNICMP(attr, "register", len) == 0)
*argt |= EX_REGSTR;
else if (STRNICMP(attr, "keepscript", len) == 0)
*argt |= EX_KEEPSCRIPT;
else if (STRNICMP(attr, "bar", len) == 0)
*argt |= EX_TRLBAR;
else
{
int i;
char_u *val = NULL;
size_t vallen = 0;
size_t attrlen = len;
// Look for the attribute name - which is the part before any '='
for (i = 0; i < (int)len; ++i)
{
if (attr[i] == '=')
{
val = &attr[i + 1];
vallen = len - i - 1;
attrlen = i;
break;
}
}
if (STRNICMP(attr, "nargs", attrlen) == 0)
{
if (vallen == 1)
{
if (*val == '0')
// Do nothing - this is the default
;
else if (*val == '1')
*argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
else if (*val == '*')
*argt |= EX_EXTRA;
else if (*val == '?')
*argt |= (EX_EXTRA | EX_NOSPC);
else if (*val == '+')
*argt |= (EX_EXTRA | EX_NEEDARG);
else
goto wrong_nargs;
}
else
{
wrong_nargs:
emsg(_(e_invalid_number_of_arguments));
return FAIL;
}
}
else if (STRNICMP(attr, "range", attrlen) == 0)
{
*argt |= EX_RANGE;
if (vallen == 1 && *val == '%')
*argt |= EX_DFLALL;
else if (val != NULL)
{
p = val;
if (*def >= 0)
{
two_count:
emsg(_(e_count_cannot_be_specified_twice));
return FAIL;
}
*def = getdigits(&p);
*argt |= EX_ZEROR;
if (p != val + vallen || vallen == 0)
{
invalid_count:
emsg(_(e_invalid_default_value_for_count));
return FAIL;
}
}
// default for -range is using buffer lines
if (*addr_type_arg == ADDR_NONE)
*addr_type_arg = ADDR_LINES;
}
else if (STRNICMP(attr, "count", attrlen) == 0)
{
*argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
// default for -count is using any number
if (*addr_type_arg == ADDR_NONE)
*addr_type_arg = ADDR_OTHER;
if (val != NULL)
{
p = val;
if (*def >= 0)
goto two_count;
*def = getdigits(&p);
if (p != val + vallen)
goto invalid_count;
}
if (*def < 0)
*def = 0;
}
else if (STRNICMP(attr, "complete", attrlen) == 0)
{
if (val == NULL)
{
semsg(_(e_argument_required_for_str), "-complete");
return FAIL;
}
if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
== FAIL)
return FAIL;
}
else if (STRNICMP(attr, "addr", attrlen) == 0)
{
*argt |= EX_RANGE;
if (val == NULL)
{
semsg(_(e_argument_required_for_str), "-addr");
return FAIL;
}
if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
return FAIL;
if (*addr_type_arg != ADDR_LINES)
*argt |= EX_ZEROR;
}
else
{
char_u ch = attr[len];
attr[len] = '\0';
semsg(_(e_invalid_attribute_str), attr);
attr[len] = ch;
return FAIL;
}
}
return OK;
}
/*
* Add a user command to the list or replace an existing one.
*/
static int
uc_add_command(
char_u *name,
size_t name_len,
char_u *rep,
long argt,
long def,
int flags,
int compl,
char_u *compl_arg UNUSED,
cmd_addr_T addr_type,
int force)
{
ucmd_T *cmd = NULL;
char_u *p;
int i;
int cmp = 1;
char_u *rep_buf = NULL;
garray_T *gap;
replace_termcodes(rep, &rep_buf, 0, 0, NULL);
if (rep_buf == NULL)
{
// can't replace termcodes - try using the string as is
rep_buf = vim_strsave(rep);
// give up if out of memory
if (rep_buf == NULL)
return FAIL;
}
// get address of growarray: global or in curbuf
if (flags & UC_BUFFER)
{
gap = &curbuf->b_ucmds;
if (gap->ga_itemsize == 0)
ga_init2(gap, sizeof(ucmd_T), 4);
}
else
gap = &ucmds;
// Search for the command in the already defined commands.
for (i = 0; i < gap->ga_len; ++i)
{
cmd = USER_CMD_GA(gap, i);
cmp = STRNCMP(name, cmd->uc_name, name_len);
if (cmp == 0)
{
if (name_len < cmd->uc_namelen)
cmp = -1;
else if (name_len > cmd->uc_namelen)
cmp = 1;
}
if (cmp == 0)
{
// Command can be replaced with "command!" and when sourcing the
// same script again, but only once.
if (!force
#ifdef FEAT_EVAL
&& (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
|| cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
#endif
)
{
semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
name);
goto fail;
}
VIM_CLEAR(cmd->uc_rep);
#if defined(FEAT_EVAL)
VIM_CLEAR(cmd->uc_compl_arg);
#endif
break;
}
// Stop as soon as we pass the name to add
if (cmp < 0)
break;
}
// Extend the array unless we're replacing an existing command
if (cmp != 0)
{
if (ga_grow(gap, 1) == FAIL)
goto fail;
if ((p = vim_strnsave(name, name_len)) == NULL)
goto fail;
cmd = USER_CMD_GA(gap, i);
mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
++gap->ga_len;
cmd->uc_name = p;
cmd->uc_namelen = name_len;
}
cmd->uc_rep = rep_buf;
cmd->uc_argt = argt;
cmd->uc_def = def;
cmd->uc_compl = compl;
cmd->uc_script_ctx = current_sctx;
if (flags & UC_VIM9)
cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
cmd->uc_flags = flags & UC_VIM9;
#ifdef FEAT_EVAL
cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
cmd->uc_compl_arg = compl_arg;
#endif
cmd->uc_addr_type = addr_type;
return OK;
fail:
vim_free(rep_buf);
#if defined(FEAT_EVAL)
vim_free(compl_arg);
#endif
return FAIL;
}
/*
* If "p" starts with "{" then read a block of commands until "}".
* Used for ":command" and ":autocmd".
*/
char_u *
may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
{
char_u *retp = p;
if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
&& eap->ea_getline != NULL)
{
garray_T ga;
char_u *line = NULL;
ga_init2(&ga, sizeof(char_u *), 10);
if (ga_copy_string(&ga, p) == FAIL)
return retp;
// If the argument ends in "}" it must have been concatenated already
// for ISN_EXEC.
if (p[STRLEN(p) - 1] != '}')
// Read lines between '{' and '}'. Does not support nesting or
// here-doc constructs.
for (;;)
{
vim_free(line);
if ((line = eap->ea_getline(':', eap->cookie,
0, GETLINE_CONCAT_CONTBAR)) == NULL)
{
emsg(_(e_missing_rcurly));
break;
}
if (ga_copy_string(&ga, line) == FAIL)
break;
if (*skipwhite(line) == '}')
break;
}
vim_free(line);
retp = *tofree = ga_concat_strings(&ga, "\n");
ga_clear_strings(&ga);
*flags |= UC_VIM9;
}
return retp;
}
/*
* ":command ..." implementation
*/
void
ex_command(exarg_T *eap)
{
char_u *name;
char_u *end;
char_u *p;
long argt = 0;
long def = -1;
int flags = 0;
int compl = EXPAND_NOTHING;
char_u *compl_arg = NULL;
cmd_addr_T addr_type_arg = ADDR_NONE;
int has_attr = (eap->arg[0] == '-');
int name_len;
p = eap->arg;
// Check for attributes
while (*p == '-')
{
++p;
end = skiptowhite(p);
if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
&compl_arg, &addr_type_arg) == FAIL)
goto theend;
p = skipwhite(end);
}
// Get the name (if any) and skip to the following argument
name = p;
if (ASCII_ISALPHA(*p))
while (ASCII_ISALNUM(*p))
++p;
if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
{
emsg(_(e_invalid_command_name));
goto theend;
}
end = p;
name_len = (int)(end - name);
// If there is nothing after the name, and no attributes were specified,
// we are listing commands
p = skipwhite(end);
if (!has_attr && ends_excmd2(eap->arg, p))
{
uc_list(name, name_len);
}
else if (!ASCII_ISUPPER(*name))
{
emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
}
else if ((name_len == 1 && *name == 'X')
|| (name_len <= 4
&& STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
{
emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
}
else if (compl > 0 && (argt & EX_EXTRA) == 0)
{
// Some plugins rely on silently ignoring the mistake, only make this
// an error in Vim9 script.
if (in_vim9script())
emsg(_(e_complete_used_without_allowing_arguments));
else
give_warning_with_source(
(char_u *)_(e_complete_used_without_allowing_arguments),
TRUE, TRUE);
}
else
{
char_u *tofree = NULL;
p = may_get_cmd_block(eap, p, &tofree, &flags);
uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
addr_type_arg, eap->forceit);
vim_free(tofree);
return; // success
}
theend:
vim_free(compl_arg);
}
/*
* ":comclear" implementation
* Clear all user commands, global and for current buffer.
*/
void
ex_comclear(exarg_T *eap UNUSED)
{
uc_clear(&ucmds);
if (curbuf != NULL)
uc_clear(&curbuf->b_ucmds);
}
/*
* If ucmd_locked is set give an error and return TRUE.
* Otherwise return FALSE.
*/
static int
is_ucmd_locked(void)
{
if (ucmd_locked > 0)
{
emsg(_(e_cannot_change_user_commands_while_listing));
return TRUE;
}
return FALSE;
}
/*
* Clear all user commands for "gap".
*/
void
uc_clear(garray_T *gap)
{
int i;
ucmd_T *cmd;
if (is_ucmd_locked())
return;
for (i = 0; i < gap->ga_len; ++i)
{
cmd = USER_CMD_GA(gap, i);
vim_free(cmd->uc_name);
cmd->uc_namelen = 0;
vim_free(cmd->uc_rep);
# if defined(FEAT_EVAL)
vim_free(cmd->uc_compl_arg);
# endif
}
ga_clear(gap);
}
/*
* ":delcommand" implementation
*/
void
ex_delcommand(exarg_T *eap)
{
int i = 0;
ucmd_T *cmd = NULL;
int res = -1;
garray_T *gap;
char_u *arg = eap->arg;
int buffer_only = FALSE;
if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
{
buffer_only = TRUE;
arg = skipwhite(arg + 7);
}
gap = &curbuf->b_ucmds;
for (;;)
{
for (i = 0; i < gap->ga_len; ++i)
{
cmd = USER_CMD_GA(gap, i);
res = STRCMP(arg, cmd->uc_name);
if (res <= 0)
break;
}
if (gap == &ucmds || res == 0 || buffer_only)
break;
gap = &ucmds;
}
if (res != 0)
{
semsg(_(buffer_only
? e_no_such_user_defined_command_in_current_buffer_str
: e_no_such_user_defined_command_str), arg);
return;
}
if (is_ucmd_locked())
return;
vim_free(cmd->uc_name);
vim_free(cmd->uc_rep);
# if defined(FEAT_EVAL)
vim_free(cmd->uc_compl_arg);
# endif
--gap->ga_len;
if (i < gap->ga_len)
mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
}
/*
* Split and quote args for <f-args>.
*/
static char_u *
uc_split_args(char_u *arg, size_t *lenp)
{
char_u *buf;
char_u *p;
char_u *q;
int len;
// Precalculate length
p = arg;
len = 2; // Initial and final quotes
while (*p)
{
if (p[0] == '\\' && p[1] == '\\')
{
len += 2;
p += 2;
}
else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
{
len += 1;
p += 2;
}
else if (*p == '\\' || *p == '"')
{
len += 2;
p += 1;
}
else if (VIM_ISWHITE(*p))
{
p = skipwhite(p);
if (*p == NUL)
break;
len += 4; // ", "
}
else
{
int charlen = (*mb_ptr2len)(p);
len += charlen;
p += charlen;
}
}
buf = alloc(len + 1);
if (buf == NULL)
{
*lenp = 0;
return buf;
}
p = arg;
q = buf;
*q++ = '"';
while (*p)
{
if (p[0] == '\\' && p[1] == '\\')
{
*q++ = '\\';
*q++ = '\\';
p += 2;
}
else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
{
*q++ = p[1];
p += 2;
}
else if (*p == '\\' || *p == '"')
{
*q++ = '\\';
*q++ = *p++;
}
else if (VIM_ISWHITE(*p))
{
p = skipwhite(p);
if (*p == NUL)
break;
*q++ = '"';
*q++ = ',';
*q++ = ' ';
*q++ = '"';
}
else
{
MB_COPY_CHAR(p, q);
}
}
*q++ = '"';
*q = NUL;
*lenp = len;
return buf;
}
static size_t
add_cmd_modifier(
char_u *buf,
size_t buflen,
char *mod_str,
size_t mod_strlen,
int *multi_mods)
{
if (buf != NULL)
{
if (*multi_mods)
{
STRCPY(buf + buflen, " "); // the separating space
++buflen;
}
STRCPY(buf + buflen, mod_str);
}
if (*multi_mods)
++mod_strlen; // +1 for the separating space
else
*multi_mods = 1;
return mod_strlen;
}
/*
* Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
* was added. Return the number of bytes added.
*/
size_t
add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
{
size_t buflen = 0;
// :aboveleft and :leftabove
if (cmod->cmod_split & WSP_ABOVE)
buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
// :belowright and :rightbelow
if (cmod->cmod_split & WSP_BELOW)
buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
// :botright
if (cmod->cmod_split & WSP_BOT)
buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
// :tab
if (cmod->cmod_tab > 0)
{
int tabnr = cmod->cmod_tab - 1;
if (tabnr == tabpage_index(curtab))
{
// For compatibility, don't add a tabpage number if it is the same
// as the default number for :tab.
buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
}
else
{
char tab_buf[NUMBUFLEN + 3];
size_t tab_buflen;
tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
}
}
// :topleft
if (cmod->cmod_split & WSP_TOP)
buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
// :vertical
if (cmod->cmod_split & WSP_VERT)
buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
// :horizontal
if (cmod->cmod_split & WSP_HOR)
buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
return buflen;
}
/*
* Generate text for the "cmod" command modifiers.
* If "buf" is NULL just return the length.
*/
size_t
produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
{
size_t buflen = 0;
int multi_mods = 0;
int i;
static keyvalue_T mod_entry_tab[] =
{
#ifdef FEAT_BROWSE_CMD
KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
#endif
#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
#endif
KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
#ifdef HAVE_SANDBOX
KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
#endif
KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
};
if (quote)
{
++buflen;
if (buf != NULL)
{
*buf = '"';
*(buf + buflen) = NUL;
}
}
else
if (buf != NULL)
*buf = NUL;
// the modifiers that are simple flags
for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
if (cmod->cmod_flags & mod_entry_tab[i].key)
buflen += add_cmd_modifier(buf, buflen,
(char *)mod_entry_tab[i].value.string,
mod_entry_tab[i].value.length, &multi_mods);
// :silent
if (cmod->cmod_flags & CMOD_SILENT)
{
if (cmod->cmod_flags & CMOD_ERRSILENT)
buflen += add_cmd_modifier(buf, buflen, "silent!",
STRLEN_LITERAL("silent!"), &multi_mods);
else
buflen += add_cmd_modifier(buf, buflen, "silent",
STRLEN_LITERAL("silent"), &multi_mods);
}
// :verbose
if (cmod->cmod_verbose > 0)
{
int verbose_value = cmod->cmod_verbose - 1;
if (verbose_value == 1)
buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
else
{
char verbose_buf[NUMBUFLEN];
size_t verbose_buflen;
verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
}
}
// flags from cmod->cmod_split
buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
if (quote)
{
if (buf == NULL)
++buflen;
else
{
*(buf + buflen) = '"';
++buflen;
*(buf + buflen) = NUL;
}
}
return buflen;
}
/*
* Check for a <> code in a user command.
* "code" points to the '<'. "len" the length of the <> (inclusive).
* "buf" is where the result is to be added.
* "split_buf" points to a buffer used for splitting, caller should free it.
* "split_len" is the length of what "split_buf" contains.
* Returns the length of the replacement, which has been added to "buf".
* Returns -1 if there was no match, and only the "<" has been copied.
*/
static size_t
uc_check_code(
char_u *code,
size_t len,
char_u *buf,
ucmd_T *cmd, // the user command we're expanding
exarg_T *eap, // ex arguments
char_u **split_buf,
size_t *split_len)
{
size_t result = 0;
char_u *p = code + 1;
size_t l = len - 2;
int quote = 0;
enum {
ct_ARGS,
ct_BANG,
ct_COUNT,
ct_LINE1,
ct_LINE2,
ct_RANGE,
ct_MODS,
ct_REGISTER,
ct_LT,
ct_NONE
} type = ct_NONE;
if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
{
quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
p += 2;
l -= 2;
}
++l;
if (l <= 1)
type = ct_NONE;
else if (STRNICMP(p, "args>", l) == 0)
type = ct_ARGS;
else if (STRNICMP(p, "bang>", l) == 0)
type = ct_BANG;
else if (STRNICMP(p, "count>", l) == 0)
type = ct_COUNT;
else if (STRNICMP(p, "line1>", l) == 0)
type = ct_LINE1;
else if (STRNICMP(p, "line2>", l) == 0)
type = ct_LINE2;
else if (STRNICMP(p, "range>", l) == 0)
type = ct_RANGE;
else if (STRNICMP(p, "lt>", l) == 0)
type = ct_LT;
else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
type = ct_REGISTER;
else if (STRNICMP(p, "mods>", l) == 0)
type = ct_MODS;
switch (type)
{
case ct_ARGS:
// Simple case first
if (*eap->arg == NUL)
{
if (quote == 1)
{
result = 2;
if (buf != NULL)
STRCPY(buf, "''");
}
else
result = 0;
break;
}
// When specified there is a single argument don't split it.
// Works for ":Cmd %" when % is "a b c".
if ((eap->argt & EX_NOSPC) && quote == 2)
quote = 1;
switch (quote)
{
case 0: // No quoting, no splitting
result = STRLEN(eap->arg);
if (buf != NULL)
STRCPY(buf, eap->arg);
break;
case 1: // Quote, but don't split
result = STRLEN(eap->arg) + 2;
for (p = eap->arg; *p; ++p)
{
if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
// DBCS can contain \ in a trail byte, skip the
// double-byte character.
++p;
else
if (*p == '\\' || *p == '"')
++result;
}
if (buf != NULL)
{
*buf++ = '"';
for (p = eap->arg; *p; ++p)
{
if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
// DBCS can contain \ in a trail byte, copy the
// double-byte character to avoid escaping.
*buf++ = *p++;
else
if (*p == '\\' || *p == '"')
*buf++ = '\\';
*buf++ = *p;
}
*buf = '"';
}
break;
case 2: // Quote and split (<f-args>)
// This is hard, so only do it once, and cache the result
if (*split_buf == NULL)
*split_buf = uc_split_args(eap->arg, split_len);
result = *split_len;
if (buf != NULL && result != 0)
STRCPY(buf, *split_buf);
break;
}
break;
case ct_BANG:
result = eap->forceit ? 1 : 0;
if (quote)
result += 2;
if (buf != NULL)
{
if (quote)
*buf++ = '"';
if (eap->forceit)
*buf++ = '!';
if (quote)
*buf = '"';
}
break;
case ct_LINE1:
case ct_LINE2:
case ct_RANGE:
case ct_COUNT:
{
char num_buf[NUMBUFLEN];
long num = (type == ct_LINE1) ? eap->line1 :
(type == ct_LINE2) ? eap->line2 :
(type == ct_RANGE) ? eap->addr_count :
(eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
size_t num_len;
num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
result = num_len;
if (quote)
result += 2;
if (buf != NULL)
{
if (quote)
*buf++ = '"';
STRCPY(buf, num_buf);
buf += num_len;
if (quote)
*buf = '"';
}
break;
}
case ct_MODS:
{
result = produce_cmdmods(buf, &cmdmod, quote);
break;
}
case ct_REGISTER:
result = eap->regname ? 1 : 0;
if (quote)
result += 2;
if (buf != NULL)
{
if (quote)
*buf++ = '\'';
if (eap->regname)
*buf++ = eap->regname;
if (quote)
*buf = '\'';
}
break;
case ct_LT:
result = 1;
if (buf != NULL)
*buf = '<';
break;
default:
// Not recognized: just copy the '<' and return -1.
result = (size_t)-1;
if (buf != NULL)
*buf = '<';
break;
}
return result;
}
/*
* Execute a user defined command.
*/
void
do_ucmd(exarg_T *eap)
{
char_u *buf;
char_u *p;
char_u *q;
char_u *start;
char_u *end = NULL;
char_u *ksp;
size_t len, totlen;
size_t split_len = 0;
char_u *split_buf = NULL;
ucmd_T *cmd;
sctx_T save_current_sctx;
int restore_current_sctx = FALSE;
#ifdef FEAT_EVAL
int restore_script_version = 0;
#endif
if (eap->cmdidx == CMD_USER)
cmd = USER_CMD(eap->useridx);
else
cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
/*
* Replace <> in the command by the arguments.
* First round: "buf" is NULL, compute length, allocate "buf".
* Second round: copy result into "buf".
*/
buf = NULL;
for (;;)
{
p = cmd->uc_rep; // source
q = buf; // destination
totlen = 0;
for (;;)
{
start = vim_strchr(p, '<');
if (start != NULL)
end = vim_strchr(start + 1, '>');
if (buf != NULL)
{
for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
;
if (*ksp == K_SPECIAL
&& (start == NULL || ksp < start || end == NULL)
&& ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
# ifdef FEAT_GUI
|| (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
# endif
))
{
// K_SPECIAL has been put in the buffer as K_SPECIAL
// KS_SPECIAL KE_FILLER, like for mappings, but
// do_cmdline() doesn't handle that, so convert it back.
// Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
len = ksp - p;
if (len > 0)
{
mch_memmove(q, p, len);
q += len;
}
*q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
p = ksp + 3;
continue;
}
}
// break if no <item> is found
if (start == NULL || end == NULL)
break;
// Include the '>'
++end;
// Take everything up to the '<'
len = start - p;
if (buf == NULL)
totlen += len;
else
{
mch_memmove(q, p, len);
q += len;
}
len = uc_check_code(start, end - start, q, cmd, eap,
&split_buf, &split_len);
if (len == (size_t)-1)
{
// no match, continue after '<'
p = start + 1;
len = 1;
}
else
p = end;
if (buf == NULL)
totlen += len;
else
q += len;
}
if (buf != NULL) // second time here, finished
{
STRCPY(q, p);
break;
}
totlen += STRLEN(p); // Add on the trailing characters
buf = alloc(totlen + 1);
if (buf == NULL)
{
vim_free(split_buf);
return;
}
}
if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
{
restore_current_sctx = TRUE;
save_current_sctx = current_sctx;
current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
#ifdef FEAT_EVAL
current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
if (cmd->uc_flags & UC_VIM9)
{
// In a {} block variables use Vim9 script rules, even in a legacy
// script.
restore_script_version =
SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
}
#endif
}
(void)do_cmdline(buf, eap->ea_getline, eap->cookie,
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
// Careful: Do not use "cmd" here, it may have become invalid if a user
// command was added.
if (restore_current_sctx)
{
#ifdef FEAT_EVAL
if (restore_script_version != 0)
SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
restore_script_version;
#endif
current_sctx = save_current_sctx;
}
vim_free(buf);
vim_free(split_buf);
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C
1
https://gitee.com/mirrors/vim.git
git@gitee.com:mirrors/vim.git
mirrors
vim
Vim
master

搜索帮助