# HG changeset patch # User Bram Moolenaar # Date 1556363707 -7200 # Node ID 5b5c5daf57dedf10b71c8a20233e67268698462e # Parent eb356c5e62873a0b8aa9234dfad719739c6d31d7 patch 8.1.1210: support for user commands is spread out commit https://github.com/vim/vim/commit/ac9fb18020d7e8bf16d02d45fbb02cf47328aaf7 Author: Bram Moolenaar Date: Sat Apr 27 13:04:13 2019 +0200 patch 8.1.1210: support for user commands is spread out Problem: Support for user commands is spread out. No good reason to make user commands optional. Solution: Move user command support to usercmd.c. Always enable the user_commands feature. diff --git a/Filelist b/Filelist --- a/Filelist +++ b/Filelist @@ -98,6 +98,7 @@ SRC_ALL = \ src/textprop.c \ src/ui.c \ src/undo.c \ + src/usercmd.c \ src/userfunc.c \ src/version.c \ src/version.h \ @@ -212,6 +213,7 @@ SRC_ALL = \ src/proto/textprop.pro \ src/proto/ui.pro \ src/proto/undo.pro \ + src/proto/usercmd.pro \ src/proto/userfunc.pro \ src/proto/version.pro \ src/proto/winclip.pro \ diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1,4 +1,4 @@ -*eval.txt* For Vim version 8.1. Last change: 2019 Apr 21 +*eval.txt* For Vim version 8.1. Last change: 2019 Apr 27 VIM REFERENCE MANUAL by Bram Moolenaar @@ -10550,7 +10550,7 @@ ttyin input is a terminal (tty) ttyout output is a terminal (tty) unix Unix version of Vim. *+unix* unnamedplus Compiled with support for "unnamedplus" in 'clipboard' -user_commands User-defined commands. +user_commands User-defined commands. (always true) vcon Win32: Virtual console support is working, can use 'termguicolors'. Also see |+vtp|. vertsplit Compiled with vertically split windows |:vsplit|. diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -456,7 +456,8 @@ N *+textprop* |text-properties| N *+timers* the |timer_start()| function N *+title* Setting the window 'title' and 'icon' N *+toolbar* |gui-toolbar| -N *+user_commands* User-defined commands. |user-commands| +T *+user_commands* User-defined commands. |user-commands| + Always enabled since 8.1.1210. B *+vartabs* Variable-width tabstops. |'vartabstop'| N *+viminfo* |'viminfo'| *+vertsplit* Vertically split windows |:vsplit|; Always enabled diff --git a/src/Make_bc5.mak b/src/Make_bc5.mak --- a/src/Make_bc5.mak +++ b/src/Make_bc5.mak @@ -565,6 +565,7 @@ vimobj = \ $(OBJDIR)\term.obj \ $(OBJDIR)\ui.obj \ $(OBJDIR)\undo.obj \ + $(OBJDIR)\usercmd.obj \ $(OBJDIR)\userfunc.obj \ $(OBJDIR)\version.obj \ $(OBJDIR)\window.obj \ diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak --- a/src/Make_cyg_ming.mak +++ b/src/Make_cyg_ming.mak @@ -757,6 +757,7 @@ OBJ = \ $(OUTDIR)/textprop.o \ $(OUTDIR)/ui.o \ $(OUTDIR)/undo.o \ + $(OUTDIR)/usercmd.o \ $(OUTDIR)/userfunc.o \ $(OUTDIR)/version.o \ $(OUTDIR)/vimrc.o \ diff --git a/src/Make_dice.mak b/src/Make_dice.mak --- a/src/Make_dice.mak +++ b/src/Make_dice.mak @@ -83,6 +83,7 @@ SRC = \ term.c \ ui.c \ undo.c \ + usercmd.c \ userfunc.c \ window.c \ version.c @@ -144,6 +145,7 @@ OBJ = o/arabic.o \ o/term.o \ o/ui.o \ o/undo.o \ + o/usercmd.o \ o/userfunc.o \ o/window.o \ $(TERMLIB) @@ -288,6 +290,8 @@ o/ui.o: ui.c $(SYMS) o/undo.o: undo.c $(SYMS) +o/usercmd.o: usercmd.c $(SYMS) + o/userfunc.o: userfunc.c $(SYMS) o/window.o: window.c $(SYMS) diff --git a/src/Make_ivc.mak b/src/Make_ivc.mak --- a/src/Make_ivc.mak +++ b/src/Make_ivc.mak @@ -269,6 +269,7 @@ LINK32_OBJS= \ "$(INTDIR)/term.obj" \ "$(INTDIR)/ui.obj" \ "$(INTDIR)/undo.obj" \ + "$(INTDIR)/usercmd.obj" \ "$(INTDIR)/userfunc.obj" \ "$(INTDIR)/version.obj" \ "$(INTDIR)/window.obj" @@ -728,6 +729,10 @@ SOURCE=.\undo.c # End Source File # Begin Source File +SOURCE=.\usercmd.c +# End Source File +# Begin Source File + SOURCE=.\userfunc.c # End Source File # Begin Source File diff --git a/src/Make_manx.mak b/src/Make_manx.mak --- a/src/Make_manx.mak +++ b/src/Make_manx.mak @@ -93,6 +93,7 @@ SRC = arabic.c \ term.c \ ui.c \ undo.c \ + usercmd.c \ userfunc.c \ window.c \ version.c @@ -156,6 +157,7 @@ OBJ = obj/arabic.o \ obj/term.o \ obj/ui.o \ obj/undo.o \ + obj/usercmd.o \ obj/userfunc.o \ obj/window.o \ $(TERMLIB) @@ -218,6 +220,7 @@ PRO = proto/arabic.pro \ proto/termlib.pro \ proto/ui.pro \ proto/undo.pro \ + proto/usercmd.pro \ proto/userfunc.pro \ proto/window.pro @@ -443,6 +446,9 @@ obj/ui.o: ui.c obj/undo.o: undo.c $(CCSYM) $@ undo.c +obj/usercmd.o: usercmd.c + $(CCSYM) $@ usercmd.c + obj/userfunc.o: userfunc.c $(CCSYM) $@ userfunc.c diff --git a/src/Make_morph.mak b/src/Make_morph.mak --- a/src/Make_morph.mak +++ b/src/Make_morph.mak @@ -81,6 +81,7 @@ SRC = arabic.c \ term.c \ ui.c \ undo.c \ + usercmd.c \ userfunc.c \ version.c \ window.c \ diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -765,6 +765,7 @@ OBJ = \ $(OUTDIR)\textprop.obj \ $(OUTDIR)\ui.obj \ $(OUTDIR)\undo.obj \ + $(OUTDIR)\usercmd.obj \ $(OUTDIR)\userfunc.obj \ $(OUTDIR)\winclip.obj \ $(OUTDIR)\window.obj \ @@ -1550,6 +1551,8 @@ lib$(MZSCHEME_MAIN_LIB)$(MZSCHEME_VER).l $(OUTDIR)/undo.obj: $(OUTDIR) undo.c $(INCL) +$(OUTDIR)/usercmd.obj: $(OUTDIR) usercmd.c $(INCL) + $(OUTDIR)/userfunc.obj: $(OUTDIR) userfunc.c $(INCL) $(OUTDIR)/window.obj: $(OUTDIR) window.c $(INCL) @@ -1693,6 +1696,7 @@ proto.h: \ proto/textprop.pro \ proto/ui.pro \ proto/undo.pro \ + proto/usercmd.pro \ proto/userfunc.pro \ proto/window.pro \ $(NETBEANS_PRO) \ diff --git a/src/Make_sas.mak b/src/Make_sas.mak --- a/src/Make_sas.mak +++ b/src/Make_sas.mak @@ -146,6 +146,7 @@ SRC = \ term.c \ ui.c \ undo.c \ + usercmd.c \ userfunc.c \ window.c \ version.c @@ -208,6 +209,7 @@ OBJ = \ term.o \ ui.o \ undo.o \ + usercmd.o \ userfunc.o \ window.o \ $(TERMLIB) @@ -271,6 +273,7 @@ PRO = \ proto/termlib.pro \ proto/ui.pro \ proto/undo.pro \ + proto/usercmd.pro \ proto/userfunc.pro \ proto/window.pro @@ -445,6 +448,8 @@ ui.o: ui.c proto/ui.pro: ui.c undo.o: undo.c proto/undo.pro: undo.c +usercmd.o: usercmd.c +proto/usercmd.pro: usercmd.c userfunc.o: userfunc.c proto/userfunc.pro: userfunc.c window.o: window.c diff --git a/src/Make_vms.mms b/src/Make_vms.mms --- a/src/Make_vms.mms +++ b/src/Make_vms.mms @@ -2,7 +2,7 @@ # Makefile for Vim on OpenVMS # # Maintainer: Zoltan Arpadffy -# Last change: 2019 Mar 22 +# Last change: 2019 Apr 26 # # This has script been tested on VMS 6.2 to 8.2 on DEC Alpha, VAX and IA64 # with MMS and MMK @@ -315,8 +315,8 @@ SRC = arabic.c autocmd.c beval.c blob.c menu.c mbyte.c memfile.c memline.c message.c misc1.c misc2.c move.c \ normal.c ops.c option.c popupmnu.c quickfix.c regexp.c search.c \ sha256.c sign.c spell.c spellfile.c syntax.c tag.c term.c termlib.c \ - textprop.c ui.c undo.c userfunc.c version.c screen.c window.c \ - os_unix.c os_vms.c pathdef.c \ + textprop.c ui.c undo.c usercmd.c userfunc.c version.c screen.c \ + window.c os_unix.c os_vms.c pathdef.c \ $(GUI_SRC) $(PERL_SRC) $(PYTHON_SRC) $(TCL_SRC) \ $(RUBY_SRC) $(HANGULIN_SRC) $(MZSCH_SRC) $(XDIFF_SRC) @@ -330,7 +330,7 @@ OBJ = arabic.obj autocmd.obj beval.obj move.obj mbyte.obj normal.obj ops.obj option.obj popupmnu.obj \ quickfix.obj regexp.obj search.obj sha256.obj sign.obj spell.obj \ spellfile.obj syntax.obj tag.obj term.obj termlib.obj textprop.obj \ - ui.obj undo.obj userfunc.obj screen.obj version.obj \ + ui.obj undo.obj usercmd.obj userfunc.obj screen.obj version.obj \ window.obj os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \ $(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \ $(RUBY_OBJ) $(HANGULIN_OBJ) $(MZSCH_OBJ) $(XDIFF_OBJ) @@ -744,10 +744,16 @@ undo.obj : undo.c vim.h [.auto]config.h ascii.h keymap.h term.h macros.h structs.h regexp.h gui.h beval.h \ [.proto]gui_beval.pro option.h ex_cmds.h proto.h globals.h \ +usercmd.obj : usercmd.c vim.h [.auto]config.h feature.h os_unix.h \ + ascii.h keymap.h term.h macros.h option.h structs.h \ + regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \ + proto.h globals.h + userfunc.obj : userfunc.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h option.h structs.h \ regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \ proto.h globals.h + version.obj : version.c vim.h [.auto]config.h feature.h os_unix.h \ ascii.h keymap.h term.h macros.h structs.h regexp.h \ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \ diff --git a/src/Makefile b/src/Makefile --- a/src/Makefile +++ b/src/Makefile @@ -1635,6 +1635,7 @@ BASIC_SRC = \ textprop.c \ ui.c \ undo.c \ + usercmd.c \ userfunc.c \ version.c \ window.c \ @@ -1747,6 +1748,7 @@ OBJ_COMMON = \ objects/textprop.o \ objects/ui.o \ objects/undo.o \ + objects/usercmd.o \ objects/userfunc.o \ objects/version.o \ objects/window.o \ @@ -1885,6 +1887,7 @@ PRO_AUTO = \ textprop.pro \ ui.pro \ undo.pro \ + usercmd.pro \ userfunc.pro \ version.pro \ window.pro \ @@ -3242,6 +3245,9 @@ objects/ui.o: ui.c objects/undo.o: undo.c $(CCC) -o $@ undo.c +objects/usercmd.o: usercmd.c + $(CCC) -o $@ usercmd.c + objects/userfunc.o: userfunc.c $(CCC) -o $@ userfunc.c @@ -3657,6 +3663,10 @@ objects/undo.o: undo.c vim.h protodef.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ proto.h globals.h +objects/usercmd.o: usercmd.c vim.h protodef.h auto/config.h feature.h os_unix.h \ + auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ + proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ + proto.h globals.h objects/userfunc.o: userfunc.c vim.h protodef.h auto/config.h feature.h os_unix.h \ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \ diff --git a/src/README.md b/src/README.md --- a/src/README.md +++ b/src/README.md @@ -28,6 +28,7 @@ buffer.c | manipulating buffers (loaded debugger.c | vim script debugger diff.c | diff mode (vimdiff) eval.c | expression evaluation +evalfunc.c | built-in functions fileio.c | reading and writing files findfile.c | search for files in 'path' fold.c | folding @@ -40,7 +41,7 @@ memfile.c | storing lines for buffers in memline.c | storing lines for buffers in memory menu.c | menus message.c | (error) messages -ops.c | handling operators ("d", "y", "p") +ops.c | handling operators ("d", "y", "p") option.c | options quickfix.c | quickfix commands (":make", ":cn") regexp.c | pattern matching @@ -49,9 +50,11 @@ search.c | pattern searching sign.c | signs spell.c | spell checking syntax.c | syntax and other highlighting -tag.c | tags +tag.c | tags term.c | terminal handling, termcap codes undo.c | undo and redo +usercmd.c | user defined commands +userfunc.c | user defined functions window.c | handling split windows diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -925,11 +925,9 @@ free_buffer_stuff( CHANGEDTICK(buf) = tick; } #endif -#ifdef FEAT_USR_CMDS - uc_clear(&buf->b_ucmds); /* clear local user commands */ -#endif + uc_clear(&buf->b_ucmds); // clear local user commands #ifdef FEAT_SIGNS - buf_delete_signs(buf, (char_u *)"*"); // delete any signs */ + buf_delete_signs(buf, (char_u *)"*"); // delete any signs #endif #ifdef FEAT_NETBEANS_INTG netbeans_file_killed(buf); diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1120,10 +1120,10 @@ call_func_retnr( return retval; } -#if (defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)) \ +#if defined(FEAT_CMDL_COMPL) \ || defined(FEAT_COMPL_FUNC) || defined(PROTO) -# if (defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)) || defined(PROTO) +# if defined(FEAT_CMDL_COMPL) || defined(PROTO) /* * Call Vim script function "func" and return the result as a string. * Returns NULL when calling the function fails. diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -6611,10 +6611,8 @@ f_has(typval_T *argvars, typval_T *rettv #if defined(FEAT_CLIPBOARD) && defined(FEAT_X11) "unnamedplus", #endif -#ifdef FEAT_USR_CMDS "user-commands", /* was accidentally included in 5.4 */ "user_commands", -#endif #ifdef FEAT_VARTABS "vartabs", #endif diff --git a/src/ex_cmds.h b/src/ex_cmds.h --- a/src/ex_cmds.h +++ b/src/ex_cmds.h @@ -1753,13 +1753,9 @@ EX(CMD_tilde, "~", do_sub, ADDR_LINES), #ifndef DO_DECLARE_EXCMD -#ifdef FEAT_USR_CMDS CMD_SIZE, /* MUST be after all real commands! */ CMD_USER = -1, /* User-defined command */ CMD_USER_BUF = -2 /* User-defined command local to buffer */ -#else - CMD_SIZE /* MUST be the last one! */ -#endif #endif }; @@ -1795,9 +1791,7 @@ struct exarg int force_ff; /* ++ff= argument (first char of argument) */ int force_enc; /* ++enc= argument (index in cmd[]) */ int bad_char; /* BAD_KEEP, BAD_DROP or replacement byte */ -#ifdef FEAT_USR_CMDS int useridx; /* user command index */ -#endif char *errmsg; /* returned error message */ char_u *(*getline)(int, void *, int); void *cookie; /* argument for getline() */ diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -19,48 +19,6 @@ static int ex_pressedreturn = FALSE; # define ex_hardcopy ex_ni #endif -#ifdef FEAT_USR_CMDS -typedef struct ucmd -{ - char_u *uc_name; /* The command name */ - 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 */ - int uc_addr_type; /* The command's address type */ -# ifdef FEAT_EVAL - sctx_T uc_script_ctx; /* SCTX where the command was defined */ -# ifdef FEAT_CMDL_COMPL - char_u *uc_compl_arg; /* completion argument if any */ -# endif -# endif -} ucmd_T; - -#define UC_BUFFER 1 /* -buffer: local to current buffer */ - -static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL}; - -#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) -#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) - -static void do_ucmd(exarg_T *eap); -static void ex_command(exarg_T *eap); -static void ex_delcommand(exarg_T *eap); -# ifdef FEAT_CMDL_COMPL -static char_u *get_user_command_name(int idx); -# endif - -/* Wether a command index indicates a user command. */ -# define IS_USER_CMDIDX(idx) ((int)(idx) < 0) - -#else -# define ex_command ex_ni -# define ex_comclear ex_ni -# define ex_delcommand ex_ni -/* Wether a command index indicates a user command. */ -# define IS_USER_CMDIDX(idx) (FALSE) -#endif - #ifdef FEAT_EVAL static char_u *do_one_cmd(char_u **, int, struct condstack *, char_u *(*fgetline)(int, void *, int), void *cookie); #else @@ -300,10 +258,6 @@ static void ex_redrawtabline(exarg_T *ea static void close_redir(void); static void ex_mkrc(exarg_T *eap); static void ex_mark(exarg_T *eap); -#ifdef FEAT_USR_CMDS -static char *uc_fun_cmd(void); -static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *compl); -#endif static void ex_startinsert(exarg_T *eap); static void ex_stopinsert(exarg_T *eap); #ifdef FEAT_FIND_ID @@ -1929,21 +1883,20 @@ do_one_cmd( ) ? find_command(&ea, NULL) : ea.cmd; } -#ifdef FEAT_USR_CMDS if (p == NULL) { if (!ea.skip) errormsg = _("E464: Ambiguous use of user-defined command"); goto doend; } - /* Check for wrong commands. */ + // Check for wrong commands. if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78 && !IS_USER_CMDIDX(ea.cmdidx)) { errormsg = uc_fun_cmd(); goto doend; } -#endif + if (ea.cmdidx == CMD_SIZE) { if (!ea.skip) @@ -2508,7 +2461,6 @@ do_one_cmd( * 7. Execute the command. */ -#ifdef FEAT_USR_CMDS if (IS_USER_CMDIDX(ea.cmdidx)) { /* @@ -2517,10 +2469,9 @@ do_one_cmd( do_ucmd(&ea); } else -#endif { /* - * Call the function to execute the command. + * Call the function to execute the builtin command. */ ea.errmsg = NULL; (cmdnames[ea.cmdidx].cmd_func)(&ea); @@ -3235,18 +3186,16 @@ find_command(exarg_T *eap, int *full UNU break; } -#ifdef FEAT_USR_CMDS - /* Look for a user defined command as a last resort. Let ":Print" be - * overruled by a user defined command. */ + // Look for a user defined command as a last resort. Let ":Print" be + // overruled by a user defined command. if ((eap->cmdidx == CMD_SIZE || eap->cmdidx == CMD_Print) && *eap->cmd >= 'A' && *eap->cmd <= 'Z') { - /* User defined commands may contain digits. */ + // User defined commands may contain digits. while (ASCII_ISALNUM(*p)) ++p; p = find_ucmd(eap, p, full, NULL, NULL); } -#endif if (p == eap->cmd) eap->cmdidx = CMD_SIZE; } @@ -3254,124 +3203,6 @@ find_command(exarg_T *eap, int *full UNU return p; } -#ifdef FEAT_USR_CMDS -/* - * 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. - */ - static 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 *compl) /* 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 = &curbuf->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; - -# ifdef FEAT_CMDL_COMPL - if (compl != NULL) - *compl = 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 -# 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; -} -#endif - #if defined(FEAT_EVAL) || defined(PROTO) static struct cmdmod { @@ -3483,10 +3314,8 @@ set_one_cmd_context( char_u *cmd, *arg; int len = 0; exarg_T ea; -#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL) +#ifdef FEAT_CMDL_COMPL int compl = EXPAND_NOTHING; -#endif -#ifdef FEAT_CMDL_COMPL int delim; #endif int forceit = FALSE; @@ -3572,11 +3401,9 @@ set_one_cmd_context( (size_t)len) == 0) break; -#ifdef FEAT_USR_CMDS if (cmd[0] >= 'A' && cmd[0] <= 'Z') - while (ASCII_ISALNUM(*p) || *p == '*') /* Allow * wild card */ + while (ASCII_ISALNUM(*p) || *p == '*') // Allow * wild card ++p; -#endif } /* @@ -3593,21 +3420,19 @@ set_one_cmd_context( ea.cmdidx = CMD_substitute; p = cmd + 1; } -#ifdef FEAT_USR_CMDS else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { ea.cmd = cmd; p = find_ucmd(&ea, p, NULL, xp, -# if defined(FEAT_CMDL_COMPL) +#if defined(FEAT_CMDL_COMPL) &compl -# else +#else NULL -# endif +#endif ); if (p == NULL) - ea.cmdidx = CMD_SIZE; /* ambiguous user command */ - } -#endif + ea.cmdidx = CMD_SIZE; // ambiguous user command + } } if (ea.cmdidx == CMD_SIZE) { @@ -3828,7 +3653,7 @@ set_one_cmd_context( { xp->xp_context = EXPAND_ENV_VARS; ++xp->xp_pattern; -#if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL) +#if defined(FEAT_CMDL_COMPL) /* Avoid that the assignment uses EXPAND_FILES again. */ if (compl != EXPAND_USER_DEFINED && compl != EXPAND_USER_LIST) compl = EXPAND_ENV_VARS; @@ -3944,68 +3769,13 @@ set_one_cmd_context( * All completion for the +cmdline_compl feature goes here. */ -# ifdef FEAT_USR_CMDS case CMD_command: - /* 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; - return NULL; - } - else if (STRNICMP(arg, "nargs", p - arg) == 0) - { - xp->xp_context = EXPAND_USER_NARGS; - xp->xp_pattern = p + 1; - return NULL; - } - else if (STRNICMP(arg, "addr", p - arg) == 0) - { - xp->xp_context = EXPAND_USER_ADDR_TYPE; - xp->xp_pattern = p + 1; - return NULL; - } - 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; - break; - } - - /* And finally comes a normal command */ - return skipwhite(p); + return set_context_in_user_cmd(xp, arg); case CMD_delcommand: xp->xp_context = EXPAND_USER_COMMANDS; xp->xp_pattern = arg; break; -# endif case CMD_global: case CMD_vglobal: @@ -4186,32 +3956,32 @@ set_one_cmd_context( xp->xp_context = EXPAND_BUFFERS; xp->xp_pattern = arg; break; -#ifdef FEAT_USR_CMDS + case CMD_USER: case CMD_USER_BUF: if (compl != EXPAND_NOTHING) { - /* XFILE: file names are handled above */ + // XFILE: file names are handled above if (!(ea.argt & XFILE)) { -# ifdef FEAT_MENU +#ifdef FEAT_MENU if (compl == EXPAND_MENUS) return set_context_in_menu_cmd(xp, cmd, arg, forceit); -# endif +#endif if (compl == EXPAND_COMMANDS) return arg; if (compl == EXPAND_MAPPINGS) return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE, FALSE, CMD_map); - /* Find start of last argument. */ + // Find start of last argument. p = arg; while (*p) { if (*p == ' ') - /* argument starts after a space */ + // argument starts after a space arg = p + 1; else if (*p == '\\' && *(p + 1) != NUL) - ++p; /* skip over escaped character */ + ++p; // skip over escaped character MB_PTR_ADV(p); } xp->xp_pattern = arg; @@ -4219,7 +3989,7 @@ set_one_cmd_context( xp->xp_context = compl; } break; -#endif + case CMD_map: case CMD_noremap: case CMD_nmap: case CMD_nnoremap: case CMD_vmap: case CMD_vnoremap: @@ -5771,7 +5541,7 @@ check_more( return OK; } -#ifdef FEAT_CMDL_COMPL +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) /* * Function given to ExpandGeneric() to obtain the list of command names. */ @@ -5779,1440 +5549,11 @@ check_more( get_command_name(expand_T *xp UNUSED, int idx) { if (idx >= (int)CMD_SIZE) -# ifdef FEAT_USR_CMDS return get_user_command_name(idx); -# else - return NULL; -# endif return cmdnames[idx].cmd_name; } #endif -#if defined(FEAT_USR_CMDS) || defined(PROTO) - 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, - int 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, FALSE, FALSE, FALSE); - 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, (int)sizeof(ucmd_T), 4); - } - else - gap = &ucmds; - - /* Search for the command in the already defined commands. */ - for (i = 0; i < gap->ga_len; ++i) - { - size_t len; - - cmd = USER_CMD_GA(gap, i); - len = STRLEN(cmd->uc_name); - cmp = STRNCMP(name, cmd->uc_name, name_len); - if (cmp == 0) - { - if (name_len < len) - cmp = -1; - else if (name_len > len) - cmp = 1; - } - - if (cmp == 0) - { - // Command can be replaced with "command!" and when sourcing the - // same script again, but only once. - if (!force && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid - || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) - { - semsg(_("E174: Command already exists: add ! to replace it: %s"), - name); - goto fail; - } - - VIM_CLEAR(cmd->uc_rep); -#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - 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) != OK) - goto fail; - if ((p = vim_strnsave(name, (int)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_rep = rep_buf; - cmd->uc_argt = argt; - cmd->uc_def = def; - cmd->uc_compl = compl; -#ifdef FEAT_EVAL - cmd->uc_script_ctx = current_sctx; - cmd->uc_script_ctx.sc_lnum += sourcing_lnum; -# ifdef FEAT_CMDL_COMPL - cmd->uc_compl_arg = compl_arg; -# endif -#endif - cmd->uc_addr_type = addr_type; - - return OK; - -fail: - vim_free(rep_buf); -#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - vim_free(compl_arg); -#endif - return FAIL; -} -#endif - -#if defined(FEAT_USR_CMDS) -static struct -{ - int expand; - char *name; - char *shortname; -} addr_type_complete[] = -{ - {ADDR_ARGUMENTS, "arguments", "arg"}, - {ADDR_LINES, "lines", "line"}, - {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"}, - {ADDR_TABS, "tabs", "tab"}, - {ADDR_BUFFERS, "buffers", "buf"}, - {ADDR_WINDOWS, "windows", "win"}, - {ADDR_QUICKFIX, "quickfix", "qf"}, - {ADDR_OTHER, "other", "?"}, - {-1, NULL, NULL} -}; -#endif - -#if defined(FEAT_USR_CMDS) || defined(FEAT_EVAL) || defined(PROTO) -/* - * List of names for completion for ":command" with the EXPAND_ flag. - * Must be alphabetical for completion. - */ -static struct -{ - int expand; - char *name; -} command_complete[] = -{ - {EXPAND_ARGLIST, "arglist"}, - {EXPAND_AUGROUP, "augroup"}, - {EXPAND_BEHAVE, "behave"}, - {EXPAND_BUFFERS, "buffer"}, - {EXPAND_COLORS, "color"}, - {EXPAND_COMMANDS, "command"}, - {EXPAND_COMPILER, "compiler"}, -#if defined(FEAT_CSCOPE) - {EXPAND_CSCOPE, "cscope"}, -#endif -#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - {EXPAND_USER_DEFINED, "custom"}, - {EXPAND_USER_LIST, "customlist"}, -#endif - {EXPAND_DIRECTORIES, "dir"}, - {EXPAND_ENV_VARS, "environment"}, - {EXPAND_EVENTS, "event"}, - {EXPAND_EXPRESSION, "expression"}, - {EXPAND_FILES, "file"}, - {EXPAND_FILES_IN_PATH, "file_in_path"}, - {EXPAND_FILETYPE, "filetype"}, - {EXPAND_FUNCTIONS, "function"}, - {EXPAND_HELP, "help"}, - {EXPAND_HIGHLIGHT, "highlight"}, -#if defined(FEAT_CMDHIST) - {EXPAND_HISTORY, "history"}, -#endif -#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) - {EXPAND_LOCALES, "locale"}, -#endif - {EXPAND_MAPCLEAR, "mapclear"}, - {EXPAND_MAPPINGS, "mapping"}, - {EXPAND_MENUS, "menu"}, - {EXPAND_MESSAGES, "messages"}, - {EXPAND_OWNSYNTAX, "syntax"}, -#if defined(FEAT_PROFILE) - {EXPAND_SYNTIME, "syntime"}, -#endif - {EXPAND_SETTINGS, "option"}, - {EXPAND_PACKADD, "packadd"}, - {EXPAND_SHELLCMD, "shellcmd"}, -#if defined(FEAT_SIGNS) - {EXPAND_SIGN, "sign"}, -#endif - {EXPAND_TAGS, "tag"}, - {EXPAND_TAGS_LISTFILES, "tag_listfiles"}, - {EXPAND_USER, "user"}, - {EXPAND_USER_VARS, "var"}, - {0, NULL} -}; -#endif - -#if defined(FEAT_USR_CMDS) || defined(PROTO) - 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; - - gap = &curbuf->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 & BANG) - { - msg_putchar('!'); - --len; - } - if (a & REGSTR) - { - msg_putchar('"'); - --len; - } - if (gap != &ucmds) - { - msg_putchar('b'); - --len; - } - if (a & TRLBAR) - { - msg_putchar('|'); - --len; - } - while (len-- > 0) - msg_putchar(' '); - - msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); - len = (int)STRLEN(cmd->uc_name) + 4; - - do { - msg_putchar(' '); - ++len; - } while (len < 22); - - // "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 & (EXTRA|NOSPC|NEEDARG))) - { - case 0: IObuff[len++] = '0'; break; - case (EXTRA): IObuff[len++] = '*'; break; - case (EXTRA|NOSPC): IObuff[len++] = '?'; break; - case (EXTRA|NEEDARG): IObuff[len++] = '+'; break; - case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break; - } - - do { - IObuff[len++] = ' '; - } while (len < 5 - over); - - // Address / Range - if (a & (RANGE|COUNT)) - { - if (a & COUNT) - { - // -count=N - sprintf((char *)IObuff + len, "%ldc", cmd->uc_def); - len += (int)STRLEN(IObuff + len); - } - else if (a & DFLALL) - IObuff[len++] = '%'; - else if (cmd->uc_def >= 0) - { - // -range=N - sprintf((char *)IObuff + len, "%ld", cmd->uc_def); - len += (int)STRLEN(IObuff + len); - } - else - IObuff[len++] = '.'; - } - - do { - IObuff[len++] = ' '; - } while (len < 8 - over); - - // Address Type - for (j = 0; addr_type_complete[j].expand != -1; ++j) - if (addr_type_complete[j].expand != ADDR_LINES - && addr_type_complete[j].expand == cmd->uc_addr_type) - { - STRCPY(IObuff + len, addr_type_complete[j].shortname); - len += (int)STRLEN(IObuff + len); - break; - } - - do { - IObuff[len++] = ' '; - } while (len < 13 - over); - - // Completion - for (j = 0; command_complete[j].expand != 0; ++j) - if (command_complete[j].expand == cmd->uc_compl) - { - STRCPY(IObuff + len, command_complete[j].name); - len += (int)STRLEN(IObuff + len); - break; - } - - do { - IObuff[len++] = ' '; - } while (len < 25 - over); - - IObuff[len] = '\0'; - 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")); -} - - static 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] = 0; - return (char *)IObuff; -} - - static int -uc_scan_attr( - char_u *attr, - size_t len, - long *argt, - long *def, - int *flags, - int *compl, - char_u **compl_arg, - int *addr_type_arg) -{ - char_u *p; - - if (len == 0) - { - emsg(_("E175: No attribute specified")); - return FAIL; - } - - /* First, try the simple attributes (no arguments) */ - if (STRNICMP(attr, "bang", len) == 0) - *argt |= BANG; - else if (STRNICMP(attr, "buffer", len) == 0) - *flags |= UC_BUFFER; - else if (STRNICMP(attr, "register", len) == 0) - *argt |= REGSTR; - else if (STRNICMP(attr, "bar", len) == 0) - *argt |= 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 |= (EXTRA | NOSPC | NEEDARG); - else if (*val == '*') - *argt |= EXTRA; - else if (*val == '?') - *argt |= (EXTRA | NOSPC); - else if (*val == '+') - *argt |= (EXTRA | NEEDARG); - else - goto wrong_nargs; - } - else - { -wrong_nargs: - emsg(_("E176: Invalid number of arguments")); - return FAIL; - } - } - else if (STRNICMP(attr, "range", attrlen) == 0) - { - *argt |= RANGE; - if (vallen == 1 && *val == '%') - *argt |= DFLALL; - else if (val != NULL) - { - p = val; - if (*def >= 0) - { -two_count: - emsg(_("E177: Count cannot be specified twice")); - return FAIL; - } - - *def = getdigits(&p); - *argt |= (ZEROR | NOTADR); - - if (p != val + vallen || vallen == 0) - { -invalid_count: - emsg(_("E178: Invalid default value for count")); - return FAIL; - } - } - } - else if (STRNICMP(attr, "count", attrlen) == 0) - { - *argt |= (COUNT | ZEROR | RANGE | NOTADR); - - 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) - { - emsg(_("E179: argument required for -complete")); - return FAIL; - } - - if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg) - == FAIL) - return FAIL; - } - else if (STRNICMP(attr, "addr", attrlen) == 0) - { - *argt |= RANGE; - if (val == NULL) - { - emsg(_("E179: argument required for -addr")); - return FAIL; - } - if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) - == FAIL) - return FAIL; - if (addr_type_arg != ADDR_LINES) - *argt |= (ZEROR | NOTADR) ; - } - else - { - char_u ch = attr[len]; - attr[len] = '\0'; - semsg(_("E181: Invalid attribute: %s"), attr); - attr[len] = ch; - return FAIL; - } - } - - return OK; -} - -/* - * ":command ..." - */ - static 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; - int addr_type_arg = ADDR_LINES; - 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) - return; - 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_excmd(*p) && !VIM_ISWHITE(*p)) - { - emsg(_("E182: Invalid command name")); - return; - } - 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_excmd(*p)) - { - uc_list(name, end - name); - } - else if (!ASCII_ISUPPER(*name)) - { - emsg(_("E183: User defined commands must start with an uppercase letter")); - return; - } - else if ((name_len == 1 && *name == 'X') - || (name_len <= 4 - && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0)) - { - emsg(_("E841: Reserved name, cannot be used for user defined command")); - return; - } - else - uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, - addr_type_arg, eap->forceit); -} - -/* - * ":comclear" - * Clear all user commands, global and for current buffer. - */ - void -ex_comclear(exarg_T *eap UNUSED) -{ - uc_clear(&ucmds); - uc_clear(&curbuf->b_ucmds); -} - -/* - * Clear all user commands for "gap". - */ - void -uc_clear(garray_T *gap) -{ - int i; - ucmd_T *cmd; - - for (i = 0; i < gap->ga_len; ++i) - { - cmd = USER_CMD_GA(gap, i); - vim_free(cmd->uc_name); - vim_free(cmd->uc_rep); -# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - vim_free(cmd->uc_compl_arg); -# endif - } - ga_clear(gap); -} - - static void -ex_delcommand(exarg_T *eap) -{ - int i = 0; - ucmd_T *cmd = NULL; - int cmp = -1; - garray_T *gap; - - gap = &curbuf->b_ucmds; - for (;;) - { - for (i = 0; i < gap->ga_len; ++i) - { - cmd = USER_CMD_GA(gap, i); - cmp = STRCMP(eap->arg, cmd->uc_name); - if (cmp <= 0) - break; - } - if (gap == &ucmds || cmp == 0) - break; - gap = &ucmds; - } - - if (cmp != 0) - { - semsg(_("E184: No such user-defined command: %s"), eap->arg); - return; - } - - vim_free(cmd->uc_name); - vim_free(cmd->uc_rep); -# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - 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 - */ - 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 += 3; /* "," */ - } - 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++ = '"'; - } - else - { - MB_COPY_CHAR(p, q); - } - } - *q++ = '"'; - *q = 0; - - *lenp = len; - return buf; -} - - static size_t -add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods) -{ - size_t result; - - result = STRLEN(mod_str); - if (*multi_mods) - result += 1; - if (buf != NULL) - { - if (*multi_mods) - STRCAT(buf, " "); - STRCAT(buf, mod_str); - } - - *multi_mods = 1; - - return result; -} - -/* - * 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 & 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 () */ - /* 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[20]; - 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; - - sprintf(num_buf, "%ld", num); - num_len = STRLEN(num_buf); - 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: - { - int multi_mods = 0; - typedef struct { - int *varp; - char *name; - } mod_entry_T; - static mod_entry_T mod_entries[] = { -#ifdef FEAT_BROWSE_CMD - {&cmdmod.browse, "browse"}, -#endif -#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) - {&cmdmod.confirm, "confirm"}, -#endif - {&cmdmod.hide, "hide"}, - {&cmdmod.keepalt, "keepalt"}, - {&cmdmod.keepjumps, "keepjumps"}, - {&cmdmod.keepmarks, "keepmarks"}, - {&cmdmod.keeppatterns, "keeppatterns"}, - {&cmdmod.lockmarks, "lockmarks"}, - {&cmdmod.noswapfile, "noswapfile"}, - {NULL, NULL} - }; - int i; - - result = quote ? 2 : 0; - if (buf != NULL) - { - if (quote) - *buf++ = '"'; - *buf = '\0'; - } - - /* :aboveleft and :leftabove */ - if (cmdmod.split & WSP_ABOVE) - result += add_cmd_modifier(buf, "aboveleft", &multi_mods); - /* :belowright and :rightbelow */ - if (cmdmod.split & WSP_BELOW) - result += add_cmd_modifier(buf, "belowright", &multi_mods); - /* :botright */ - if (cmdmod.split & WSP_BOT) - result += add_cmd_modifier(buf, "botright", &multi_mods); - - /* the modifiers that are simple flags */ - for (i = 0; mod_entries[i].varp != NULL; ++i) - if (*mod_entries[i].varp) - result += add_cmd_modifier(buf, mod_entries[i].name, - &multi_mods); - - /* TODO: How to support :noautocmd? */ -#ifdef HAVE_SANDBOX - /* TODO: How to support :sandbox?*/ -#endif - /* :silent */ - if (msg_silent > 0) - result += add_cmd_modifier(buf, - emsg_silent > 0 ? "silent!" : "silent", &multi_mods); - /* :tab */ - if (cmdmod.tab > 0) - result += add_cmd_modifier(buf, "tab", &multi_mods); - /* :topleft */ - if (cmdmod.split & WSP_TOP) - result += add_cmd_modifier(buf, "topleft", &multi_mods); - /* TODO: How to support :unsilent?*/ - /* :verbose */ - if (p_verbose > 0) - result += add_cmd_modifier(buf, "verbose", &multi_mods); - /* :vertical */ - if (cmdmod.split & WSP_VERT) - result += add_cmd_modifier(buf, "vertical", &multi_mods); - if (quote && buf != NULL) - { - buf += result - 2; - *buf = '"'; - } - 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; -} - - static 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; -#ifdef FEAT_EVAL - sctx_T save_current_sctx = current_sctx; -#endif - - if (eap->cmdidx == CMD_USER) - cmd = USER_CMD(eap->useridx); - else - cmd = USER_CMD_GA(&curbuf->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 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((unsigned)(totlen + 1)); - if (buf == NULL) - { - vim_free(split_buf); - return; - } - } - -#ifdef FEAT_EVAL - current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; -#endif - (void)do_cmdline(buf, eap->getline, eap->cookie, - DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); -#ifdef FEAT_EVAL - current_sctx = save_current_sctx; -#endif - vim_free(buf); - vim_free(split_buf); -} - -# if defined(FEAT_CMDL_COMPL) || defined(PROTO) - static char_u * -get_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) -{ - if (idx < curbuf->b_ucmds.ga_len) - return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; - idx -= curbuf->b_ucmds.ga_len; - if (idx < ucmds.ga_len) - return USER_CMD(idx)->uc_name; - return NULL; -} - -/* - * 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) -{ - return (char_u *)addr_type_complete[idx].name; -} - -/* - * 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"}; - - if (idx >= (int)(sizeof(user_cmd_flags) / sizeof(user_cmd_flags[0]))) - 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 >= (int)(sizeof(user_cmd_nargs) / sizeof(user_cmd_nargs[0]))) - 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) -{ - return (char_u *)command_complete[idx].name; -} -# endif /* FEAT_CMDL_COMPL */ - -/* - * Parse address type argument - */ - int -parse_addr_type_arg( - char_u *value, - int vallen, - long *argt, - int *addr_type_arg) -{ - int i, a, b; - - for (i = 0; addr_type_complete[i].expand != -1; ++i) - { - a = (int)STRLEN(addr_type_complete[i].name) == vallen; - b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; - if (a && b) - { - *addr_type_arg = addr_type_complete[i].expand; - break; - } - } - - if (addr_type_complete[i].expand == -1) - { - char_u *err = value; - - for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++) - ; - err[i] = NUL; - semsg(_("E180: Invalid address type value: %s"), err); - return FAIL; - } - - if (*addr_type_arg != ADDR_LINES) - *argt |= NOTADR; - - return OK; -} - -#endif /* FEAT_USR_CMDS */ - -#if defined(FEAT_USR_CMDS) || defined(FEAT_EVAL) || defined(PROTO) -/* - * 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) && defined(FEAT_CMDL_COMPL) - size_t arglen = 0; -# endif - int i; - int valend = vallen; - - /* 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) && defined(FEAT_CMDL_COMPL) - arglen = vallen - i - 1; -# endif - valend = i; - break; - } - } - - for (i = 0; command_complete[i].expand != 0; ++i) - { - if ((int)STRLEN(command_complete[i].name) == valend - && STRNCMP(value, command_complete[i].name, valend) == 0) - { - *complp = command_complete[i].expand; - if (command_complete[i].expand == EXPAND_BUFFERS) - *argt |= BUFNAME; - else if (command_complete[i].expand == EXPAND_DIRECTORIES - || command_complete[i].expand == EXPAND_FILES) - *argt |= XFILE; - break; - } - } - - if (command_complete[i].expand == 0) - { - semsg(_("E180: Invalid complete value: %s"), value); - return FAIL; - } - -# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST - && arg != NULL) -# else - if (arg != NULL) -# endif - { - emsg(_("E468: Completion argument only allowed for custom completion")); - return FAIL; - } - -# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) - if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) - && arg == NULL) - { - emsg(_("E467: Custom completion requires a function argument")); - return FAIL; - } - - if (arg != NULL) - *compl_arg = vim_strnsave(arg, (int)arglen); -# endif - return OK; -} - - int -cmdcomplete_str_to_type(char_u *complete_str) -{ - int i; - - for (i = 0; command_complete[i].expand != 0; ++i) - if (STRCMP(complete_str, command_complete[i].name) == 0) - return command_complete[i].expand; - - return EXPAND_NOTHING; -} -#endif - static void ex_colorscheme(exarg_T *eap) { diff --git a/src/ex_getln.c b/src/ex_getln.c --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -111,7 +111,7 @@ static int ExpandPackAddDir(char_u *pat, # ifdef FEAT_CMDHIST static char_u *get_history_arg(expand_T *xp, int idx); # endif -# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) +# if defined(FEAT_EVAL) static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, char_u ***file); static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file); # endif @@ -939,7 +939,7 @@ getcmdline_int( { xpc.xp_context = ccline.xp_context; xpc.xp_pattern = ccline.cmdbuff; -# if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL) +# if defined(FEAT_CMDL_COMPL) xpc.xp_arg = ccline.xp_arg; # endif } @@ -4210,7 +4210,7 @@ ExpandInit(expand_T *xp) #endif xp->xp_numfiles = -1; xp->xp_files = NULL; -#if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) xp->xp_arg = NULL; #endif xp->xp_line = NULL; @@ -4879,7 +4879,7 @@ set_cmd_context( { xp->xp_context = ccline.xp_context; xp->xp_pattern = ccline.cmdbuff; -# if defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL) +# if defined(FEAT_CMDL_COMPL) xp->xp_arg = ccline.xp_arg; # endif } @@ -5130,7 +5130,7 @@ ExpandFromContext( char *directories[] = {"syntax", "indent", "ftplugin", NULL}; return ExpandRTDir(pat, 0, num_file, file, directories); } -# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) +# if defined(FEAT_EVAL) if (xp->xp_context == EXPAND_USER_LIST) return ExpandUserList(xp, num_file, file); # endif @@ -5149,7 +5149,7 @@ ExpandFromContext( ret = ExpandSettings(xp, ®match, num_file, file); else if (xp->xp_context == EXPAND_MAPPINGS) ret = ExpandMappings(®match, num_file, file); -# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) +# if defined(FEAT_EVAL) else if (xp->xp_context == EXPAND_USER_DEFINED) ret = ExpandUserDefined(xp, ®match, num_file, file); # endif @@ -5170,13 +5170,11 @@ ExpandFromContext( #ifdef FEAT_CMDHIST {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, #endif -#ifdef FEAT_USR_CMDS {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, {EXPAND_USER_ADDR_TYPE, get_user_cmd_addr_type, FALSE, TRUE}, {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, -#endif #ifdef FEAT_EVAL {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, @@ -5473,7 +5471,7 @@ expand_shellcmd( } -# if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) +# if defined(FEAT_EVAL) /* * Call "user_expand_func()" to invoke a user defined Vim script function and * return the result (either a string or a List). diff --git a/src/feature.h b/src/feature.h --- a/src/feature.h +++ b/src/feature.h @@ -379,10 +379,8 @@ /* * +user_commands Allow the user to define his own commands. + * Now always enabled. */ -#ifdef FEAT_NORMAL -# define FEAT_USR_CMDS -#endif /* * +printer ":hardcopy" command diff --git a/src/macros.h b/src/macros.h --- a/src/macros.h +++ b/src/macros.h @@ -336,3 +336,6 @@ (p) = NULL; \ } \ } while (0) + +/* Wether a command index indicates a user command. */ +#define IS_USER_CMDIDX(idx) ((int)(idx) < 0) diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -1082,10 +1082,8 @@ free_all_mem(void) ui_remove_balloon(); # endif -# if defined(FEAT_USR_CMDS) - /* Clear user commands (before deleting buffers). */ + // Clear user commands (before deleting buffers). ex_comclear(NULL); -# endif # ifdef FEAT_MENU /* Clear menus. */ @@ -1130,7 +1128,9 @@ free_all_mem(void) free_search_patterns(); free_old_sub(); free_last_insert(); +# if defined(FEAT_INS_EXPAND) free_insexpand_stuff(); +# endif free_prev_shellcmd(); free_regexp_stuff(); free_tag_stuff(); diff --git a/src/proto.h b/src/proto.h --- a/src/proto.h +++ b/src/proto.h @@ -227,6 +227,7 @@ void qsort(void *base, size_t elm_count, # endif # include "ui.pro" # include "undo.pro" +# include "usercmd.pro" # include "userfunc.pro" # include "version.pro" # include "window.pro" diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro --- a/src/proto/ex_docmd.pro +++ b/src/proto/ex_docmd.pro @@ -19,16 +19,6 @@ int ends_excmd(int c); char_u *find_nextcmd(char_u *p); char_u *check_nextcmd(char_u *p); char_u *get_command_name(expand_T *xp, int idx); -void ex_comclear(exarg_T *eap); -void uc_clear(garray_T *gap); -char_u *get_user_commands(expand_T *xp, int idx); -char_u *get_user_cmd_addr_type(expand_T *xp, int idx); -char_u *get_user_cmd_flags(expand_T *xp, int idx); -char_u *get_user_cmd_nargs(expand_T *xp, int idx); -char_u *get_user_cmd_complete(expand_T *xp, int idx); -int parse_addr_type_arg(char_u *value, int vallen, long *argt, int *addr_type_arg); -int parse_compl_arg(char_u *value, int vallen, int *complp, long *argt, char_u **compl_arg); -int cmdcomplete_str_to_type(char_u *complete_str); void not_exiting(void); void tabpage_close(int forceit); void tabpage_close_other(tabpage_T *tp, int forceit); diff --git a/src/proto/usercmd.pro b/src/proto/usercmd.pro new file mode 100644 --- /dev/null +++ b/src/proto/usercmd.pro @@ -0,0 +1,18 @@ +/* usercmd.c */ +char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int *compl); +char_u *set_context_in_user_cmd(expand_T *xp, char_u *arg_in); +char_u *get_user_command_name(int idx); +char_u *get_user_commands(expand_T *xp, int idx); +char_u *get_user_cmd_addr_type(expand_T *xp, int idx); +char_u *get_user_cmd_flags(expand_T *xp, int idx); +char_u *get_user_cmd_nargs(expand_T *xp, int idx); +char_u *get_user_cmd_complete(expand_T *xp, int idx); +char *uc_fun_cmd(void); +void ex_command(exarg_T *eap); +void ex_comclear(exarg_T *eap); +void uc_clear(garray_T *gap); +void ex_delcommand(exarg_T *eap); +void do_ucmd(exarg_T *eap); +int parse_compl_arg(char_u *value, int vallen, int *complp, long *argt, char_u **compl_arg); +int cmdcomplete_str_to_type(char_u *complete_str); +/* vim: set ft=c : */ diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -549,7 +549,7 @@ typedef struct expand int xp_context; /* type of expansion */ char_u *xp_pattern; /* start of item to expand */ int xp_pattern_len; /* bytes in xp_pattern before cursor */ -#if defined(FEAT_USR_CMDS) && defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) char_u *xp_arg; /* completion function */ sctx_T xp_script_ctx; /* SCTX for completion function */ #endif @@ -2143,10 +2143,8 @@ struct file_buffer /* First abbreviation local to a buffer. */ mapblock_T *b_first_abbr; #endif -#ifdef FEAT_USR_CMDS - /* User commands local to the buffer. */ + // User commands local to the buffer. garray_T b_ucmds; -#endif /* * start and end of an operator, also used for '[ and '] */ diff --git a/src/usercmd.c b/src/usercmd.c new file mode 100644 --- /dev/null +++ b/src/usercmd.c @@ -0,0 +1,1656 @@ +/* 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 + 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 + int uc_addr_type; // The command's address type +# ifdef FEAT_EVAL + sctx_T uc_script_ctx; // SCTX where the command was defined +# ifdef FEAT_CMDL_COMPL + char_u *uc_compl_arg; // completion argument if any +# endif +# endif +} ucmd_T; + +// List of all user commands. +static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL}; + +#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 for completion. + */ +static struct +{ + int expand; + char *name; +} command_complete[] = +{ + {EXPAND_ARGLIST, "arglist"}, + {EXPAND_AUGROUP, "augroup"}, + {EXPAND_BEHAVE, "behave"}, + {EXPAND_BUFFERS, "buffer"}, + {EXPAND_COLORS, "color"}, + {EXPAND_COMMANDS, "command"}, + {EXPAND_COMPILER, "compiler"}, +#if defined(FEAT_CSCOPE) + {EXPAND_CSCOPE, "cscope"}, +#endif +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + {EXPAND_USER_DEFINED, "custom"}, + {EXPAND_USER_LIST, "customlist"}, +#endif + {EXPAND_DIRECTORIES, "dir"}, + {EXPAND_ENV_VARS, "environment"}, + {EXPAND_EVENTS, "event"}, + {EXPAND_EXPRESSION, "expression"}, + {EXPAND_FILES, "file"}, + {EXPAND_FILES_IN_PATH, "file_in_path"}, + {EXPAND_FILETYPE, "filetype"}, + {EXPAND_FUNCTIONS, "function"}, + {EXPAND_HELP, "help"}, + {EXPAND_HIGHLIGHT, "highlight"}, +#if defined(FEAT_CMDHIST) + {EXPAND_HISTORY, "history"}, +#endif +#if defined(HAVE_LOCALE_H) || defined(X_LOCALE) + {EXPAND_LOCALES, "locale"}, +#endif + {EXPAND_MAPCLEAR, "mapclear"}, + {EXPAND_MAPPINGS, "mapping"}, + {EXPAND_MENUS, "menu"}, + {EXPAND_MESSAGES, "messages"}, + {EXPAND_OWNSYNTAX, "syntax"}, +#if defined(FEAT_PROFILE) + {EXPAND_SYNTIME, "syntime"}, +#endif + {EXPAND_SETTINGS, "option"}, + {EXPAND_PACKADD, "packadd"}, + {EXPAND_SHELLCMD, "shellcmd"}, +#if defined(FEAT_SIGNS) + {EXPAND_SIGN, "sign"}, +#endif + {EXPAND_TAGS, "tag"}, + {EXPAND_TAGS_LISTFILES, "tag_listfiles"}, + {EXPAND_USER, "user"}, + {EXPAND_USER_VARS, "var"}, + {0, NULL} +}; + +/* + * List of names of address types. Must be alphabetical for completion. + */ +static struct +{ + int expand; + char *name; + char *shortname; +} addr_type_complete[] = +{ + {ADDR_ARGUMENTS, "arguments", "arg"}, + {ADDR_LINES, "lines", "line"}, + {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"}, + {ADDR_TABS, "tabs", "tab"}, + {ADDR_BUFFERS, "buffers", "buf"}, + {ADDR_WINDOWS, "windows", "win"}, + {ADDR_QUICKFIX, "quickfix", "qf"}, + {ADDR_OTHER, "other", "?"}, + {-1, NULL, NULL} +}; + +#define UC_BUFFER 1 // -buffer: local to current buffer + +/* + * 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 *compl UNUSED) // 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 = &curbuf->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; + +# ifdef FEAT_CMDL_COMPL + if (compl != NULL) + *compl = 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 +# 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; +} + +#if defined(FEAT_CMDL_COMPL) || defined(PROTO) + + 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; + return NULL; + } + else if (STRNICMP(arg, "nargs", p - arg) == 0) + { + xp->xp_context = EXPAND_USER_NARGS; + xp->xp_pattern = p + 1; + return NULL; + } + else if (STRNICMP(arg, "addr", p - arg) == 0) + { + xp->xp_context = EXPAND_USER_ADDR_TYPE; + xp->xp_pattern = p + 1; + return NULL; + } + 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); +} + + char_u * +get_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) +{ + if (idx < curbuf->b_ucmds.ga_len) + return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; + idx -= curbuf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) + return USER_CMD(idx)->uc_name; + return NULL; +} + +/* + * 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) +{ + return (char_u *)addr_type_complete[idx].name; +} + +/* + * 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" + }; + + if (idx >= (int)(sizeof(user_cmd_flags) / sizeof(user_cmd_flags[0]))) + 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 >= (int)(sizeof(user_cmd_nargs) / sizeof(user_cmd_nargs[0]))) + 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) +{ + return (char_u *)command_complete[idx].name; +} + + int +cmdcomplete_str_to_type(char_u *complete_str) +{ + int i; + + for (i = 0; command_complete[i].expand != 0; ++i) + if (STRCMP(complete_str, command_complete[i].name) == 0) + return command_complete[i].expand; + + return EXPAND_NOTHING; +} + +#endif // FEAT_CMDL_COMPL + +/* + * 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; + + gap = &curbuf->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 & BANG) + { + msg_putchar('!'); + --len; + } + if (a & REGSTR) + { + msg_putchar('"'); + --len; + } + if (gap != &ucmds) + { + msg_putchar('b'); + --len; + } + if (a & TRLBAR) + { + msg_putchar('|'); + --len; + } + while (len-- > 0) + msg_putchar(' '); + + msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D)); + len = (int)STRLEN(cmd->uc_name) + 4; + + do { + msg_putchar(' '); + ++len; + } while (len < 22); + + // "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 & (EXTRA|NOSPC|NEEDARG))) + { + case 0: IObuff[len++] = '0'; break; + case (EXTRA): IObuff[len++] = '*'; break; + case (EXTRA|NOSPC): IObuff[len++] = '?'; break; + case (EXTRA|NEEDARG): IObuff[len++] = '+'; break; + case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break; + } + + do { + IObuff[len++] = ' '; + } while (len < 5 - over); + + // Address / Range + if (a & (RANGE|COUNT)) + { + if (a & COUNT) + { + // -count=N + sprintf((char *)IObuff + len, "%ldc", cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } + else if (a & DFLALL) + IObuff[len++] = '%'; + else if (cmd->uc_def >= 0) + { + // -range=N + sprintf((char *)IObuff + len, "%ld", cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } + else + IObuff[len++] = '.'; + } + + do { + IObuff[len++] = ' '; + } while (len < 8 - over); + + // Address Type + for (j = 0; addr_type_complete[j].expand != -1; ++j) + if (addr_type_complete[j].expand != ADDR_LINES + && addr_type_complete[j].expand == cmd->uc_addr_type) + { + STRCPY(IObuff + len, addr_type_complete[j].shortname); + len += (int)STRLEN(IObuff + len); + break; + } + + do { + IObuff[len++] = ' '; + } while (len < 13 - over); + + // Completion + for (j = 0; command_complete[j].expand != 0; ++j) + if (command_complete[j].expand == cmd->uc_compl) + { + STRCPY(IObuff + len, command_complete[j].name); + len += (int)STRLEN(IObuff + len); + break; + } + + do { + IObuff[len++] = ' '; + } while (len < 25 - over); + + IObuff[len] = '\0'; + 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")); +} + + 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] = 0; + return (char *)IObuff; +} + +/* + * Parse address type argument + */ + static int +parse_addr_type_arg( + char_u *value, + int vallen, + long *argt, + int *addr_type_arg) +{ + int i, a, b; + + for (i = 0; addr_type_complete[i].expand != -1; ++i) + { + a = (int)STRLEN(addr_type_complete[i].name) == vallen; + b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0; + if (a && b) + { + *addr_type_arg = addr_type_complete[i].expand; + break; + } + } + + if (addr_type_complete[i].expand == -1) + { + char_u *err = value; + + for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++) + ; + err[i] = NUL; + semsg(_("E180: Invalid address type value: %s"), err); + return FAIL; + } + + if (*addr_type_arg != ADDR_LINES) + *argt |= NOTADR; + + return OK; +} + +/* + * 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) && defined(FEAT_CMDL_COMPL) + size_t arglen = 0; +# endif + int i; + int valend = vallen; + + // 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) && defined(FEAT_CMDL_COMPL) + arglen = vallen - i - 1; +# endif + valend = i; + break; + } + } + + for (i = 0; command_complete[i].expand != 0; ++i) + { + if ((int)STRLEN(command_complete[i].name) == valend + && STRNCMP(value, command_complete[i].name, valend) == 0) + { + *complp = command_complete[i].expand; + if (command_complete[i].expand == EXPAND_BUFFERS) + *argt |= BUFNAME; + else if (command_complete[i].expand == EXPAND_DIRECTORIES + || command_complete[i].expand == EXPAND_FILES) + *argt |= XFILE; + break; + } + } + + if (command_complete[i].expand == 0) + { + semsg(_("E180: Invalid complete value: %s"), value); + return FAIL; + } + +# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST + && arg != NULL) +# else + if (arg != NULL) +# endif + { + emsg(_("E468: Completion argument only allowed for custom completion")); + return FAIL; + } + +# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) + && arg == NULL) + { + emsg(_("E467: Custom completion requires a function argument")); + return FAIL; + } + + if (arg != NULL) + *compl_arg = vim_strnsave(arg, (int)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 *compl, + char_u **compl_arg, + int *addr_type_arg) +{ + char_u *p; + + if (len == 0) + { + emsg(_("E175: No attribute specified")); + return FAIL; + } + + // First, try the simple attributes (no arguments) + if (STRNICMP(attr, "bang", len) == 0) + *argt |= BANG; + else if (STRNICMP(attr, "buffer", len) == 0) + *flags |= UC_BUFFER; + else if (STRNICMP(attr, "register", len) == 0) + *argt |= REGSTR; + else if (STRNICMP(attr, "bar", len) == 0) + *argt |= 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 |= (EXTRA | NOSPC | NEEDARG); + else if (*val == '*') + *argt |= EXTRA; + else if (*val == '?') + *argt |= (EXTRA | NOSPC); + else if (*val == '+') + *argt |= (EXTRA | NEEDARG); + else + goto wrong_nargs; + } + else + { +wrong_nargs: + emsg(_("E176: Invalid number of arguments")); + return FAIL; + } + } + else if (STRNICMP(attr, "range", attrlen) == 0) + { + *argt |= RANGE; + if (vallen == 1 && *val == '%') + *argt |= DFLALL; + else if (val != NULL) + { + p = val; + if (*def >= 0) + { +two_count: + emsg(_("E177: Count cannot be specified twice")); + return FAIL; + } + + *def = getdigits(&p); + *argt |= (ZEROR | NOTADR); + + if (p != val + vallen || vallen == 0) + { +invalid_count: + emsg(_("E178: Invalid default value for count")); + return FAIL; + } + } + } + else if (STRNICMP(attr, "count", attrlen) == 0) + { + *argt |= (COUNT | ZEROR | RANGE | NOTADR); + + 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) + { + emsg(_("E179: argument required for -complete")); + return FAIL; + } + + if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg) + == FAIL) + return FAIL; + } + else if (STRNICMP(attr, "addr", attrlen) == 0) + { + *argt |= RANGE; + if (val == NULL) + { + emsg(_("E179: argument required for -addr")); + return FAIL; + } + if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) + == FAIL) + return FAIL; + if (addr_type_arg != ADDR_LINES) + *argt |= (ZEROR | NOTADR) ; + } + else + { + char_u ch = attr[len]; + attr[len] = '\0'; + semsg(_("E181: Invalid attribute: %s"), 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, + int 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, FALSE, FALSE, FALSE); + 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, (int)sizeof(ucmd_T), 4); + } + else + gap = &ucmds; + + // Search for the command in the already defined commands. + for (i = 0; i < gap->ga_len; ++i) + { + size_t len; + + cmd = USER_CMD_GA(gap, i); + len = STRLEN(cmd->uc_name); + cmp = STRNCMP(name, cmd->uc_name, name_len); + if (cmp == 0) + { + if (name_len < len) + cmp = -1; + else if (name_len > len) + 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(_("E174: Command already exists: add ! to replace it: %s"), + name); + goto fail; + } + + VIM_CLEAR(cmd->uc_rep); +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + 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) != OK) + goto fail; + if ((p = vim_strnsave(name, (int)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_rep = rep_buf; + cmd->uc_argt = argt; + cmd->uc_def = def; + cmd->uc_compl = compl; +#ifdef FEAT_EVAL + cmd->uc_script_ctx = current_sctx; + cmd->uc_script_ctx.sc_lnum += sourcing_lnum; +# ifdef FEAT_CMDL_COMPL + cmd->uc_compl_arg = compl_arg; +# endif +#endif + cmd->uc_addr_type = addr_type; + + return OK; + +fail: + vim_free(rep_buf); +#if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + vim_free(compl_arg); +#endif + return FAIL; +} + +/* + * ":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; + int addr_type_arg = ADDR_LINES; + 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) + return; + 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_excmd(*p) && !VIM_ISWHITE(*p)) + { + emsg(_("E182: Invalid command name")); + return; + } + 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_excmd(*p)) + { + uc_list(name, end - name); + } + else if (!ASCII_ISUPPER(*name)) + { + emsg(_("E183: User defined commands must start with an uppercase letter")); + return; + } + else if ((name_len == 1 && *name == 'X') + || (name_len <= 4 + && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0)) + { + emsg(_("E841: Reserved name, cannot be used for user defined command")); + return; + } + else + uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, + addr_type_arg, eap->forceit); +} + +/* + * ":comclear" implementation + * Clear all user commands, global and for current buffer. + */ + void +ex_comclear(exarg_T *eap UNUSED) +{ + uc_clear(&ucmds); + uc_clear(&curbuf->b_ucmds); +} + +/* + * Clear all user commands for "gap". + */ + void +uc_clear(garray_T *gap) +{ + int i; + ucmd_T *cmd; + + for (i = 0; i < gap->ga_len; ++i) + { + cmd = USER_CMD_GA(gap, i); + vim_free(cmd->uc_name); + vim_free(cmd->uc_rep); +# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + 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 cmp = -1; + garray_T *gap; + + gap = &curbuf->b_ucmds; + for (;;) + { + for (i = 0; i < gap->ga_len; ++i) + { + cmd = USER_CMD_GA(gap, i); + cmp = STRCMP(eap->arg, cmd->uc_name); + if (cmp <= 0) + break; + } + if (gap == &ucmds || cmp == 0) + break; + gap = &ucmds; + } + + if (cmp != 0) + { + semsg(_("E184: No such user-defined command: %s"), eap->arg); + return; + } + + vim_free(cmd->uc_name); + vim_free(cmd->uc_rep); +# if defined(FEAT_EVAL) && defined(FEAT_CMDL_COMPL) + 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 . + */ + 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 += 3; // "," + } + 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++ = '"'; + } + else + { + MB_COPY_CHAR(p, q); + } + } + *q++ = '"'; + *q = 0; + + *lenp = len; + return buf; +} + + static size_t +add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods) +{ + size_t result; + + result = STRLEN(mod_str); + if (*multi_mods) + result += 1; + if (buf != NULL) + { + if (*multi_mods) + STRCAT(buf, " "); + STRCAT(buf, mod_str); + } + + *multi_mods = 1; + + return result; +} + +/* + * 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 & 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 () + // 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[20]; + 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; + + sprintf(num_buf, "%ld", num); + num_len = STRLEN(num_buf); + 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: + { + int multi_mods = 0; + typedef struct { + int *varp; + char *name; + } mod_entry_T; + static mod_entry_T mod_entries[] = { +#ifdef FEAT_BROWSE_CMD + {&cmdmod.browse, "browse"}, +#endif +#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG) + {&cmdmod.confirm, "confirm"}, +#endif + {&cmdmod.hide, "hide"}, + {&cmdmod.keepalt, "keepalt"}, + {&cmdmod.keepjumps, "keepjumps"}, + {&cmdmod.keepmarks, "keepmarks"}, + {&cmdmod.keeppatterns, "keeppatterns"}, + {&cmdmod.lockmarks, "lockmarks"}, + {&cmdmod.noswapfile, "noswapfile"}, + {NULL, NULL} + }; + int i; + + result = quote ? 2 : 0; + if (buf != NULL) + { + if (quote) + *buf++ = '"'; + *buf = '\0'; + } + + // :aboveleft and :leftabove + if (cmdmod.split & WSP_ABOVE) + result += add_cmd_modifier(buf, "aboveleft", &multi_mods); + // :belowright and :rightbelow + if (cmdmod.split & WSP_BELOW) + result += add_cmd_modifier(buf, "belowright", &multi_mods); + // :botright + if (cmdmod.split & WSP_BOT) + result += add_cmd_modifier(buf, "botright", &multi_mods); + + // the modifiers that are simple flags + for (i = 0; mod_entries[i].varp != NULL; ++i) + if (*mod_entries[i].varp) + result += add_cmd_modifier(buf, mod_entries[i].name, + &multi_mods); + + // TODO: How to support :noautocmd? +#ifdef HAVE_SANDBOX + // TODO: How to support :sandbox? +#endif + // :silent + if (msg_silent > 0) + result += add_cmd_modifier(buf, + emsg_silent > 0 ? "silent!" : "silent", &multi_mods); + // :tab + if (cmdmod.tab > 0) + result += add_cmd_modifier(buf, "tab", &multi_mods); + // :topleft + if (cmdmod.split & WSP_TOP) + result += add_cmd_modifier(buf, "topleft", &multi_mods); + // TODO: How to support :unsilent? + // :verbose + if (p_verbose > 0) + result += add_cmd_modifier(buf, "verbose", &multi_mods); + // :vertical + if (cmdmod.split & WSP_VERT) + result += add_cmd_modifier(buf, "vertical", &multi_mods); + if (quote && buf != NULL) + { + buf += result - 2; + *buf = '"'; + } + 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; +#ifdef FEAT_EVAL + sctx_T save_current_sctx = current_sctx; +#endif + + if (eap->cmdidx == CMD_USER) + cmd = USER_CMD(eap->useridx); + else + cmd = USER_CMD_GA(&curbuf->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 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((unsigned)(totlen + 1)); + if (buf == NULL) + { + vim_free(split_buf); + return; + } + } + +#ifdef FEAT_EVAL + current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; +#endif + (void)do_cmdline(buf, eap->getline, eap->cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); +#ifdef FEAT_EVAL + current_sctx = save_current_sctx; +#endif + vim_free(buf); + vim_free(split_buf); +} diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -672,11 +672,7 @@ static char *(features[]) = #else "-toolbar", #endif -#ifdef FEAT_USR_CMDS "+user_commands", -#else - "-user_commands", -#endif #ifdef FEAT_VARTABS "+vartabs", #else @@ -772,6 +768,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1210, +/**/ 1209, /**/ 1208,