blob: d8783321d8a914aa7c33c4d7d49c1f635fe07dd1 [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)
1170 return;
1171 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));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001182 return;
1183 }
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))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001191 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001192 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001193 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001194 else if ((name_len == 1 && *name == 'X')
1195 || (name_len <= 4
1196 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001197 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001198 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001199 {
1200 // Some plugins rely on silently ignoring the mistake, only make this
1201 // an error in Vim9 script.
1202 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001203 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001204 else
1205 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001206 (char_u *)_(e_complete_used_without_allowing_arguments),
1207 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001208 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001209 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001210 {
1211 char_u *tofree = NULL;
1212
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001213 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001214
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001215 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1216 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001217 vim_free(tofree);
1218 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001219}
1220
1221/*
1222 * ":comclear" implementation
1223 * Clear all user commands, global and for current buffer.
1224 */
1225 void
1226ex_comclear(exarg_T *eap UNUSED)
1227{
1228 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001229 if (curbuf != NULL)
1230 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001231}
1232
1233/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001234 * If ucmd_locked is set give an error and return TRUE.
1235 * Otherwise return FALSE.
1236 */
1237 static int
1238is_ucmd_locked(void)
1239{
1240 if (ucmd_locked > 0)
1241 {
1242 emsg(_(e_cannot_change_user_commands_while_listing));
1243 return TRUE;
1244 }
1245 return FALSE;
1246}
1247
1248/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001249 * Clear all user commands for "gap".
1250 */
1251 void
1252uc_clear(garray_T *gap)
1253{
1254 int i;
1255 ucmd_T *cmd;
1256
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001257 if (is_ucmd_locked())
1258 return;
1259
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001260 for (i = 0; i < gap->ga_len; ++i)
1261 {
1262 cmd = USER_CMD_GA(gap, i);
1263 vim_free(cmd->uc_name);
1264 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001265# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001266 vim_free(cmd->uc_compl_arg);
1267# endif
1268 }
1269 ga_clear(gap);
1270}
1271
1272/*
1273 * ":delcommand" implementation
1274 */
1275 void
1276ex_delcommand(exarg_T *eap)
1277{
1278 int i = 0;
1279 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001280 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001281 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001282 char_u *arg = eap->arg;
1283 int buffer_only = FALSE;
1284
1285 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1286 {
1287 buffer_only = TRUE;
1288 arg = skipwhite(arg + 7);
1289 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001290
1291 gap = &curbuf->b_ucmds;
1292 for (;;)
1293 {
1294 for (i = 0; i < gap->ga_len; ++i)
1295 {
1296 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001297 res = STRCMP(arg, cmd->uc_name);
1298 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001299 break;
1300 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001301 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001302 break;
1303 gap = &ucmds;
1304 }
1305
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001306 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001307 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001308 semsg(_(buffer_only
1309 ? e_no_such_user_defined_command_in_current_buffer_str
1310 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001311 return;
1312 }
1313
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001314 if (is_ucmd_locked())
1315 return;
1316
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001317 vim_free(cmd->uc_name);
1318 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001319# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001320 vim_free(cmd->uc_compl_arg);
1321# endif
1322
1323 --gap->ga_len;
1324
1325 if (i < gap->ga_len)
1326 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1327}
1328
1329/*
1330 * Split and quote args for <f-args>.
1331 */
1332 static char_u *
1333uc_split_args(char_u *arg, size_t *lenp)
1334{
1335 char_u *buf;
1336 char_u *p;
1337 char_u *q;
1338 int len;
1339
1340 // Precalculate length
1341 p = arg;
1342 len = 2; // Initial and final quotes
1343
1344 while (*p)
1345 {
1346 if (p[0] == '\\' && p[1] == '\\')
1347 {
1348 len += 2;
1349 p += 2;
1350 }
1351 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1352 {
1353 len += 1;
1354 p += 2;
1355 }
1356 else if (*p == '\\' || *p == '"')
1357 {
1358 len += 2;
1359 p += 1;
1360 }
1361 else if (VIM_ISWHITE(*p))
1362 {
1363 p = skipwhite(p);
1364 if (*p == NUL)
1365 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001366 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001367 }
1368 else
1369 {
1370 int charlen = (*mb_ptr2len)(p);
1371
1372 len += charlen;
1373 p += charlen;
1374 }
1375 }
1376
1377 buf = alloc(len + 1);
1378 if (buf == NULL)
1379 {
1380 *lenp = 0;
1381 return buf;
1382 }
1383
1384 p = arg;
1385 q = buf;
1386 *q++ = '"';
1387 while (*p)
1388 {
1389 if (p[0] == '\\' && p[1] == '\\')
1390 {
1391 *q++ = '\\';
1392 *q++ = '\\';
1393 p += 2;
1394 }
1395 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1396 {
1397 *q++ = p[1];
1398 p += 2;
1399 }
1400 else if (*p == '\\' || *p == '"')
1401 {
1402 *q++ = '\\';
1403 *q++ = *p++;
1404 }
1405 else if (VIM_ISWHITE(*p))
1406 {
1407 p = skipwhite(p);
1408 if (*p == NUL)
1409 break;
1410 *q++ = '"';
1411 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001412 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001413 *q++ = '"';
1414 }
1415 else
1416 {
1417 MB_COPY_CHAR(p, q);
1418 }
1419 }
1420 *q++ = '"';
1421 *q = 0;
1422
1423 *lenp = len;
1424 return buf;
1425}
1426
1427 static size_t
1428add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1429{
1430 size_t result;
1431
1432 result = STRLEN(mod_str);
1433 if (*multi_mods)
1434 result += 1;
1435 if (buf != NULL)
1436 {
1437 if (*multi_mods)
1438 STRCAT(buf, " ");
1439 STRCAT(buf, mod_str);
1440 }
1441
1442 *multi_mods = 1;
1443
1444 return result;
1445}
1446
1447/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001448 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001449 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001450 */
1451 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001452add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001453{
1454 size_t result = 0;
1455
1456 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001457 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001458 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1459 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001460 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001461 result += add_cmd_modifier(buf, "belowright", multi_mods);
1462 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001463 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001464 result += add_cmd_modifier(buf, "botright", multi_mods);
1465
1466 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001467 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001468 {
1469 int tabnr = cmod->cmod_tab - 1;
1470
1471 if (tabnr == tabpage_index(curtab))
1472 {
1473 // For compatibility, don't add a tabpage number if it is the same
1474 // as the default number for :tab.
1475 result += add_cmd_modifier(buf, "tab", multi_mods);
1476 }
1477 else
1478 {
1479 char tab_buf[NUMBUFLEN + 3];
1480
1481 sprintf(tab_buf, "%dtab", tabnr);
1482 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1483 }
1484 }
1485
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001486 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001487 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001488 result += add_cmd_modifier(buf, "topleft", multi_mods);
1489 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001490 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001491 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001492 // :horizontal
1493 if (cmod->cmod_split & WSP_HOR)
1494 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001495 return result;
1496}
1497
1498/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001499 * Generate text for the "cmod" command modifiers.
1500 * If "buf" is NULL just return the length.
1501 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001502 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001503produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1504{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001505 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001506 int multi_mods = 0;
1507 int i;
1508 typedef struct {
1509 int flag;
1510 char *name;
1511 } mod_entry_T;
1512 static mod_entry_T mod_entries[] = {
1513#ifdef FEAT_BROWSE_CMD
1514 {CMOD_BROWSE, "browse"},
1515#endif
1516#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1517 {CMOD_CONFIRM, "confirm"},
1518#endif
1519 {CMOD_HIDE, "hide"},
1520 {CMOD_KEEPALT, "keepalt"},
1521 {CMOD_KEEPJUMPS, "keepjumps"},
1522 {CMOD_KEEPMARKS, "keepmarks"},
1523 {CMOD_KEEPPATTERNS, "keeppatterns"},
1524 {CMOD_LOCKMARKS, "lockmarks"},
1525 {CMOD_NOSWAPFILE, "noswapfile"},
1526 {CMOD_UNSILENT, "unsilent"},
1527 {CMOD_NOAUTOCMD, "noautocmd"},
1528#ifdef HAVE_SANDBOX
1529 {CMOD_SANDBOX, "sandbox"},
1530#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001531 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001532 {0, NULL}
1533 };
1534
1535 result = quote ? 2 : 0;
1536 if (buf != NULL)
1537 {
1538 if (quote)
1539 *buf++ = '"';
1540 *buf = '\0';
1541 }
1542
1543 // the modifiers that are simple flags
1544 for (i = 0; mod_entries[i].name != NULL; ++i)
1545 if (cmod->cmod_flags & mod_entries[i].flag)
1546 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1547
1548 // :silent
1549 if (cmod->cmod_flags & CMOD_SILENT)
1550 result += add_cmd_modifier(buf,
1551 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1552 : "silent", &multi_mods);
1553 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001554 if (cmod->cmod_verbose > 0)
1555 {
1556 int verbose_value = cmod->cmod_verbose - 1;
1557
1558 if (verbose_value == 1)
1559 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1560 else
1561 {
1562 char verbose_buf[NUMBUFLEN];
1563
1564 sprintf(verbose_buf, "%dverbose", verbose_value);
1565 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1566 }
1567 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001568 // flags from cmod->cmod_split
dundargocc57b5bc2022-11-02 13:30:51 +00001569 result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001570
Bram Moolenaar02194d22020-10-24 23:08:38 +02001571 if (quote && buf != NULL)
1572 {
1573 buf += result - 2;
1574 *buf = '"';
1575 }
1576 return result;
1577}
1578
1579/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001580 * Check for a <> code in a user command.
1581 * "code" points to the '<'. "len" the length of the <> (inclusive).
1582 * "buf" is where the result is to be added.
1583 * "split_buf" points to a buffer used for splitting, caller should free it.
1584 * "split_len" is the length of what "split_buf" contains.
1585 * Returns the length of the replacement, which has been added to "buf".
1586 * Returns -1 if there was no match, and only the "<" has been copied.
1587 */
1588 static size_t
1589uc_check_code(
1590 char_u *code,
1591 size_t len,
1592 char_u *buf,
1593 ucmd_T *cmd, // the user command we're expanding
1594 exarg_T *eap, // ex arguments
1595 char_u **split_buf,
1596 size_t *split_len)
1597{
1598 size_t result = 0;
1599 char_u *p = code + 1;
1600 size_t l = len - 2;
1601 int quote = 0;
1602 enum {
1603 ct_ARGS,
1604 ct_BANG,
1605 ct_COUNT,
1606 ct_LINE1,
1607 ct_LINE2,
1608 ct_RANGE,
1609 ct_MODS,
1610 ct_REGISTER,
1611 ct_LT,
1612 ct_NONE
1613 } type = ct_NONE;
1614
1615 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1616 {
1617 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1618 p += 2;
1619 l -= 2;
1620 }
1621
1622 ++l;
1623 if (l <= 1)
1624 type = ct_NONE;
1625 else if (STRNICMP(p, "args>", l) == 0)
1626 type = ct_ARGS;
1627 else if (STRNICMP(p, "bang>", l) == 0)
1628 type = ct_BANG;
1629 else if (STRNICMP(p, "count>", l) == 0)
1630 type = ct_COUNT;
1631 else if (STRNICMP(p, "line1>", l) == 0)
1632 type = ct_LINE1;
1633 else if (STRNICMP(p, "line2>", l) == 0)
1634 type = ct_LINE2;
1635 else if (STRNICMP(p, "range>", l) == 0)
1636 type = ct_RANGE;
1637 else if (STRNICMP(p, "lt>", l) == 0)
1638 type = ct_LT;
1639 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1640 type = ct_REGISTER;
1641 else if (STRNICMP(p, "mods>", l) == 0)
1642 type = ct_MODS;
1643
1644 switch (type)
1645 {
1646 case ct_ARGS:
1647 // Simple case first
1648 if (*eap->arg == NUL)
1649 {
1650 if (quote == 1)
1651 {
1652 result = 2;
1653 if (buf != NULL)
1654 STRCPY(buf, "''");
1655 }
1656 else
1657 result = 0;
1658 break;
1659 }
1660
1661 // When specified there is a single argument don't split it.
1662 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001663 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001664 quote = 1;
1665
1666 switch (quote)
1667 {
1668 case 0: // No quoting, no splitting
1669 result = STRLEN(eap->arg);
1670 if (buf != NULL)
1671 STRCPY(buf, eap->arg);
1672 break;
1673 case 1: // Quote, but don't split
1674 result = STRLEN(eap->arg) + 2;
1675 for (p = eap->arg; *p; ++p)
1676 {
1677 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1678 // DBCS can contain \ in a trail byte, skip the
1679 // double-byte character.
1680 ++p;
1681 else
1682 if (*p == '\\' || *p == '"')
1683 ++result;
1684 }
1685
1686 if (buf != NULL)
1687 {
1688 *buf++ = '"';
1689 for (p = eap->arg; *p; ++p)
1690 {
1691 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1692 // DBCS can contain \ in a trail byte, copy the
1693 // double-byte character to avoid escaping.
1694 *buf++ = *p++;
1695 else
1696 if (*p == '\\' || *p == '"')
1697 *buf++ = '\\';
1698 *buf++ = *p;
1699 }
1700 *buf = '"';
1701 }
1702
1703 break;
1704 case 2: // Quote and split (<f-args>)
1705 // This is hard, so only do it once, and cache the result
1706 if (*split_buf == NULL)
1707 *split_buf = uc_split_args(eap->arg, split_len);
1708
1709 result = *split_len;
1710 if (buf != NULL && result != 0)
1711 STRCPY(buf, *split_buf);
1712
1713 break;
1714 }
1715 break;
1716
1717 case ct_BANG:
1718 result = eap->forceit ? 1 : 0;
1719 if (quote)
1720 result += 2;
1721 if (buf != NULL)
1722 {
1723 if (quote)
1724 *buf++ = '"';
1725 if (eap->forceit)
1726 *buf++ = '!';
1727 if (quote)
1728 *buf = '"';
1729 }
1730 break;
1731
1732 case ct_LINE1:
1733 case ct_LINE2:
1734 case ct_RANGE:
1735 case ct_COUNT:
1736 {
1737 char num_buf[20];
1738 long num = (type == ct_LINE1) ? eap->line1 :
1739 (type == ct_LINE2) ? eap->line2 :
1740 (type == ct_RANGE) ? eap->addr_count :
1741 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1742 size_t num_len;
1743
1744 sprintf(num_buf, "%ld", num);
1745 num_len = STRLEN(num_buf);
1746 result = num_len;
1747
1748 if (quote)
1749 result += 2;
1750
1751 if (buf != NULL)
1752 {
1753 if (quote)
1754 *buf++ = '"';
1755 STRCPY(buf, num_buf);
1756 buf += num_len;
1757 if (quote)
1758 *buf = '"';
1759 }
1760
1761 break;
1762 }
1763
1764 case ct_MODS:
1765 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001766 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001767 break;
1768 }
1769
1770 case ct_REGISTER:
1771 result = eap->regname ? 1 : 0;
1772 if (quote)
1773 result += 2;
1774 if (buf != NULL)
1775 {
1776 if (quote)
1777 *buf++ = '\'';
1778 if (eap->regname)
1779 *buf++ = eap->regname;
1780 if (quote)
1781 *buf = '\'';
1782 }
1783 break;
1784
1785 case ct_LT:
1786 result = 1;
1787 if (buf != NULL)
1788 *buf = '<';
1789 break;
1790
1791 default:
1792 // Not recognized: just copy the '<' and return -1.
1793 result = (size_t)-1;
1794 if (buf != NULL)
1795 *buf = '<';
1796 break;
1797 }
1798
1799 return result;
1800}
1801
1802/*
1803 * Execute a user defined command.
1804 */
1805 void
1806do_ucmd(exarg_T *eap)
1807{
1808 char_u *buf;
1809 char_u *p;
1810 char_u *q;
1811
1812 char_u *start;
1813 char_u *end = NULL;
1814 char_u *ksp;
1815 size_t len, totlen;
1816
1817 size_t split_len = 0;
1818 char_u *split_buf = NULL;
1819 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001820 sctx_T save_current_sctx;
1821 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001822#ifdef FEAT_EVAL
1823 int restore_script_version = 0;
1824#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001825
1826 if (eap->cmdidx == CMD_USER)
1827 cmd = USER_CMD(eap->useridx);
1828 else
1829 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1830
1831 /*
1832 * Replace <> in the command by the arguments.
1833 * First round: "buf" is NULL, compute length, allocate "buf".
1834 * Second round: copy result into "buf".
1835 */
1836 buf = NULL;
1837 for (;;)
1838 {
1839 p = cmd->uc_rep; // source
1840 q = buf; // destination
1841 totlen = 0;
1842
1843 for (;;)
1844 {
1845 start = vim_strchr(p, '<');
1846 if (start != NULL)
1847 end = vim_strchr(start + 1, '>');
1848 if (buf != NULL)
1849 {
1850 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1851 ;
1852 if (*ksp == K_SPECIAL
1853 && (start == NULL || ksp < start || end == NULL)
1854 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1855# ifdef FEAT_GUI
1856 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1857# endif
1858 ))
1859 {
1860 // K_SPECIAL has been put in the buffer as K_SPECIAL
1861 // KS_SPECIAL KE_FILLER, like for mappings, but
1862 // do_cmdline() doesn't handle that, so convert it back.
1863 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1864 len = ksp - p;
1865 if (len > 0)
1866 {
1867 mch_memmove(q, p, len);
1868 q += len;
1869 }
1870 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1871 p = ksp + 3;
1872 continue;
1873 }
1874 }
1875
1876 // break if no <item> is found
1877 if (start == NULL || end == NULL)
1878 break;
1879
1880 // Include the '>'
1881 ++end;
1882
1883 // Take everything up to the '<'
1884 len = start - p;
1885 if (buf == NULL)
1886 totlen += len;
1887 else
1888 {
1889 mch_memmove(q, p, len);
1890 q += len;
1891 }
1892
1893 len = uc_check_code(start, end - start, q, cmd, eap,
1894 &split_buf, &split_len);
1895 if (len == (size_t)-1)
1896 {
1897 // no match, continue after '<'
1898 p = start + 1;
1899 len = 1;
1900 }
1901 else
1902 p = end;
1903 if (buf == NULL)
1904 totlen += len;
1905 else
1906 q += len;
1907 }
1908 if (buf != NULL) // second time here, finished
1909 {
1910 STRCPY(q, p);
1911 break;
1912 }
1913
1914 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001915 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001916 if (buf == NULL)
1917 {
1918 vim_free(split_buf);
1919 return;
1920 }
1921 }
1922
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001923 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1924 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001925 restore_current_sctx = TRUE;
1926 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001927 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001928#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001929 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001930 if (cmd->uc_flags & UC_VIM9)
1931 {
1932 // In a {} block variables use Vim9 script rules, even in a legacy
1933 // script.
1934 restore_script_version =
1935 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1936 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1937 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001938#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001939 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001940
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001941 (void)do_cmdline(buf, eap->getline, eap->cookie,
1942 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001943
1944 // Careful: Do not use "cmd" here, it may have become invalid if a user
1945 // command was added.
1946 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001947 {
1948#ifdef FEAT_EVAL
1949 if (restore_script_version != 0)
1950 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1951 restore_script_version;
1952#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001953 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001954 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001955 vim_free(buf);
1956 vim_free(split_buf);
1957}