blob: 3bd6fd536ff09cd080da69afe65b8d6178b42dea [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000025 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010026# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028# endif
29} ucmd_T;
30
31// List of all user commands.
32static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
33
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000034// When non-zero it is not allowed to add or remove user commands
35static int ucmd_locked = 0;
36
Bram Moolenaarac9fb182019-04-27 13:04:13 +020037#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
38#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
39
40/*
41 * List of names for completion for ":command" with the EXPAND_ flag.
42 * Must be alphabetical for completion.
43 */
44static struct
45{
46 int expand;
47 char *name;
48} command_complete[] =
49{
50 {EXPAND_ARGLIST, "arglist"},
51 {EXPAND_AUGROUP, "augroup"},
52 {EXPAND_BEHAVE, "behave"},
53 {EXPAND_BUFFERS, "buffer"},
54 {EXPAND_COLORS, "color"},
55 {EXPAND_COMMANDS, "command"},
56 {EXPAND_COMPILER, "compiler"},
57#if defined(FEAT_CSCOPE)
58 {EXPAND_CSCOPE, "cscope"},
59#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020060#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_USER_DEFINED, "custom"},
62 {EXPAND_USER_LIST, "customlist"},
63#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010064 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020065 {EXPAND_DIRECTORIES, "dir"},
66 {EXPAND_ENV_VARS, "environment"},
67 {EXPAND_EVENTS, "event"},
68 {EXPAND_EXPRESSION, "expression"},
69 {EXPAND_FILES, "file"},
70 {EXPAND_FILES_IN_PATH, "file_in_path"},
71 {EXPAND_FILETYPE, "filetype"},
72 {EXPAND_FUNCTIONS, "function"},
73 {EXPAND_HELP, "help"},
74 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020075 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020076#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
77 {EXPAND_LOCALES, "locale"},
78#endif
79 {EXPAND_MAPCLEAR, "mapclear"},
80 {EXPAND_MAPPINGS, "mapping"},
81 {EXPAND_MENUS, "menu"},
82 {EXPAND_MESSAGES, "messages"},
83 {EXPAND_OWNSYNTAX, "syntax"},
84#if defined(FEAT_PROFILE)
85 {EXPAND_SYNTIME, "syntime"},
86#endif
87 {EXPAND_SETTINGS, "option"},
88 {EXPAND_PACKADD, "packadd"},
89 {EXPAND_SHELLCMD, "shellcmd"},
90#if defined(FEAT_SIGNS)
91 {EXPAND_SIGN, "sign"},
92#endif
93 {EXPAND_TAGS, "tag"},
94 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
95 {EXPAND_USER, "user"},
96 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000097#if defined(FEAT_EVAL)
98 {EXPAND_BREAKPOINT, "breakpoint"},
Yegappan Lakshmanan454ce672022-03-24 11:22:13 +000099 {EXPAND_SCRIPTNAMES, "scriptnames"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000100#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200101 {0, NULL}
102};
103
104/*
105 * List of names of address types. Must be alphabetical for completion.
106 */
107static struct
108{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200109 cmd_addr_T expand;
110 char *name;
111 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200112} addr_type_complete[] =
113{
114 {ADDR_ARGUMENTS, "arguments", "arg"},
115 {ADDR_LINES, "lines", "line"},
116 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
117 {ADDR_TABS, "tabs", "tab"},
118 {ADDR_BUFFERS, "buffers", "buf"},
119 {ADDR_WINDOWS, "windows", "win"},
120 {ADDR_QUICKFIX, "quickfix", "qf"},
121 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200122 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200123};
124
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200125/*
126 * Search for a user command that matches "eap->cmd".
127 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
128 * Return a pointer to just after the command.
129 * Return NULL if there is no matching command.
130 */
131 char_u *
132find_ucmd(
133 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000134 char_u *p, // end of the command (possibly including count)
135 int *full, // set to TRUE for a full match
136 expand_T *xp, // used for completion, NULL otherwise
137 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200138{
139 int len = (int)(p - eap->cmd);
140 int j, k, matchlen = 0;
141 ucmd_T *uc;
142 int found = FALSE;
143 int possible = FALSE;
144 char_u *cp, *np; // Point into typed cmd and test name
145 garray_T *gap;
146 int amb_local = FALSE; // Found ambiguous buffer-local command,
147 // only full match global is accepted.
148
149 /*
150 * Look for buffer-local user commands first, then global ones.
151 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000152 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200153 for (;;)
154 {
155 for (j = 0; j < gap->ga_len; ++j)
156 {
157 uc = USER_CMD_GA(gap, j);
158 cp = eap->cmd;
159 np = uc->uc_name;
160 k = 0;
161 while (k < len && *np != NUL && *cp++ == *np++)
162 k++;
163 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
164 {
165 // If finding a second match, the command is ambiguous. But
166 // not if a buffer-local command wasn't a full match and a
167 // global command is a full match.
168 if (k == len && found && *np != NUL)
169 {
170 if (gap == &ucmds)
171 return NULL;
172 amb_local = TRUE;
173 }
174
175 if (!found || (k == len && *np == NUL))
176 {
177 // If we matched up to a digit, then there could
178 // be another command including the digit that we
179 // should use instead.
180 if (k == len)
181 found = TRUE;
182 else
183 possible = TRUE;
184
185 if (gap == &ucmds)
186 eap->cmdidx = CMD_USER;
187 else
188 eap->cmdidx = CMD_USER_BUF;
189 eap->argt = (long)uc->uc_argt;
190 eap->useridx = j;
191 eap->addr_type = uc->uc_addr_type;
192
Bram Moolenaar52111f82019-04-29 21:30:45 +0200193 if (complp != NULL)
194 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200195# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200196 if (xp != NULL)
197 {
198 xp->xp_arg = uc->uc_compl_arg;
199 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100200 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200201 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200202# endif
203 // Do not search for further abbreviations
204 // if this is an exact match.
205 matchlen = k;
206 if (k == len && *np == NUL)
207 {
208 if (full != NULL)
209 *full = TRUE;
210 amb_local = FALSE;
211 break;
212 }
213 }
214 }
215 }
216
217 // Stop if we found a full match or searched all.
218 if (j < gap->ga_len || gap == &ucmds)
219 break;
220 gap = &ucmds;
221 }
222
223 // Only found ambiguous matches.
224 if (amb_local)
225 {
226 if (xp != NULL)
227 xp->xp_context = EXPAND_UNSUCCESSFUL;
228 return NULL;
229 }
230
231 // The match we found may be followed immediately by a number. Move "p"
232 // back to point to it.
233 if (found || possible)
234 return p + (matchlen - len);
235 return p;
236}
237
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000238/*
239 * Set completion context for :command
240 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200241 char_u *
242set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
243{
244 char_u *arg = arg_in;
245 char_u *p;
246
247 // Check for attributes
248 while (*arg == '-')
249 {
250 arg++; // Skip "-"
251 p = skiptowhite(arg);
252 if (*p == NUL)
253 {
254 // Cursor is still in the attribute
255 p = vim_strchr(arg, '=');
256 if (p == NULL)
257 {
258 // No "=", so complete attribute names
259 xp->xp_context = EXPAND_USER_CMD_FLAGS;
260 xp->xp_pattern = arg;
261 return NULL;
262 }
263
264 // For the -complete, -nargs and -addr attributes, we complete
265 // their arguments as well.
266 if (STRNICMP(arg, "complete", p - arg) == 0)
267 {
268 xp->xp_context = EXPAND_USER_COMPLETE;
269 xp->xp_pattern = p + 1;
270 return NULL;
271 }
272 else if (STRNICMP(arg, "nargs", p - arg) == 0)
273 {
274 xp->xp_context = EXPAND_USER_NARGS;
275 xp->xp_pattern = p + 1;
276 return NULL;
277 }
278 else if (STRNICMP(arg, "addr", p - arg) == 0)
279 {
280 xp->xp_context = EXPAND_USER_ADDR_TYPE;
281 xp->xp_pattern = p + 1;
282 return NULL;
283 }
284 return NULL;
285 }
286 arg = skipwhite(p);
287 }
288
289 // After the attributes comes the new command name
290 p = skiptowhite(arg);
291 if (*p == NUL)
292 {
293 xp->xp_context = EXPAND_USER_COMMANDS;
294 xp->xp_pattern = arg;
295 return NULL;
296 }
297
298 // And finally comes a normal command
299 return skipwhite(p);
300}
301
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000302/*
303 * Set the completion context for the argument of a user defined command.
304 */
305 char_u *
306set_context_in_user_cmdarg(
307 char_u *cmd UNUSED,
308 char_u *arg,
309 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000310 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000311 expand_T *xp,
312 int forceit)
313{
314 char_u *p;
315
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000316 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000317 return NULL;
318
319 if (argt & EX_XFILE)
320 {
321 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000322 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000323 return NULL;
324 }
325
326#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000327 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000328 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
329#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000330 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000331 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000332 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000333 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
334 FALSE, CMD_map);
335 // Find start of last argument.
336 p = arg;
337 while (*p)
338 {
339 if (*p == ' ')
340 // argument starts after a space
341 arg = p + 1;
342 else if (*p == '\\' && *(p + 1) != NUL)
343 ++p; // skip over escaped character
344 MB_PTR_ADV(p);
345 }
346 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000347 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000348
349 return NULL;
350}
351
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200352 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200353expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200354{
355 return get_user_commands(NULL, idx - (int)CMD_SIZE);
356}
357
358/*
359 * Function given to ExpandGeneric() to obtain the list of user command names.
360 */
361 char_u *
362get_user_commands(expand_T *xp UNUSED, int idx)
363{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200364 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000365 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200366
367 if (idx < buf->b_ucmds.ga_len)
368 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100369
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200370 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200371 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100372 {
373 int i;
374 char_u *name = USER_CMD(idx)->uc_name;
375
376 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
377 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
378 // global command is overruled by buffer-local one
379 return (char_u *)"";
380 return name;
381 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200382 return NULL;
383}
384
Dominique Pelle748b3082022-01-08 12:41:16 +0000385#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200386/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200387 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
388 * CMD_USER_BUF.
389 * Returns NULL if the command is not found.
390 */
391 char_u *
392get_user_command_name(int idx, int cmdidx)
393{
394 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
395 return USER_CMD(idx)->uc_name;
396 if (cmdidx == CMD_USER_BUF)
397 {
398 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000399 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200400
401 if (idx < buf->b_ucmds.ga_len)
402 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
403 }
404 return NULL;
405}
Dominique Pelle748b3082022-01-08 12:41:16 +0000406#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200407
408/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200409 * Function given to ExpandGeneric() to obtain the list of user address type
410 * names.
411 */
412 char_u *
413get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
414{
415 return (char_u *)addr_type_complete[idx].name;
416}
417
418/*
419 * Function given to ExpandGeneric() to obtain the list of user command
420 * attributes.
421 */
422 char_u *
423get_user_cmd_flags(expand_T *xp UNUSED, int idx)
424{
425 static char *user_cmd_flags[] = {
426 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000427 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200428 };
429
K.Takataeeec2542021-06-02 13:28:16 +0200430 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200431 return NULL;
432 return (char_u *)user_cmd_flags[idx];
433}
434
435/*
436 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
437 */
438 char_u *
439get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
440{
441 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
442
K.Takataeeec2542021-06-02 13:28:16 +0200443 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200444 return NULL;
445 return (char_u *)user_cmd_nargs[idx];
446}
447
448/*
449 * Function given to ExpandGeneric() to obtain the list of values for
450 * -complete.
451 */
452 char_u *
453get_user_cmd_complete(expand_T *xp UNUSED, int idx)
454{
455 return (char_u *)command_complete[idx].name;
456}
457
Dominique Pelle748b3082022-01-08 12:41:16 +0000458#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100459/*
460 * Get the name of completion type "expand" as a string.
461 */
462 char_u *
463cmdcomplete_type_to_str(int expand)
464{
465 int i;
466
467 for (i = 0; command_complete[i].expand != 0; i++)
468 if (command_complete[i].expand == expand)
469 return (char_u *)command_complete[i].name;
470
471 return NULL;
472}
473
474/*
475 * Get the index of completion type "complete_str".
476 * Returns EXPAND_NOTHING if no match found.
477 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200478 int
479cmdcomplete_str_to_type(char_u *complete_str)
480{
481 int i;
482
483 for (i = 0; command_complete[i].expand != 0; ++i)
484 if (STRCMP(complete_str, command_complete[i].name) == 0)
485 return command_complete[i].expand;
486
487 return EXPAND_NOTHING;
488}
Dominique Pelle748b3082022-01-08 12:41:16 +0000489#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200490
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200491/*
492 * List user commands starting with "name[name_len]".
493 */
494 static void
495uc_list(char_u *name, size_t name_len)
496{
497 int i, j;
498 int found = FALSE;
499 ucmd_T *cmd;
500 int len;
501 int over;
502 long a;
503 garray_T *gap;
504
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000505 // don't allow for adding or removing user commands here
506 ++ucmd_locked;
507
Bram Moolenaare38eab22019-12-05 21:50:01 +0100508 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000509 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510 for (;;)
511 {
512 for (i = 0; i < gap->ga_len; ++i)
513 {
514 cmd = USER_CMD_GA(gap, i);
515 a = (long)cmd->uc_argt;
516
517 // Skip commands which don't match the requested prefix and
518 // commands filtered out.
519 if (STRNCMP(name, cmd->uc_name, name_len) != 0
520 || message_filtered(cmd->uc_name))
521 continue;
522
523 // Put out the title first time
524 if (!found)
525 msg_puts_title(_("\n Name Args Address Complete Definition"));
526 found = TRUE;
527 msg_putchar('\n');
528 if (got_int)
529 break;
530
531 // Special cases
532 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200533 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200534 {
535 msg_putchar('!');
536 --len;
537 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200538 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200539 {
540 msg_putchar('"');
541 --len;
542 }
543 if (gap != &ucmds)
544 {
545 msg_putchar('b');
546 --len;
547 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200548 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200549 {
550 msg_putchar('|');
551 --len;
552 }
553 while (len-- > 0)
554 msg_putchar(' ');
555
556 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
557 len = (int)STRLEN(cmd->uc_name) + 4;
558
559 do {
560 msg_putchar(' ');
561 ++len;
562 } while (len < 22);
563
564 // "over" is how much longer the name is than the column width for
565 // the name, we'll try to align what comes after.
566 over = len - 22;
567 len = 0;
568
569 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200570 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200571 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200572 case 0: IObuff[len++] = '0'; break;
573 case (EX_EXTRA): IObuff[len++] = '*'; break;
574 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
575 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
576 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200577 }
578
579 do {
580 IObuff[len++] = ' ';
581 } while (len < 5 - over);
582
583 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200584 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200585 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200586 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200587 {
588 // -count=N
589 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
590 len += (int)STRLEN(IObuff + len);
591 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200592 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200593 IObuff[len++] = '%';
594 else if (cmd->uc_def >= 0)
595 {
596 // -range=N
597 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
598 len += (int)STRLEN(IObuff + len);
599 }
600 else
601 IObuff[len++] = '.';
602 }
603
604 do {
605 IObuff[len++] = ' ';
606 } while (len < 8 - over);
607
608 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200609 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200610 if (addr_type_complete[j].expand != ADDR_LINES
611 && addr_type_complete[j].expand == cmd->uc_addr_type)
612 {
613 STRCPY(IObuff + len, addr_type_complete[j].shortname);
614 len += (int)STRLEN(IObuff + len);
615 break;
616 }
617
618 do {
619 IObuff[len++] = ' ';
620 } while (len < 13 - over);
621
622 // Completion
623 for (j = 0; command_complete[j].expand != 0; ++j)
624 if (command_complete[j].expand == cmd->uc_compl)
625 {
626 STRCPY(IObuff + len, command_complete[j].name);
627 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000628#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000629 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
630 && STRLEN(cmd->uc_compl_arg) < 200)
631 {
632 IObuff[len] = ',';
633 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
634 len += (int)STRLEN(IObuff + len);
635 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000636#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200637 break;
638 }
639
640 do {
641 IObuff[len++] = ' ';
642 } while (len < 25 - over);
643
644 IObuff[len] = '\0';
645 msg_outtrans(IObuff);
646
647 msg_outtrans_special(cmd->uc_rep, FALSE,
648 name_len == 0 ? Columns - 47 : 0);
649#ifdef FEAT_EVAL
650 if (p_verbose > 0)
651 last_set_msg(cmd->uc_script_ctx);
652#endif
653 out_flush();
654 ui_breakcheck();
655 if (got_int)
656 break;
657 }
658 if (gap == &ucmds || i < gap->ga_len)
659 break;
660 gap = &ucmds;
661 }
662
663 if (!found)
664 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000665
666 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200667}
668
669 char *
670uc_fun_cmd(void)
671{
672 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
673 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
674 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
675 0xb9, 0x7f, 0};
676 int i;
677
678 for (i = 0; fcmd[i]; ++i)
679 IObuff[i] = fcmd[i] - 0x40;
680 IObuff[i] = 0;
681 return (char *)IObuff;
682}
683
684/*
685 * Parse address type argument
686 */
687 static int
688parse_addr_type_arg(
689 char_u *value,
690 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200691 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200692{
693 int i, a, b;
694
Bram Moolenaarb7316892019-05-01 18:08:42 +0200695 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200696 {
697 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
698 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
699 if (a && b)
700 {
701 *addr_type_arg = addr_type_complete[i].expand;
702 break;
703 }
704 }
705
Bram Moolenaarb7316892019-05-01 18:08:42 +0200706 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200707 {
708 char_u *err = value;
709
710 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
711 ;
712 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000713 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200714 return FAIL;
715 }
716
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200717 return OK;
718}
719
720/*
721 * Parse a completion argument "value[vallen]".
722 * The detected completion goes in "*complp", argument type in "*argt".
723 * When there is an argument, for function and user defined completion, it's
724 * copied to allocated memory and stored in "*compl_arg".
725 * Returns FAIL if something is wrong.
726 */
727 int
728parse_compl_arg(
729 char_u *value,
730 int vallen,
731 int *complp,
732 long *argt,
733 char_u **compl_arg UNUSED)
734{
735 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200736# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200737 size_t arglen = 0;
738# endif
739 int i;
740 int valend = vallen;
741
742 // Look for any argument part - which is the part after any ','
743 for (i = 0; i < vallen; ++i)
744 {
745 if (value[i] == ',')
746 {
747 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200748# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200749 arglen = vallen - i - 1;
750# endif
751 valend = i;
752 break;
753 }
754 }
755
756 for (i = 0; command_complete[i].expand != 0; ++i)
757 {
758 if ((int)STRLEN(command_complete[i].name) == valend
759 && STRNCMP(value, command_complete[i].name, valend) == 0)
760 {
761 *complp = command_complete[i].expand;
762 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200763 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200764 else if (command_complete[i].expand == EXPAND_DIRECTORIES
765 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200766 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200767 break;
768 }
769 }
770
771 if (command_complete[i].expand == 0)
772 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000773 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200774 return FAIL;
775 }
776
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200777# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200778 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
779 && arg != NULL)
780# else
781 if (arg != NULL)
782# endif
783 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000784 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200785 return FAIL;
786 }
787
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200788# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
790 && arg == NULL)
791 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000792 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200793 return FAIL;
794 }
795
796 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200797 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200798# endif
799 return OK;
800}
801
802/*
803 * Scan attributes in the ":command" command.
804 * Return FAIL when something is wrong.
805 */
806 static int
807uc_scan_attr(
808 char_u *attr,
809 size_t len,
810 long *argt,
811 long *def,
812 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200813 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200814 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200815 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200816{
817 char_u *p;
818
819 if (len == 0)
820 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000821 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200822 return FAIL;
823 }
824
825 // First, try the simple attributes (no arguments)
826 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200827 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200828 else if (STRNICMP(attr, "buffer", len) == 0)
829 *flags |= UC_BUFFER;
830 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200831 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000832 else if (STRNICMP(attr, "keepscript", len) == 0)
833 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200834 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200835 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200836 else
837 {
838 int i;
839 char_u *val = NULL;
840 size_t vallen = 0;
841 size_t attrlen = len;
842
843 // Look for the attribute name - which is the part before any '='
844 for (i = 0; i < (int)len; ++i)
845 {
846 if (attr[i] == '=')
847 {
848 val = &attr[i + 1];
849 vallen = len - i - 1;
850 attrlen = i;
851 break;
852 }
853 }
854
855 if (STRNICMP(attr, "nargs", attrlen) == 0)
856 {
857 if (vallen == 1)
858 {
859 if (*val == '0')
860 // Do nothing - this is the default
861 ;
862 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200863 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200864 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200865 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200866 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200867 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200868 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200869 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870 else
871 goto wrong_nargs;
872 }
873 else
874 {
875wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000876 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877 return FAIL;
878 }
879 }
880 else if (STRNICMP(attr, "range", attrlen) == 0)
881 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200882 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200883 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200884 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200885 else if (val != NULL)
886 {
887 p = val;
888 if (*def >= 0)
889 {
890two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000891 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200892 return FAIL;
893 }
894
895 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200896 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200897
898 if (p != val + vallen || vallen == 0)
899 {
900invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000901 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 return FAIL;
903 }
904 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200905 // default for -range is using buffer lines
906 if (*addr_type_arg == ADDR_NONE)
907 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 }
909 else if (STRNICMP(attr, "count", attrlen) == 0)
910 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200911 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200912 // default for -count is using any number
913 if (*addr_type_arg == ADDR_NONE)
914 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200915
916 if (val != NULL)
917 {
918 p = val;
919 if (*def >= 0)
920 goto two_count;
921
922 *def = getdigits(&p);
923
924 if (p != val + vallen)
925 goto invalid_count;
926 }
927
928 if (*def < 0)
929 *def = 0;
930 }
931 else if (STRNICMP(attr, "complete", attrlen) == 0)
932 {
933 if (val == NULL)
934 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000935 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200936 return FAIL;
937 }
938
Bram Moolenaar52111f82019-04-29 21:30:45 +0200939 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200940 == FAIL)
941 return FAIL;
942 }
943 else if (STRNICMP(attr, "addr", attrlen) == 0)
944 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200945 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200946 if (val == NULL)
947 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000948 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200949 return FAIL;
950 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200951 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200952 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200953 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200954 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200955 }
956 else
957 {
958 char_u ch = attr[len];
959 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000960 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961 attr[len] = ch;
962 return FAIL;
963 }
964 }
965
966 return OK;
967}
968
969/*
970 * Add a user command to the list or replace an existing one.
971 */
972 static int
973uc_add_command(
974 char_u *name,
975 size_t name_len,
976 char_u *rep,
977 long argt,
978 long def,
979 int flags,
980 int compl,
981 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200982 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200983 int force)
984{
985 ucmd_T *cmd = NULL;
986 char_u *p;
987 int i;
988 int cmp = 1;
989 char_u *rep_buf = NULL;
990 garray_T *gap;
991
Bram Moolenaar459fd782019-10-13 16:43:39 +0200992 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200993 if (rep_buf == NULL)
994 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200995 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200996 rep_buf = vim_strsave(rep);
997
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200998 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200999 if (rep_buf == NULL)
1000 return FAIL;
1001 }
1002
1003 // get address of growarray: global or in curbuf
1004 if (flags & UC_BUFFER)
1005 {
1006 gap = &curbuf->b_ucmds;
1007 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001008 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001009 }
1010 else
1011 gap = &ucmds;
1012
1013 // Search for the command in the already defined commands.
1014 for (i = 0; i < gap->ga_len; ++i)
1015 {
1016 size_t len;
1017
1018 cmd = USER_CMD_GA(gap, i);
1019 len = STRLEN(cmd->uc_name);
1020 cmp = STRNCMP(name, cmd->uc_name, name_len);
1021 if (cmp == 0)
1022 {
1023 if (name_len < len)
1024 cmp = -1;
1025 else if (name_len > len)
1026 cmp = 1;
1027 }
1028
1029 if (cmp == 0)
1030 {
1031 // Command can be replaced with "command!" and when sourcing the
1032 // same script again, but only once.
1033 if (!force
1034#ifdef FEAT_EVAL
1035 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1036 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1037#endif
1038 )
1039 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001040 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001041 name);
1042 goto fail;
1043 }
1044
1045 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001046#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001047 VIM_CLEAR(cmd->uc_compl_arg);
1048#endif
1049 break;
1050 }
1051
1052 // Stop as soon as we pass the name to add
1053 if (cmp < 0)
1054 break;
1055 }
1056
1057 // Extend the array unless we're replacing an existing command
1058 if (cmp != 0)
1059 {
1060 if (ga_grow(gap, 1) != OK)
1061 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001062 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001063 goto fail;
1064
1065 cmd = USER_CMD_GA(gap, i);
1066 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1067
1068 ++gap->ga_len;
1069
1070 cmd->uc_name = p;
1071 }
1072
1073 cmd->uc_rep = rep_buf;
1074 cmd->uc_argt = argt;
1075 cmd->uc_def = def;
1076 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001077 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001078 if (flags & UC_VIM9)
1079 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001080 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001081#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001082 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001083 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001084#endif
1085 cmd->uc_addr_type = addr_type;
1086
1087 return OK;
1088
1089fail:
1090 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001091#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001092 vim_free(compl_arg);
1093#endif
1094 return FAIL;
1095}
1096
1097/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001098 * If "p" starts with "{" then read a block of commands until "}".
1099 * Used for ":command" and ":autocmd".
1100 */
1101 char_u *
1102may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1103{
1104 char_u *retp = p;
1105
1106 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1107 && eap->getline != NULL)
1108 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001109 garray_T ga;
1110 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001111
1112 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001113 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001114 return retp;
1115
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001116 // If the argument ends in "}" it must have been concatenated already
1117 // for ISN_EXEC.
1118 if (p[STRLEN(p) - 1] != '}')
1119 // Read lines between '{' and '}'. Does not support nesting or
1120 // here-doc constructs.
1121 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001122 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001123 vim_free(line);
1124 if ((line = eap->getline(':', eap->cookie,
1125 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1126 {
1127 emsg(_(e_missing_rcurly));
1128 break;
1129 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001130 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001131 break;
1132 if (*skipwhite(line) == '}')
1133 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001134 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001135 vim_free(line);
1136 retp = *tofree = ga_concat_strings(&ga, "\n");
1137 ga_clear_strings(&ga);
1138 *flags |= UC_VIM9;
1139 }
1140 return retp;
1141}
1142
1143/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001144 * ":command ..." implementation
1145 */
1146 void
1147ex_command(exarg_T *eap)
1148{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001149 char_u *name;
1150 char_u *end;
1151 char_u *p;
1152 long argt = 0;
1153 long def = -1;
1154 int flags = 0;
1155 int compl = EXPAND_NOTHING;
1156 char_u *compl_arg = NULL;
1157 cmd_addr_T addr_type_arg = ADDR_NONE;
1158 int has_attr = (eap->arg[0] == '-');
1159 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001160
1161 p = eap->arg;
1162
1163 // Check for attributes
1164 while (*p == '-')
1165 {
1166 ++p;
1167 end = skiptowhite(p);
1168 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1169 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001170 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001171 p = skipwhite(end);
1172 }
1173
1174 // Get the name (if any) and skip to the following argument
1175 name = p;
1176 if (ASCII_ISALPHA(*p))
1177 while (ASCII_ISALNUM(*p))
1178 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001179 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001180 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001181 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001182 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001183 }
1184 end = p;
1185 name_len = (int)(end - name);
1186
1187 // If there is nothing after the name, and no attributes were specified,
1188 // we are listing commands
1189 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001190 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001191 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001192 uc_list(name, end - name);
zeertzjq33e54302022-12-19 16:49:27 +00001193 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001194 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001195 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001196 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001197 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001198 else if ((name_len == 1 && *name == 'X')
1199 || (name_len <= 4
1200 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001201 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001202 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001203 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001204 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001205 {
1206 // Some plugins rely on silently ignoring the mistake, only make this
1207 // an error in Vim9 script.
1208 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001209 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001210 else
1211 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001212 (char_u *)_(e_complete_used_without_allowing_arguments),
1213 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001214 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001215 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001216 {
1217 char_u *tofree = NULL;
1218
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001219 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001220
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001221 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1222 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001223 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001224
1225 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001226 }
zeertzjq33e54302022-12-19 16:49:27 +00001227
1228theend:
1229 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001230}
1231
1232/*
1233 * ":comclear" implementation
1234 * Clear all user commands, global and for current buffer.
1235 */
1236 void
1237ex_comclear(exarg_T *eap UNUSED)
1238{
1239 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001240 if (curbuf != NULL)
1241 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001242}
1243
1244/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001245 * If ucmd_locked is set give an error and return TRUE.
1246 * Otherwise return FALSE.
1247 */
1248 static int
1249is_ucmd_locked(void)
1250{
1251 if (ucmd_locked > 0)
1252 {
1253 emsg(_(e_cannot_change_user_commands_while_listing));
1254 return TRUE;
1255 }
1256 return FALSE;
1257}
1258
1259/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001260 * Clear all user commands for "gap".
1261 */
1262 void
1263uc_clear(garray_T *gap)
1264{
1265 int i;
1266 ucmd_T *cmd;
1267
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001268 if (is_ucmd_locked())
1269 return;
1270
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001271 for (i = 0; i < gap->ga_len; ++i)
1272 {
1273 cmd = USER_CMD_GA(gap, i);
1274 vim_free(cmd->uc_name);
1275 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001276# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001277 vim_free(cmd->uc_compl_arg);
1278# endif
1279 }
1280 ga_clear(gap);
1281}
1282
1283/*
1284 * ":delcommand" implementation
1285 */
1286 void
1287ex_delcommand(exarg_T *eap)
1288{
1289 int i = 0;
1290 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001291 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001292 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001293 char_u *arg = eap->arg;
1294 int buffer_only = FALSE;
1295
1296 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1297 {
1298 buffer_only = TRUE;
1299 arg = skipwhite(arg + 7);
1300 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001301
1302 gap = &curbuf->b_ucmds;
1303 for (;;)
1304 {
1305 for (i = 0; i < gap->ga_len; ++i)
1306 {
1307 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001308 res = STRCMP(arg, cmd->uc_name);
1309 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001310 break;
1311 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001312 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001313 break;
1314 gap = &ucmds;
1315 }
1316
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001317 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001318 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001319 semsg(_(buffer_only
1320 ? e_no_such_user_defined_command_in_current_buffer_str
1321 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001322 return;
1323 }
1324
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001325 if (is_ucmd_locked())
1326 return;
1327
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001328 vim_free(cmd->uc_name);
1329 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001330# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001331 vim_free(cmd->uc_compl_arg);
1332# endif
1333
1334 --gap->ga_len;
1335
1336 if (i < gap->ga_len)
1337 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1338}
1339
1340/*
1341 * Split and quote args for <f-args>.
1342 */
1343 static char_u *
1344uc_split_args(char_u *arg, size_t *lenp)
1345{
1346 char_u *buf;
1347 char_u *p;
1348 char_u *q;
1349 int len;
1350
1351 // Precalculate length
1352 p = arg;
1353 len = 2; // Initial and final quotes
1354
1355 while (*p)
1356 {
1357 if (p[0] == '\\' && p[1] == '\\')
1358 {
1359 len += 2;
1360 p += 2;
1361 }
1362 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1363 {
1364 len += 1;
1365 p += 2;
1366 }
1367 else if (*p == '\\' || *p == '"')
1368 {
1369 len += 2;
1370 p += 1;
1371 }
1372 else if (VIM_ISWHITE(*p))
1373 {
1374 p = skipwhite(p);
1375 if (*p == NUL)
1376 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001377 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001378 }
1379 else
1380 {
1381 int charlen = (*mb_ptr2len)(p);
1382
1383 len += charlen;
1384 p += charlen;
1385 }
1386 }
1387
1388 buf = alloc(len + 1);
1389 if (buf == NULL)
1390 {
1391 *lenp = 0;
1392 return buf;
1393 }
1394
1395 p = arg;
1396 q = buf;
1397 *q++ = '"';
1398 while (*p)
1399 {
1400 if (p[0] == '\\' && p[1] == '\\')
1401 {
1402 *q++ = '\\';
1403 *q++ = '\\';
1404 p += 2;
1405 }
1406 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1407 {
1408 *q++ = p[1];
1409 p += 2;
1410 }
1411 else if (*p == '\\' || *p == '"')
1412 {
1413 *q++ = '\\';
1414 *q++ = *p++;
1415 }
1416 else if (VIM_ISWHITE(*p))
1417 {
1418 p = skipwhite(p);
1419 if (*p == NUL)
1420 break;
1421 *q++ = '"';
1422 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001423 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001424 *q++ = '"';
1425 }
1426 else
1427 {
1428 MB_COPY_CHAR(p, q);
1429 }
1430 }
1431 *q++ = '"';
1432 *q = 0;
1433
1434 *lenp = len;
1435 return buf;
1436}
1437
1438 static size_t
1439add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1440{
1441 size_t result;
1442
1443 result = STRLEN(mod_str);
1444 if (*multi_mods)
1445 result += 1;
1446 if (buf != NULL)
1447 {
1448 if (*multi_mods)
1449 STRCAT(buf, " ");
1450 STRCAT(buf, mod_str);
1451 }
1452
1453 *multi_mods = 1;
1454
1455 return result;
1456}
1457
1458/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001459 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001460 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001461 */
1462 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001463add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001464{
1465 size_t result = 0;
1466
1467 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001468 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001469 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1470 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001471 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001472 result += add_cmd_modifier(buf, "belowright", multi_mods);
1473 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001474 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001475 result += add_cmd_modifier(buf, "botright", multi_mods);
1476
1477 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001478 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001479 {
1480 int tabnr = cmod->cmod_tab - 1;
1481
1482 if (tabnr == tabpage_index(curtab))
1483 {
1484 // For compatibility, don't add a tabpage number if it is the same
1485 // as the default number for :tab.
1486 result += add_cmd_modifier(buf, "tab", multi_mods);
1487 }
1488 else
1489 {
1490 char tab_buf[NUMBUFLEN + 3];
1491
1492 sprintf(tab_buf, "%dtab", tabnr);
1493 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1494 }
1495 }
1496
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001497 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001498 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001499 result += add_cmd_modifier(buf, "topleft", multi_mods);
1500 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001501 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001502 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001503 // :horizontal
1504 if (cmod->cmod_split & WSP_HOR)
1505 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001506 return result;
1507}
1508
1509/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001510 * Generate text for the "cmod" command modifiers.
1511 * If "buf" is NULL just return the length.
1512 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001513 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001514produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1515{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001516 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001517 int multi_mods = 0;
1518 int i;
1519 typedef struct {
1520 int flag;
1521 char *name;
1522 } mod_entry_T;
1523 static mod_entry_T mod_entries[] = {
1524#ifdef FEAT_BROWSE_CMD
1525 {CMOD_BROWSE, "browse"},
1526#endif
1527#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1528 {CMOD_CONFIRM, "confirm"},
1529#endif
1530 {CMOD_HIDE, "hide"},
1531 {CMOD_KEEPALT, "keepalt"},
1532 {CMOD_KEEPJUMPS, "keepjumps"},
1533 {CMOD_KEEPMARKS, "keepmarks"},
1534 {CMOD_KEEPPATTERNS, "keeppatterns"},
1535 {CMOD_LOCKMARKS, "lockmarks"},
1536 {CMOD_NOSWAPFILE, "noswapfile"},
1537 {CMOD_UNSILENT, "unsilent"},
1538 {CMOD_NOAUTOCMD, "noautocmd"},
1539#ifdef HAVE_SANDBOX
1540 {CMOD_SANDBOX, "sandbox"},
1541#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001542 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001543 {0, NULL}
1544 };
1545
1546 result = quote ? 2 : 0;
1547 if (buf != NULL)
1548 {
1549 if (quote)
1550 *buf++ = '"';
1551 *buf = '\0';
1552 }
1553
1554 // the modifiers that are simple flags
1555 for (i = 0; mod_entries[i].name != NULL; ++i)
1556 if (cmod->cmod_flags & mod_entries[i].flag)
1557 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1558
1559 // :silent
1560 if (cmod->cmod_flags & CMOD_SILENT)
1561 result += add_cmd_modifier(buf,
1562 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1563 : "silent", &multi_mods);
1564 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001565 if (cmod->cmod_verbose > 0)
1566 {
1567 int verbose_value = cmod->cmod_verbose - 1;
1568
1569 if (verbose_value == 1)
1570 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1571 else
1572 {
1573 char verbose_buf[NUMBUFLEN];
1574
1575 sprintf(verbose_buf, "%dverbose", verbose_value);
1576 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1577 }
1578 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001579 // flags from cmod->cmod_split
dundargocc57b5bc2022-11-02 13:30:51 +00001580 result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001581
Bram Moolenaar02194d22020-10-24 23:08:38 +02001582 if (quote && buf != NULL)
1583 {
1584 buf += result - 2;
1585 *buf = '"';
1586 }
1587 return result;
1588}
1589
1590/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001591 * Check for a <> code in a user command.
1592 * "code" points to the '<'. "len" the length of the <> (inclusive).
1593 * "buf" is where the result is to be added.
1594 * "split_buf" points to a buffer used for splitting, caller should free it.
1595 * "split_len" is the length of what "split_buf" contains.
1596 * Returns the length of the replacement, which has been added to "buf".
1597 * Returns -1 if there was no match, and only the "<" has been copied.
1598 */
1599 static size_t
1600uc_check_code(
1601 char_u *code,
1602 size_t len,
1603 char_u *buf,
1604 ucmd_T *cmd, // the user command we're expanding
1605 exarg_T *eap, // ex arguments
1606 char_u **split_buf,
1607 size_t *split_len)
1608{
1609 size_t result = 0;
1610 char_u *p = code + 1;
1611 size_t l = len - 2;
1612 int quote = 0;
1613 enum {
1614 ct_ARGS,
1615 ct_BANG,
1616 ct_COUNT,
1617 ct_LINE1,
1618 ct_LINE2,
1619 ct_RANGE,
1620 ct_MODS,
1621 ct_REGISTER,
1622 ct_LT,
1623 ct_NONE
1624 } type = ct_NONE;
1625
1626 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1627 {
1628 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1629 p += 2;
1630 l -= 2;
1631 }
1632
1633 ++l;
1634 if (l <= 1)
1635 type = ct_NONE;
1636 else if (STRNICMP(p, "args>", l) == 0)
1637 type = ct_ARGS;
1638 else if (STRNICMP(p, "bang>", l) == 0)
1639 type = ct_BANG;
1640 else if (STRNICMP(p, "count>", l) == 0)
1641 type = ct_COUNT;
1642 else if (STRNICMP(p, "line1>", l) == 0)
1643 type = ct_LINE1;
1644 else if (STRNICMP(p, "line2>", l) == 0)
1645 type = ct_LINE2;
1646 else if (STRNICMP(p, "range>", l) == 0)
1647 type = ct_RANGE;
1648 else if (STRNICMP(p, "lt>", l) == 0)
1649 type = ct_LT;
1650 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1651 type = ct_REGISTER;
1652 else if (STRNICMP(p, "mods>", l) == 0)
1653 type = ct_MODS;
1654
1655 switch (type)
1656 {
1657 case ct_ARGS:
1658 // Simple case first
1659 if (*eap->arg == NUL)
1660 {
1661 if (quote == 1)
1662 {
1663 result = 2;
1664 if (buf != NULL)
1665 STRCPY(buf, "''");
1666 }
1667 else
1668 result = 0;
1669 break;
1670 }
1671
1672 // When specified there is a single argument don't split it.
1673 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001674 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001675 quote = 1;
1676
1677 switch (quote)
1678 {
1679 case 0: // No quoting, no splitting
1680 result = STRLEN(eap->arg);
1681 if (buf != NULL)
1682 STRCPY(buf, eap->arg);
1683 break;
1684 case 1: // Quote, but don't split
1685 result = STRLEN(eap->arg) + 2;
1686 for (p = eap->arg; *p; ++p)
1687 {
1688 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1689 // DBCS can contain \ in a trail byte, skip the
1690 // double-byte character.
1691 ++p;
1692 else
1693 if (*p == '\\' || *p == '"')
1694 ++result;
1695 }
1696
1697 if (buf != NULL)
1698 {
1699 *buf++ = '"';
1700 for (p = eap->arg; *p; ++p)
1701 {
1702 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1703 // DBCS can contain \ in a trail byte, copy the
1704 // double-byte character to avoid escaping.
1705 *buf++ = *p++;
1706 else
1707 if (*p == '\\' || *p == '"')
1708 *buf++ = '\\';
1709 *buf++ = *p;
1710 }
1711 *buf = '"';
1712 }
1713
1714 break;
1715 case 2: // Quote and split (<f-args>)
1716 // This is hard, so only do it once, and cache the result
1717 if (*split_buf == NULL)
1718 *split_buf = uc_split_args(eap->arg, split_len);
1719
1720 result = *split_len;
1721 if (buf != NULL && result != 0)
1722 STRCPY(buf, *split_buf);
1723
1724 break;
1725 }
1726 break;
1727
1728 case ct_BANG:
1729 result = eap->forceit ? 1 : 0;
1730 if (quote)
1731 result += 2;
1732 if (buf != NULL)
1733 {
1734 if (quote)
1735 *buf++ = '"';
1736 if (eap->forceit)
1737 *buf++ = '!';
1738 if (quote)
1739 *buf = '"';
1740 }
1741 break;
1742
1743 case ct_LINE1:
1744 case ct_LINE2:
1745 case ct_RANGE:
1746 case ct_COUNT:
1747 {
1748 char num_buf[20];
1749 long num = (type == ct_LINE1) ? eap->line1 :
1750 (type == ct_LINE2) ? eap->line2 :
1751 (type == ct_RANGE) ? eap->addr_count :
1752 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1753 size_t num_len;
1754
1755 sprintf(num_buf, "%ld", num);
1756 num_len = STRLEN(num_buf);
1757 result = num_len;
1758
1759 if (quote)
1760 result += 2;
1761
1762 if (buf != NULL)
1763 {
1764 if (quote)
1765 *buf++ = '"';
1766 STRCPY(buf, num_buf);
1767 buf += num_len;
1768 if (quote)
1769 *buf = '"';
1770 }
1771
1772 break;
1773 }
1774
1775 case ct_MODS:
1776 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001777 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001778 break;
1779 }
1780
1781 case ct_REGISTER:
1782 result = eap->regname ? 1 : 0;
1783 if (quote)
1784 result += 2;
1785 if (buf != NULL)
1786 {
1787 if (quote)
1788 *buf++ = '\'';
1789 if (eap->regname)
1790 *buf++ = eap->regname;
1791 if (quote)
1792 *buf = '\'';
1793 }
1794 break;
1795
1796 case ct_LT:
1797 result = 1;
1798 if (buf != NULL)
1799 *buf = '<';
1800 break;
1801
1802 default:
1803 // Not recognized: just copy the '<' and return -1.
1804 result = (size_t)-1;
1805 if (buf != NULL)
1806 *buf = '<';
1807 break;
1808 }
1809
1810 return result;
1811}
1812
1813/*
1814 * Execute a user defined command.
1815 */
1816 void
1817do_ucmd(exarg_T *eap)
1818{
1819 char_u *buf;
1820 char_u *p;
1821 char_u *q;
1822
1823 char_u *start;
1824 char_u *end = NULL;
1825 char_u *ksp;
1826 size_t len, totlen;
1827
1828 size_t split_len = 0;
1829 char_u *split_buf = NULL;
1830 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001831 sctx_T save_current_sctx;
1832 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001833#ifdef FEAT_EVAL
1834 int restore_script_version = 0;
1835#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001836
1837 if (eap->cmdidx == CMD_USER)
1838 cmd = USER_CMD(eap->useridx);
1839 else
1840 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1841
1842 /*
1843 * Replace <> in the command by the arguments.
1844 * First round: "buf" is NULL, compute length, allocate "buf".
1845 * Second round: copy result into "buf".
1846 */
1847 buf = NULL;
1848 for (;;)
1849 {
1850 p = cmd->uc_rep; // source
1851 q = buf; // destination
1852 totlen = 0;
1853
1854 for (;;)
1855 {
1856 start = vim_strchr(p, '<');
1857 if (start != NULL)
1858 end = vim_strchr(start + 1, '>');
1859 if (buf != NULL)
1860 {
1861 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1862 ;
1863 if (*ksp == K_SPECIAL
1864 && (start == NULL || ksp < start || end == NULL)
1865 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1866# ifdef FEAT_GUI
1867 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1868# endif
1869 ))
1870 {
1871 // K_SPECIAL has been put in the buffer as K_SPECIAL
1872 // KS_SPECIAL KE_FILLER, like for mappings, but
1873 // do_cmdline() doesn't handle that, so convert it back.
1874 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1875 len = ksp - p;
1876 if (len > 0)
1877 {
1878 mch_memmove(q, p, len);
1879 q += len;
1880 }
1881 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1882 p = ksp + 3;
1883 continue;
1884 }
1885 }
1886
1887 // break if no <item> is found
1888 if (start == NULL || end == NULL)
1889 break;
1890
1891 // Include the '>'
1892 ++end;
1893
1894 // Take everything up to the '<'
1895 len = start - p;
1896 if (buf == NULL)
1897 totlen += len;
1898 else
1899 {
1900 mch_memmove(q, p, len);
1901 q += len;
1902 }
1903
1904 len = uc_check_code(start, end - start, q, cmd, eap,
1905 &split_buf, &split_len);
1906 if (len == (size_t)-1)
1907 {
1908 // no match, continue after '<'
1909 p = start + 1;
1910 len = 1;
1911 }
1912 else
1913 p = end;
1914 if (buf == NULL)
1915 totlen += len;
1916 else
1917 q += len;
1918 }
1919 if (buf != NULL) // second time here, finished
1920 {
1921 STRCPY(q, p);
1922 break;
1923 }
1924
1925 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001926 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001927 if (buf == NULL)
1928 {
1929 vim_free(split_buf);
1930 return;
1931 }
1932 }
1933
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001934 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1935 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001936 restore_current_sctx = TRUE;
1937 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001938 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001939#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001940 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001941 if (cmd->uc_flags & UC_VIM9)
1942 {
1943 // In a {} block variables use Vim9 script rules, even in a legacy
1944 // script.
1945 restore_script_version =
1946 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1947 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1948 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001949#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001950 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001951
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001952 (void)do_cmdline(buf, eap->getline, eap->cookie,
1953 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001954
1955 // Careful: Do not use "cmd" here, it may have become invalid if a user
1956 // command was added.
1957 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001958 {
1959#ifdef FEAT_EVAL
1960 if (restore_script_version != 0)
1961 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1962 restore_script_version;
1963#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001964 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001965 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001966 vim_free(buf);
1967 vim_free(split_buf);
1968}