]> granicus.if.org Git - vim/commitdiff
patch 8.1.1210: support for user commands is spread out v8.1.1210
authorBram Moolenaar <Bram@vim.org>
Sat, 27 Apr 2019 11:04:13 +0000 (13:04 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 27 Apr 2019 11:04:13 +0000 (13:04 +0200)
Problem:    Support for user commands is spread out. No good reason to make
            user commands optional.
Solution:   Move user command support to usercmd.c.  Always enable the
            user_commands feature.

29 files changed:
Filelist
runtime/doc/eval.txt
runtime/doc/various.txt
src/Make_bc5.mak
src/Make_cyg_ming.mak
src/Make_dice.mak
src/Make_ivc.mak
src/Make_manx.mak
src/Make_morph.mak
src/Make_mvc.mak
src/Make_sas.mak
src/Make_vms.mms
src/Makefile
src/README.md
src/buffer.c
src/eval.c
src/evalfunc.c
src/ex_cmds.h
src/ex_docmd.c
src/ex_getln.c
src/feature.h
src/macros.h
src/misc2.c
src/proto.h
src/proto/ex_docmd.pro
src/proto/usercmd.pro [new file with mode: 0644]
src/structs.h
src/usercmd.c [new file with mode: 0644]
src/version.c

index fc6374bacbdd9432a745f23aa7c19ecf63c13d05..a6cc76c9f126c3d7d9eb802bc30b0c3f623056ff 100644 (file)
--- 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 \
index 261457128ec5726e29a3cc67dc36c264304f7d8f..ceaa0e0645f02d9c9dc6112d6b1ae9aed5872d6d 100644 (file)
@@ -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|.
index 428a7348e7b7447e45407d414626c7dd09c97d88..923ac240f1019590001f9caa7e3b78c64566aac9 100644 (file)
@@ -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
index 62d03972e87ae8e72c780cc44296a5d8773465a9..a6b4bc96e956f7713ae3f22b406a6e29cabaa8ab 100644 (file)
@@ -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 \
index 3c01e63fe08557d0cb364652c43126ec5504b61d..599b9eeb4c120727cb7e7921dae2e107301c42ea 100644 (file)
@@ -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 \
index 93e960a8db70e2b4ace19699dd4ed8e70b2af99b..89fa58945979b00784912ffc523d1acc8ea1ff21 100644 (file)
@@ -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)
index 08bd87451fba7bab5dbfa2ec63268ccbcb496397..a8b9dffd7b9cd35e3a16c0b827c544c7f4337cd3 100644 (file)
@@ -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
index 6eb7bfa68441e63aa34256ecd33f9507b74db6ae..e53522f06edf33188e9301337f0d29d50daffb7e 100644 (file)
@@ -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
 
index 65cf8447be7e026427756b463e9fb3510b14c958..a5ce62b8c6a4219d0b3a8949f5e0a6d148b5d5df 100644 (file)
@@ -81,6 +81,7 @@ SRC = arabic.c                                                \
        term.c                                                  \
        ui.c                                                    \
        undo.c                                                  \
+       usercmd.c                                               \
        userfunc.c                                              \
        version.c                                               \
        window.c                                                \
index 7da56bc2d83175333d0a08cf8032a12b79eae983..ef2b7f3f9dc507799f567cb5175134a8ba5176d9 100644 (file)
@@ -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) \
index 78d6d2a93fd9cfb3e34854793cc796ad242db811..0d8eb3d9ec464d1c44c3f02f7e03400e203b6365 100644 (file)
@@ -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
index edf77cb6bd4eb33ea8263c5a60cfc3f267adcea2..e25e426fd5d1b63fab91c52845459f67472e24b4 100644 (file)
@@ -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 \
index 40f4b1b90d7211b4e03836db979763101fedb79f..0e719ea12f46fe77211c3fe12c5b2f163f09489f 100644 (file)
@@ -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 \
index f1e7ec2c311b312bb376361e5f42848599fa5eb7..c6bb8ac25c8baee4acaa763f47a8a46b65c6959c 100644 (file)
@@ -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
 
 
index 869150006f08559112894d0b146bf3321586829d..5a30affabec0a2d426d96ce983d8d75391c0e8f1 100644 (file)
@@ -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);
index bf82d02633fa5ee2d504ad2e57491f588461b6d5..a1ad0e673b7e37cf18fa69112a86c0230f37a91b 100644 (file)
@@ -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.
index 228b71ad5c22f378c3ead2bf26efca4e4769ad07..58b8492e569e9927300ccaca9af852217aca1955 100644 (file)
@@ -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
index c080cbef0fec5d55a3347efd6ceb5aaa0176f73f..ac354cda249002a4b598f721dbf04234968f39b5 100644 (file)
@@ -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() */
index 6b2fe4628eb1b7fe44e05392b6ece79d6aa767b9..0086589342877db1c8393dcaa72e8c438eb42e3e 100644 (file)
@@ -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,1438 +5549,9 @@ 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
index e6bb815b45ab8cdaa06bbb34abaab792d6a26670..b2b6f534a260da61a09d96d0508557c4a752d030 100644 (file)
@@ -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).
index b900569a59dd7837952b4068a1794199d2269cba..95701a79a699f90251ba9988ec1422c223c2ed41 100644 (file)
 
 /*
  * +user_commands      Allow the user to define his own commands.
+ *                     Now always enabled.
  */
-#ifdef FEAT_NORMAL
-# define FEAT_USR_CMDS
-#endif
 
 /*
  * +printer            ":hardcopy" command
index 571fed0a7edc1052e9a168838e25c35dec1741b1..8c0e04a56cfe634c810cebd4803183b844d2ff06 100644 (file)
            (p) = NULL; \
        } \
     } while (0)
+
+/* Wether a command index indicates a user command. */
+#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
index 5c5c3ebded46cbfeb0b31a1042c0d0a49ebe4c68..79fe70255cb8022e7d7494ff0c6419d5b42ba64f 100644 (file)
@@ -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();
index 448233ad12b01232969070e8ccedb4d36e22db63..e34af85007f11b0a95072f3235a891d087c10c39 100644 (file)
@@ -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"
index 8f3852a4aaf5669ddf00240b55b97d0e4411b5a5..710f48b781a3bee3f1036cb09f8e248409279347 100644 (file)
@@ -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 (file)
index 0000000..45ae3e7
--- /dev/null
@@ -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 : */
index 104265ecc4dab3f21ed7fd0856f97f45c9ac9104..89749e5c214b2dc05b29d6e742ea84ab24c89634 100644 (file)
@@ -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 (file)
index 0000000..737b2fe
--- /dev/null
@@ -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);
+}
index d024265aaf64775b2c64d80e6e669e90227a6b02..86a074b283b3bae9926be43fa8d0d683849deecd 100644 (file)
@@ -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
@@ -771,6 +767,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1210,
 /**/
     1209,
 /**/