summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Filelist2
-rw-r--r--runtime/doc/eval.txt4
-rw-r--r--runtime/doc/various.txt3
-rw-r--r--src/Make_bc5.mak1
-rw-r--r--src/Make_cyg_ming.mak1
-rw-r--r--src/Make_dice.mak4
-rw-r--r--src/Make_ivc.mak5
-rw-r--r--src/Make_manx.mak6
-rw-r--r--src/Make_morph.mak1
-rw-r--r--src/Make_mvc.mak4
-rw-r--r--src/Make_sas.mak5
-rw-r--r--src/Make_vms.mms14
-rw-r--r--src/Makefile10
-rw-r--r--src/README.md7
-rw-r--r--src/buffer.c6
-rw-r--r--src/eval.c4
-rw-r--r--src/evalfunc.c2
-rw-r--r--src/ex_cmds.h6
-rw-r--r--src/ex_docmd.c1705
-rw-r--r--src/ex_getln.c16
-rw-r--r--src/feature.h4
-rw-r--r--src/macros.h3
-rw-r--r--src/misc2.c6
-rw-r--r--src/proto.h1
-rw-r--r--src/proto/ex_docmd.pro10
-rw-r--r--src/proto/usercmd.pro18
-rw-r--r--src/structs.h6
-rw-r--r--src/usercmd.c1656
-rw-r--r--src/version.c6
29 files changed, 1778 insertions, 1738 deletions
diff --git a/Filelist b/Filelist
index fc6374bac..a6cc76c9f 100644
--- 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
index 261457128..ceaa0e064 100644
--- 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
index 428a7348e..923ac240f 100644
--- 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
index 62d03972e..a6b4bc96e 100644
--- 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
index 3c01e63fe..599b9eeb4 100644
--- 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
index 93e960a8d..89fa58945 100644
--- 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
index 08bd87451..a8b9dffd7 100644
--- 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
index 6eb7bfa68..e53522f06 100644
--- 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
index 65cf8447b..a5ce62b8c 100644
--- 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
index 7da56bc2d..ef2b7f3f9 100644
--- 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 @@ $(OUTDIR)/ui.obj: $(OUTDIR) ui.c $(INCL)
$(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
index 78d6d2a93..0d8eb3d9e 100644
--- 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
index edf77cb6b..e25e426fd 100644
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -2,7 +2,7 @@
# Makefile for Vim on OpenVMS
#
# Maintainer: Zoltan Arpadffy <arpadffy@polarhome.com>
-# 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 blowfish.c buffer.c charset.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 blob.obj blowfish.obj buffer.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 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 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
index 40f4b1b90..0e719ea12 100644
--- 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/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/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
index f1e7ec2c3..c6bb8ac25 100644
--- a/src/README.md
+++ b/src/README.md
@@ -28,6 +28,7 @@ buffer.c | manipulating buffers (loaded files)
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 a swapfile
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
index 869150006..5a30affab 100644
--- 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
index bf82d0263..a1ad0e673 100644
--- 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
index 228b71ad5..58b8492e5 100644
--- 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
index c080cbef0..ac354cda2 100644
--- 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
index 6b2fe4628..008658934 100644
--- 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 *eap);
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 UNUSED)
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 UNUSED)
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)
- int compl = EXPAND_NOTHING;
-#endif
#ifdef FEAT_CMDL_COMPL
+ int compl = EXPAND_NOTHING;
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 */
+ ea.cmdidx = CMD_SIZE; // ambiguous user command
}
-#endif
}
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 <f-args>
- */
- static char_u *
-uc_split_args(char_u *arg, size_t *lenp)
-{
- char_u *buf;
- char_u *p;
- char_u *q;
- int len;
-
- /* Precalculate length */
- p = arg;
- len = 2; /* Initial and final quotes */
-
- while (*p)
- {
- if (p[0] == '\\' && p[1] == '\\')
- {
- len += 2;
- p += 2;
- }
- else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
- {
- len += 1;
- p += 2;
- }
- else if (*p == '\\' || *p == '"')
- {
- len += 2;
- p += 1;
- }
- else if (VIM_ISWHITE(*p))
- {
- p = skipwhite(p);
- if (*p == NUL)
- break;
- len += 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 (<f-args>) */
- /* This is hard, so only do it once, and cache the result */
- if (*split_buf == NULL)
- *split_buf = uc_split_args(eap->arg, split_len);
-
- result = *split_len;
- if (buf != NULL && result != 0)
- STRCPY(buf, *split_buf);
-
- break;
- }
- break;
-
- case ct_BANG:
- result = eap->forceit ? 1 : 0;
- if (quote)
- result += 2;
- if (buf != NULL)
- {
- if (quote)
- *buf++ = '"';
- if (eap->forceit)
- *buf++ = '!';
- if (quote)
- *buf = '"';
- }
- break;
-
- case ct_LINE1:
- case ct_LINE2:
- case ct_RANGE:
- case ct_COUNT:
- {
- char num_buf[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 <item> is found */
- if (start == NULL || end == NULL)
- break;
-
- /* Include the '>' */
- ++end;
-
- /* Take everything up to the '<' */
- len = start - p;
- if (buf == NULL)
- totlen += len;
- else
- {
- mch_memmove(q, p, len);
- q += len;
- }
-
- len = uc_check_code(start, end - start, q, cmd, eap,
- &split_buf, &split_len);
- if (len == (size_t)-1)
- {
- /* no match, continue after '<' */
- p = start + 1;
- len = 1;
- }
- else
- p = end;
- if (buf == NULL)
- totlen += len;
- else
- q += len;
- }
- if (buf != NULL) /* second time here, finished */
- {
- STRCPY(q, p);
- break;
- }
-
- totlen += STRLEN(p); /* Add on the trailing characters */
- buf = alloc((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
index e6bb815b4..b2b6f534a 100644
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -111,7 +111,7 @@ static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file);
# 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, &regmatch, num_file, file);
else if (xp->xp_context == EXPAND_MAPPINGS)
ret = ExpandMappings(&regmatch, 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, &regmatch, 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
index b900569a5..95701a79a 100644
--- 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
index 571fed0a7..8c0e04a56 100644
--- 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
index 5c5c3ebde..79fe70255 100644
--- 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
index 448233ad1..e34af8500 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -227,6 +227,7 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void
# 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
index 8f3852a4a..710f48b78 100644
--- 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
index 000000000..45ae3e7fe
--- /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
index 104265ecc..89749e5c2 100644
--- 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
index 000000000..737b2feef
--- /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 <f-args>.
+ */
+ static char_u *
+uc_split_args(char_u *arg, size_t *lenp)
+{
+ char_u *buf;
+ char_u *p;
+ char_u *q;
+ int len;
+
+ // Precalculate length
+ p = arg;
+ len = 2; // Initial and final quotes
+
+ while (*p)
+ {
+ if (p[0] == '\\' && p[1] == '\\')
+ {
+ len += 2;
+ p += 2;
+ }
+ else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
+ {
+ len += 1;
+ p += 2;
+ }
+ else if (*p == '\\' || *p == '"')
+ {
+ len += 2;
+ p += 1;
+ }
+ else if (VIM_ISWHITE(*p))
+ {
+ p = skipwhite(p);
+ if (*p == NUL)
+ break;
+ len += 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 (<f-args>)
+ // This is hard, so only do it once, and cache the result
+ if (*split_buf == NULL)
+ *split_buf = uc_split_args(eap->arg, split_len);
+
+ result = *split_len;
+ if (buf != NULL && result != 0)
+ STRCPY(buf, *split_buf);
+
+ break;
+ }
+ break;
+
+ case ct_BANG:
+ result = eap->forceit ? 1 : 0;
+ if (quote)
+ result += 2;
+ if (buf != NULL)
+ {
+ if (quote)
+ *buf++ = '"';
+ if (eap->forceit)
+ *buf++ = '!';
+ if (quote)
+ *buf = '"';
+ }
+ break;
+
+ case ct_LINE1:
+ case ct_LINE2:
+ case ct_RANGE:
+ case ct_COUNT:
+ {
+ char num_buf[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 <item> is found
+ if (start == NULL || end == NULL)
+ break;
+
+ // Include the '>'
+ ++end;
+
+ // Take everything up to the '<'
+ len = start - p;
+ if (buf == NULL)
+ totlen += len;
+ else
+ {
+ mch_memmove(q, p, len);
+ q += len;
+ }
+
+ len = uc_check_code(start, end - start, q, cmd, eap,
+ &split_buf, &split_len);
+ if (len == (size_t)-1)
+ {
+ // no match, continue after '<'
+ p = start + 1;
+ len = 1;
+ }
+ else
+ p = end;
+ if (buf == NULL)
+ totlen += len;
+ else
+ q += len;
+ }
+ if (buf != NULL) // second time here, finished
+ {
+ STRCPY(q, p);
+ break;
+ }
+
+ totlen += STRLEN(p); // Add on the trailing characters
+ buf = alloc((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
index d024265aa..86a074b28 100644
--- 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,