blob: 70dbbb03badcf56af8299d7cef054c3c196b2bd6 [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 Moolenaar9b8d6222020-12-28 18:26:00 +010025# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020026 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027# endif
28} ucmd_T;
29
30// List of all user commands.
31static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
32
33#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
34#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
35
36/*
37 * List of names for completion for ":command" with the EXPAND_ flag.
38 * Must be alphabetical for completion.
39 */
40static struct
41{
42 int expand;
43 char *name;
44} command_complete[] =
45{
46 {EXPAND_ARGLIST, "arglist"},
47 {EXPAND_AUGROUP, "augroup"},
48 {EXPAND_BEHAVE, "behave"},
49 {EXPAND_BUFFERS, "buffer"},
50 {EXPAND_COLORS, "color"},
51 {EXPAND_COMMANDS, "command"},
52 {EXPAND_COMPILER, "compiler"},
53#if defined(FEAT_CSCOPE)
54 {EXPAND_CSCOPE, "cscope"},
55#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020056#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020057 {EXPAND_USER_DEFINED, "custom"},
58 {EXPAND_USER_LIST, "customlist"},
59#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010060 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_DIRECTORIES, "dir"},
62 {EXPAND_ENV_VARS, "environment"},
63 {EXPAND_EVENTS, "event"},
64 {EXPAND_EXPRESSION, "expression"},
65 {EXPAND_FILES, "file"},
66 {EXPAND_FILES_IN_PATH, "file_in_path"},
67 {EXPAND_FILETYPE, "filetype"},
68 {EXPAND_FUNCTIONS, "function"},
69 {EXPAND_HELP, "help"},
70 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020071 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020072#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
73 {EXPAND_LOCALES, "locale"},
74#endif
75 {EXPAND_MAPCLEAR, "mapclear"},
76 {EXPAND_MAPPINGS, "mapping"},
77 {EXPAND_MENUS, "menu"},
78 {EXPAND_MESSAGES, "messages"},
79 {EXPAND_OWNSYNTAX, "syntax"},
80#if defined(FEAT_PROFILE)
81 {EXPAND_SYNTIME, "syntime"},
82#endif
83 {EXPAND_SETTINGS, "option"},
84 {EXPAND_PACKADD, "packadd"},
85 {EXPAND_SHELLCMD, "shellcmd"},
86#if defined(FEAT_SIGNS)
87 {EXPAND_SIGN, "sign"},
88#endif
89 {EXPAND_TAGS, "tag"},
90 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
91 {EXPAND_USER, "user"},
92 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000093#if defined(FEAT_EVAL)
94 {EXPAND_BREAKPOINT, "breakpoint"},
95#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020096 {0, NULL}
97};
98
99/*
100 * List of names of address types. Must be alphabetical for completion.
101 */
102static struct
103{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200104 cmd_addr_T expand;
105 char *name;
106 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200107} addr_type_complete[] =
108{
109 {ADDR_ARGUMENTS, "arguments", "arg"},
110 {ADDR_LINES, "lines", "line"},
111 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
112 {ADDR_TABS, "tabs", "tab"},
113 {ADDR_BUFFERS, "buffers", "buf"},
114 {ADDR_WINDOWS, "windows", "win"},
115 {ADDR_QUICKFIX, "quickfix", "qf"},
116 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200117 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200118};
119
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200120/*
121 * Search for a user command that matches "eap->cmd".
122 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
123 * Return a pointer to just after the command.
124 * Return NULL if there is no matching command.
125 */
126 char_u *
127find_ucmd(
128 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000129 char_u *p, // end of the command (possibly including count)
130 int *full, // set to TRUE for a full match
131 expand_T *xp, // used for completion, NULL otherwise
132 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200133{
134 int len = (int)(p - eap->cmd);
135 int j, k, matchlen = 0;
136 ucmd_T *uc;
137 int found = FALSE;
138 int possible = FALSE;
139 char_u *cp, *np; // Point into typed cmd and test name
140 garray_T *gap;
141 int amb_local = FALSE; // Found ambiguous buffer-local command,
142 // only full match global is accepted.
143
144 /*
145 * Look for buffer-local user commands first, then global ones.
146 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000147 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200148 for (;;)
149 {
150 for (j = 0; j < gap->ga_len; ++j)
151 {
152 uc = USER_CMD_GA(gap, j);
153 cp = eap->cmd;
154 np = uc->uc_name;
155 k = 0;
156 while (k < len && *np != NUL && *cp++ == *np++)
157 k++;
158 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
159 {
160 // If finding a second match, the command is ambiguous. But
161 // not if a buffer-local command wasn't a full match and a
162 // global command is a full match.
163 if (k == len && found && *np != NUL)
164 {
165 if (gap == &ucmds)
166 return NULL;
167 amb_local = TRUE;
168 }
169
170 if (!found || (k == len && *np == NUL))
171 {
172 // If we matched up to a digit, then there could
173 // be another command including the digit that we
174 // should use instead.
175 if (k == len)
176 found = TRUE;
177 else
178 possible = TRUE;
179
180 if (gap == &ucmds)
181 eap->cmdidx = CMD_USER;
182 else
183 eap->cmdidx = CMD_USER_BUF;
184 eap->argt = (long)uc->uc_argt;
185 eap->useridx = j;
186 eap->addr_type = uc->uc_addr_type;
187
Bram Moolenaar52111f82019-04-29 21:30:45 +0200188 if (complp != NULL)
189 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200190# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200191 if (xp != NULL)
192 {
193 xp->xp_arg = uc->uc_compl_arg;
194 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100195 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200196 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200197# endif
198 // Do not search for further abbreviations
199 // if this is an exact match.
200 matchlen = k;
201 if (k == len && *np == NUL)
202 {
203 if (full != NULL)
204 *full = TRUE;
205 amb_local = FALSE;
206 break;
207 }
208 }
209 }
210 }
211
212 // Stop if we found a full match or searched all.
213 if (j < gap->ga_len || gap == &ucmds)
214 break;
215 gap = &ucmds;
216 }
217
218 // Only found ambiguous matches.
219 if (amb_local)
220 {
221 if (xp != NULL)
222 xp->xp_context = EXPAND_UNSUCCESSFUL;
223 return NULL;
224 }
225
226 // The match we found may be followed immediately by a number. Move "p"
227 // back to point to it.
228 if (found || possible)
229 return p + (matchlen - len);
230 return p;
231}
232
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000233/*
234 * Set completion context for :command
235 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200236 char_u *
237set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
238{
239 char_u *arg = arg_in;
240 char_u *p;
241
242 // Check for attributes
243 while (*arg == '-')
244 {
245 arg++; // Skip "-"
246 p = skiptowhite(arg);
247 if (*p == NUL)
248 {
249 // Cursor is still in the attribute
250 p = vim_strchr(arg, '=');
251 if (p == NULL)
252 {
253 // No "=", so complete attribute names
254 xp->xp_context = EXPAND_USER_CMD_FLAGS;
255 xp->xp_pattern = arg;
256 return NULL;
257 }
258
259 // For the -complete, -nargs and -addr attributes, we complete
260 // their arguments as well.
261 if (STRNICMP(arg, "complete", p - arg) == 0)
262 {
263 xp->xp_context = EXPAND_USER_COMPLETE;
264 xp->xp_pattern = p + 1;
265 return NULL;
266 }
267 else if (STRNICMP(arg, "nargs", p - arg) == 0)
268 {
269 xp->xp_context = EXPAND_USER_NARGS;
270 xp->xp_pattern = p + 1;
271 return NULL;
272 }
273 else if (STRNICMP(arg, "addr", p - arg) == 0)
274 {
275 xp->xp_context = EXPAND_USER_ADDR_TYPE;
276 xp->xp_pattern = p + 1;
277 return NULL;
278 }
279 return NULL;
280 }
281 arg = skipwhite(p);
282 }
283
284 // After the attributes comes the new command name
285 p = skiptowhite(arg);
286 if (*p == NUL)
287 {
288 xp->xp_context = EXPAND_USER_COMMANDS;
289 xp->xp_pattern = arg;
290 return NULL;
291 }
292
293 // And finally comes a normal command
294 return skipwhite(p);
295}
296
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000297/*
298 * Set the completion context for the argument of a user defined command.
299 */
300 char_u *
301set_context_in_user_cmdarg(
302 char_u *cmd UNUSED,
303 char_u *arg,
304 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000305 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000306 expand_T *xp,
307 int forceit)
308{
309 char_u *p;
310
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000311 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000312 return NULL;
313
314 if (argt & EX_XFILE)
315 {
316 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000317 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000318 return NULL;
319 }
320
321#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000322 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000323 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
324#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000325 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000326 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000327 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000328 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
329 FALSE, CMD_map);
330 // Find start of last argument.
331 p = arg;
332 while (*p)
333 {
334 if (*p == ' ')
335 // argument starts after a space
336 arg = p + 1;
337 else if (*p == '\\' && *(p + 1) != NUL)
338 ++p; // skip over escaped character
339 MB_PTR_ADV(p);
340 }
341 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000342 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000343
344 return NULL;
345}
346
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200347 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200348expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200349{
350 return get_user_commands(NULL, idx - (int)CMD_SIZE);
351}
352
353/*
354 * Function given to ExpandGeneric() to obtain the list of user command names.
355 */
356 char_u *
357get_user_commands(expand_T *xp UNUSED, int idx)
358{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200359 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000360 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200361
362 if (idx < buf->b_ucmds.ga_len)
363 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
364 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200365 if (idx < ucmds.ga_len)
366 return USER_CMD(idx)->uc_name;
367 return NULL;
368}
369
Dominique Pelle748b3082022-01-08 12:41:16 +0000370#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200371/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200372 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
373 * CMD_USER_BUF.
374 * Returns NULL if the command is not found.
375 */
376 char_u *
377get_user_command_name(int idx, int cmdidx)
378{
379 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
380 return USER_CMD(idx)->uc_name;
381 if (cmdidx == CMD_USER_BUF)
382 {
383 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000384 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200385
386 if (idx < buf->b_ucmds.ga_len)
387 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
388 }
389 return NULL;
390}
Dominique Pelle748b3082022-01-08 12:41:16 +0000391#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200392
393/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200394 * Function given to ExpandGeneric() to obtain the list of user address type
395 * names.
396 */
397 char_u *
398get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
399{
400 return (char_u *)addr_type_complete[idx].name;
401}
402
403/*
404 * Function given to ExpandGeneric() to obtain the list of user command
405 * attributes.
406 */
407 char_u *
408get_user_cmd_flags(expand_T *xp UNUSED, int idx)
409{
410 static char *user_cmd_flags[] = {
411 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000412 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200413 };
414
K.Takataeeec2542021-06-02 13:28:16 +0200415 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200416 return NULL;
417 return (char_u *)user_cmd_flags[idx];
418}
419
420/*
421 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
422 */
423 char_u *
424get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
425{
426 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
427
K.Takataeeec2542021-06-02 13:28:16 +0200428 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200429 return NULL;
430 return (char_u *)user_cmd_nargs[idx];
431}
432
433/*
434 * Function given to ExpandGeneric() to obtain the list of values for
435 * -complete.
436 */
437 char_u *
438get_user_cmd_complete(expand_T *xp UNUSED, int idx)
439{
440 return (char_u *)command_complete[idx].name;
441}
442
Dominique Pelle748b3082022-01-08 12:41:16 +0000443#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200444 int
445cmdcomplete_str_to_type(char_u *complete_str)
446{
447 int i;
448
449 for (i = 0; command_complete[i].expand != 0; ++i)
450 if (STRCMP(complete_str, command_complete[i].name) == 0)
451 return command_complete[i].expand;
452
453 return EXPAND_NOTHING;
454}
Dominique Pelle748b3082022-01-08 12:41:16 +0000455#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200456
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200457/*
458 * List user commands starting with "name[name_len]".
459 */
460 static void
461uc_list(char_u *name, size_t name_len)
462{
463 int i, j;
464 int found = FALSE;
465 ucmd_T *cmd;
466 int len;
467 int over;
468 long a;
469 garray_T *gap;
470
Bram Moolenaare38eab22019-12-05 21:50:01 +0100471 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000472 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200473 for (;;)
474 {
475 for (i = 0; i < gap->ga_len; ++i)
476 {
477 cmd = USER_CMD_GA(gap, i);
478 a = (long)cmd->uc_argt;
479
480 // Skip commands which don't match the requested prefix and
481 // commands filtered out.
482 if (STRNCMP(name, cmd->uc_name, name_len) != 0
483 || message_filtered(cmd->uc_name))
484 continue;
485
486 // Put out the title first time
487 if (!found)
488 msg_puts_title(_("\n Name Args Address Complete Definition"));
489 found = TRUE;
490 msg_putchar('\n');
491 if (got_int)
492 break;
493
494 // Special cases
495 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200496 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200497 {
498 msg_putchar('!');
499 --len;
500 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200501 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200502 {
503 msg_putchar('"');
504 --len;
505 }
506 if (gap != &ucmds)
507 {
508 msg_putchar('b');
509 --len;
510 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200511 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200512 {
513 msg_putchar('|');
514 --len;
515 }
516 while (len-- > 0)
517 msg_putchar(' ');
518
519 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
520 len = (int)STRLEN(cmd->uc_name) + 4;
521
522 do {
523 msg_putchar(' ');
524 ++len;
525 } while (len < 22);
526
527 // "over" is how much longer the name is than the column width for
528 // the name, we'll try to align what comes after.
529 over = len - 22;
530 len = 0;
531
532 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200533 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200534 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200535 case 0: IObuff[len++] = '0'; break;
536 case (EX_EXTRA): IObuff[len++] = '*'; break;
537 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
538 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
539 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200540 }
541
542 do {
543 IObuff[len++] = ' ';
544 } while (len < 5 - over);
545
546 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200547 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200548 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200549 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200550 {
551 // -count=N
552 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
553 len += (int)STRLEN(IObuff + len);
554 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200555 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200556 IObuff[len++] = '%';
557 else if (cmd->uc_def >= 0)
558 {
559 // -range=N
560 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
561 len += (int)STRLEN(IObuff + len);
562 }
563 else
564 IObuff[len++] = '.';
565 }
566
567 do {
568 IObuff[len++] = ' ';
569 } while (len < 8 - over);
570
571 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200572 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200573 if (addr_type_complete[j].expand != ADDR_LINES
574 && addr_type_complete[j].expand == cmd->uc_addr_type)
575 {
576 STRCPY(IObuff + len, addr_type_complete[j].shortname);
577 len += (int)STRLEN(IObuff + len);
578 break;
579 }
580
581 do {
582 IObuff[len++] = ' ';
583 } while (len < 13 - over);
584
585 // Completion
586 for (j = 0; command_complete[j].expand != 0; ++j)
587 if (command_complete[j].expand == cmd->uc_compl)
588 {
589 STRCPY(IObuff + len, command_complete[j].name);
590 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000591#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000592 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
593 && STRLEN(cmd->uc_compl_arg) < 200)
594 {
595 IObuff[len] = ',';
596 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
597 len += (int)STRLEN(IObuff + len);
598 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000599#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200600 break;
601 }
602
603 do {
604 IObuff[len++] = ' ';
605 } while (len < 25 - over);
606
607 IObuff[len] = '\0';
608 msg_outtrans(IObuff);
609
610 msg_outtrans_special(cmd->uc_rep, FALSE,
611 name_len == 0 ? Columns - 47 : 0);
612#ifdef FEAT_EVAL
613 if (p_verbose > 0)
614 last_set_msg(cmd->uc_script_ctx);
615#endif
616 out_flush();
617 ui_breakcheck();
618 if (got_int)
619 break;
620 }
621 if (gap == &ucmds || i < gap->ga_len)
622 break;
623 gap = &ucmds;
624 }
625
626 if (!found)
627 msg(_("No user-defined commands found"));
628}
629
630 char *
631uc_fun_cmd(void)
632{
633 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
634 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
635 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
636 0xb9, 0x7f, 0};
637 int i;
638
639 for (i = 0; fcmd[i]; ++i)
640 IObuff[i] = fcmd[i] - 0x40;
641 IObuff[i] = 0;
642 return (char *)IObuff;
643}
644
645/*
646 * Parse address type argument
647 */
648 static int
649parse_addr_type_arg(
650 char_u *value,
651 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200652 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200653{
654 int i, a, b;
655
Bram Moolenaarb7316892019-05-01 18:08:42 +0200656 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200657 {
658 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
659 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
660 if (a && b)
661 {
662 *addr_type_arg = addr_type_complete[i].expand;
663 break;
664 }
665 }
666
Bram Moolenaarb7316892019-05-01 18:08:42 +0200667 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200668 {
669 char_u *err = value;
670
671 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
672 ;
673 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000674 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200675 return FAIL;
676 }
677
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200678 return OK;
679}
680
681/*
682 * Parse a completion argument "value[vallen]".
683 * The detected completion goes in "*complp", argument type in "*argt".
684 * When there is an argument, for function and user defined completion, it's
685 * copied to allocated memory and stored in "*compl_arg".
686 * Returns FAIL if something is wrong.
687 */
688 int
689parse_compl_arg(
690 char_u *value,
691 int vallen,
692 int *complp,
693 long *argt,
694 char_u **compl_arg UNUSED)
695{
696 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200697# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200698 size_t arglen = 0;
699# endif
700 int i;
701 int valend = vallen;
702
703 // Look for any argument part - which is the part after any ','
704 for (i = 0; i < vallen; ++i)
705 {
706 if (value[i] == ',')
707 {
708 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200709# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200710 arglen = vallen - i - 1;
711# endif
712 valend = i;
713 break;
714 }
715 }
716
717 for (i = 0; command_complete[i].expand != 0; ++i)
718 {
719 if ((int)STRLEN(command_complete[i].name) == valend
720 && STRNCMP(value, command_complete[i].name, valend) == 0)
721 {
722 *complp = command_complete[i].expand;
723 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200724 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200725 else if (command_complete[i].expand == EXPAND_DIRECTORIES
726 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200727 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200728 break;
729 }
730 }
731
732 if (command_complete[i].expand == 0)
733 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000734 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200735 return FAIL;
736 }
737
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200738# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200739 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
740 && arg != NULL)
741# else
742 if (arg != NULL)
743# endif
744 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000745 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746 return FAIL;
747 }
748
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200749# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200750 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
751 && arg == NULL)
752 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000753 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200754 return FAIL;
755 }
756
757 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200758 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200759# endif
760 return OK;
761}
762
763/*
764 * Scan attributes in the ":command" command.
765 * Return FAIL when something is wrong.
766 */
767 static int
768uc_scan_attr(
769 char_u *attr,
770 size_t len,
771 long *argt,
772 long *def,
773 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200774 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200775 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200776 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777{
778 char_u *p;
779
780 if (len == 0)
781 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000782 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200783 return FAIL;
784 }
785
786 // First, try the simple attributes (no arguments)
787 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200788 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789 else if (STRNICMP(attr, "buffer", len) == 0)
790 *flags |= UC_BUFFER;
791 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200792 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000793 else if (STRNICMP(attr, "keepscript", len) == 0)
794 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200795 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200796 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200797 else
798 {
799 int i;
800 char_u *val = NULL;
801 size_t vallen = 0;
802 size_t attrlen = len;
803
804 // Look for the attribute name - which is the part before any '='
805 for (i = 0; i < (int)len; ++i)
806 {
807 if (attr[i] == '=')
808 {
809 val = &attr[i + 1];
810 vallen = len - i - 1;
811 attrlen = i;
812 break;
813 }
814 }
815
816 if (STRNICMP(attr, "nargs", attrlen) == 0)
817 {
818 if (vallen == 1)
819 {
820 if (*val == '0')
821 // Do nothing - this is the default
822 ;
823 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200824 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200825 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200826 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200827 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200828 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200829 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200830 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200831 else
832 goto wrong_nargs;
833 }
834 else
835 {
836wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000837 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200838 return FAIL;
839 }
840 }
841 else if (STRNICMP(attr, "range", attrlen) == 0)
842 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200843 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200844 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200845 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200846 else if (val != NULL)
847 {
848 p = val;
849 if (*def >= 0)
850 {
851two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000852 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200853 return FAIL;
854 }
855
856 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200857 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200858
859 if (p != val + vallen || vallen == 0)
860 {
861invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000862 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200863 return FAIL;
864 }
865 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200866 // default for -range is using buffer lines
867 if (*addr_type_arg == ADDR_NONE)
868 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200869 }
870 else if (STRNICMP(attr, "count", attrlen) == 0)
871 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200872 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200873 // default for -count is using any number
874 if (*addr_type_arg == ADDR_NONE)
875 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200876
877 if (val != NULL)
878 {
879 p = val;
880 if (*def >= 0)
881 goto two_count;
882
883 *def = getdigits(&p);
884
885 if (p != val + vallen)
886 goto invalid_count;
887 }
888
889 if (*def < 0)
890 *def = 0;
891 }
892 else if (STRNICMP(attr, "complete", attrlen) == 0)
893 {
894 if (val == NULL)
895 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000896 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200897 return FAIL;
898 }
899
Bram Moolenaar52111f82019-04-29 21:30:45 +0200900 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200901 == FAIL)
902 return FAIL;
903 }
904 else if (STRNICMP(attr, "addr", attrlen) == 0)
905 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200906 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200907 if (val == NULL)
908 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000909 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200910 return FAIL;
911 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200912 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200913 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200914 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200915 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200916 }
917 else
918 {
919 char_u ch = attr[len];
920 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000921 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200922 attr[len] = ch;
923 return FAIL;
924 }
925 }
926
927 return OK;
928}
929
930/*
931 * Add a user command to the list or replace an existing one.
932 */
933 static int
934uc_add_command(
935 char_u *name,
936 size_t name_len,
937 char_u *rep,
938 long argt,
939 long def,
940 int flags,
941 int compl,
942 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200943 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200944 int force)
945{
946 ucmd_T *cmd = NULL;
947 char_u *p;
948 int i;
949 int cmp = 1;
950 char_u *rep_buf = NULL;
951 garray_T *gap;
952
Bram Moolenaar459fd782019-10-13 16:43:39 +0200953 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200954 if (rep_buf == NULL)
955 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200956 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 rep_buf = vim_strsave(rep);
958
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200959 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200960 if (rep_buf == NULL)
961 return FAIL;
962 }
963
964 // get address of growarray: global or in curbuf
965 if (flags & UC_BUFFER)
966 {
967 gap = &curbuf->b_ucmds;
968 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +0000969 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200970 }
971 else
972 gap = &ucmds;
973
974 // Search for the command in the already defined commands.
975 for (i = 0; i < gap->ga_len; ++i)
976 {
977 size_t len;
978
979 cmd = USER_CMD_GA(gap, i);
980 len = STRLEN(cmd->uc_name);
981 cmp = STRNCMP(name, cmd->uc_name, name_len);
982 if (cmp == 0)
983 {
984 if (name_len < len)
985 cmp = -1;
986 else if (name_len > len)
987 cmp = 1;
988 }
989
990 if (cmp == 0)
991 {
992 // Command can be replaced with "command!" and when sourcing the
993 // same script again, but only once.
994 if (!force
995#ifdef FEAT_EVAL
996 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
997 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
998#endif
999 )
1000 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001001 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001002 name);
1003 goto fail;
1004 }
1005
1006 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001007#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001008 VIM_CLEAR(cmd->uc_compl_arg);
1009#endif
1010 break;
1011 }
1012
1013 // Stop as soon as we pass the name to add
1014 if (cmp < 0)
1015 break;
1016 }
1017
1018 // Extend the array unless we're replacing an existing command
1019 if (cmp != 0)
1020 {
1021 if (ga_grow(gap, 1) != OK)
1022 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001023 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001024 goto fail;
1025
1026 cmd = USER_CMD_GA(gap, i);
1027 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1028
1029 ++gap->ga_len;
1030
1031 cmd->uc_name = p;
1032 }
1033
1034 cmd->uc_rep = rep_buf;
1035 cmd->uc_argt = argt;
1036 cmd->uc_def = def;
1037 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001038 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001039 if (flags & UC_VIM9)
1040 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001041#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001042 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001043 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001044#endif
1045 cmd->uc_addr_type = addr_type;
1046
1047 return OK;
1048
1049fail:
1050 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001051#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001052 vim_free(compl_arg);
1053#endif
1054 return FAIL;
1055}
1056
1057/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001058 * If "p" starts with "{" then read a block of commands until "}".
1059 * Used for ":command" and ":autocmd".
1060 */
1061 char_u *
1062may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1063{
1064 char_u *retp = p;
1065
1066 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1067 && eap->getline != NULL)
1068 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001069 garray_T ga;
1070 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001071
1072 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001073 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001074 return retp;
1075
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001076 // If the argument ends in "}" it must have been concatenated already
1077 // for ISN_EXEC.
1078 if (p[STRLEN(p) - 1] != '}')
1079 // Read lines between '{' and '}'. Does not support nesting or
1080 // here-doc constructs.
1081 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001082 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001083 vim_free(line);
1084 if ((line = eap->getline(':', eap->cookie,
1085 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1086 {
1087 emsg(_(e_missing_rcurly));
1088 break;
1089 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001090 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001091 break;
1092 if (*skipwhite(line) == '}')
1093 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001094 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001095 vim_free(line);
1096 retp = *tofree = ga_concat_strings(&ga, "\n");
1097 ga_clear_strings(&ga);
1098 *flags |= UC_VIM9;
1099 }
1100 return retp;
1101}
1102
1103/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001104 * ":command ..." implementation
1105 */
1106 void
1107ex_command(exarg_T *eap)
1108{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001109 char_u *name;
1110 char_u *end;
1111 char_u *p;
1112 long argt = 0;
1113 long def = -1;
1114 int flags = 0;
1115 int compl = EXPAND_NOTHING;
1116 char_u *compl_arg = NULL;
1117 cmd_addr_T addr_type_arg = ADDR_NONE;
1118 int has_attr = (eap->arg[0] == '-');
1119 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001120
1121 p = eap->arg;
1122
1123 // Check for attributes
1124 while (*p == '-')
1125 {
1126 ++p;
1127 end = skiptowhite(p);
1128 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1129 &compl_arg, &addr_type_arg) == FAIL)
1130 return;
1131 p = skipwhite(end);
1132 }
1133
1134 // Get the name (if any) and skip to the following argument
1135 name = p;
1136 if (ASCII_ISALPHA(*p))
1137 while (ASCII_ISALNUM(*p))
1138 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001139 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001140 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001141 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001142 return;
1143 }
1144 end = p;
1145 name_len = (int)(end - name);
1146
1147 // If there is nothing after the name, and no attributes were specified,
1148 // we are listing commands
1149 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001150 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001151 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001152 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001153 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001154 else if ((name_len == 1 && *name == 'X')
1155 || (name_len <= 4
1156 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001157 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001158 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001159 {
1160 // Some plugins rely on silently ignoring the mistake, only make this
1161 // an error in Vim9 script.
1162 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001163 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001164 else
1165 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001166 (char_u *)_(e_complete_used_without_allowing_arguments),
1167 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001168 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001169 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001170 {
1171 char_u *tofree = NULL;
1172
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001173 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001174
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001175 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1176 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001177 vim_free(tofree);
1178 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001179}
1180
1181/*
1182 * ":comclear" implementation
1183 * Clear all user commands, global and for current buffer.
1184 */
1185 void
1186ex_comclear(exarg_T *eap UNUSED)
1187{
1188 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001189 if (curbuf != NULL)
1190 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001191}
1192
1193/*
1194 * Clear all user commands for "gap".
1195 */
1196 void
1197uc_clear(garray_T *gap)
1198{
1199 int i;
1200 ucmd_T *cmd;
1201
1202 for (i = 0; i < gap->ga_len; ++i)
1203 {
1204 cmd = USER_CMD_GA(gap, i);
1205 vim_free(cmd->uc_name);
1206 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001207# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001208 vim_free(cmd->uc_compl_arg);
1209# endif
1210 }
1211 ga_clear(gap);
1212}
1213
1214/*
1215 * ":delcommand" implementation
1216 */
1217 void
1218ex_delcommand(exarg_T *eap)
1219{
1220 int i = 0;
1221 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001222 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001223 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001224 char_u *arg = eap->arg;
1225 int buffer_only = FALSE;
1226
1227 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1228 {
1229 buffer_only = TRUE;
1230 arg = skipwhite(arg + 7);
1231 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001232
1233 gap = &curbuf->b_ucmds;
1234 for (;;)
1235 {
1236 for (i = 0; i < gap->ga_len; ++i)
1237 {
1238 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001239 res = STRCMP(arg, cmd->uc_name);
1240 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001241 break;
1242 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001243 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001244 break;
1245 gap = &ucmds;
1246 }
1247
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001248 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001249 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001250 semsg(_(buffer_only
1251 ? e_no_such_user_defined_command_in_current_buffer_str
1252 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001253 return;
1254 }
1255
1256 vim_free(cmd->uc_name);
1257 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001258# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001259 vim_free(cmd->uc_compl_arg);
1260# endif
1261
1262 --gap->ga_len;
1263
1264 if (i < gap->ga_len)
1265 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1266}
1267
1268/*
1269 * Split and quote args for <f-args>.
1270 */
1271 static char_u *
1272uc_split_args(char_u *arg, size_t *lenp)
1273{
1274 char_u *buf;
1275 char_u *p;
1276 char_u *q;
1277 int len;
1278
1279 // Precalculate length
1280 p = arg;
1281 len = 2; // Initial and final quotes
1282
1283 while (*p)
1284 {
1285 if (p[0] == '\\' && p[1] == '\\')
1286 {
1287 len += 2;
1288 p += 2;
1289 }
1290 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1291 {
1292 len += 1;
1293 p += 2;
1294 }
1295 else if (*p == '\\' || *p == '"')
1296 {
1297 len += 2;
1298 p += 1;
1299 }
1300 else if (VIM_ISWHITE(*p))
1301 {
1302 p = skipwhite(p);
1303 if (*p == NUL)
1304 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001305 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001306 }
1307 else
1308 {
1309 int charlen = (*mb_ptr2len)(p);
1310
1311 len += charlen;
1312 p += charlen;
1313 }
1314 }
1315
1316 buf = alloc(len + 1);
1317 if (buf == NULL)
1318 {
1319 *lenp = 0;
1320 return buf;
1321 }
1322
1323 p = arg;
1324 q = buf;
1325 *q++ = '"';
1326 while (*p)
1327 {
1328 if (p[0] == '\\' && p[1] == '\\')
1329 {
1330 *q++ = '\\';
1331 *q++ = '\\';
1332 p += 2;
1333 }
1334 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1335 {
1336 *q++ = p[1];
1337 p += 2;
1338 }
1339 else if (*p == '\\' || *p == '"')
1340 {
1341 *q++ = '\\';
1342 *q++ = *p++;
1343 }
1344 else if (VIM_ISWHITE(*p))
1345 {
1346 p = skipwhite(p);
1347 if (*p == NUL)
1348 break;
1349 *q++ = '"';
1350 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001351 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001352 *q++ = '"';
1353 }
1354 else
1355 {
1356 MB_COPY_CHAR(p, q);
1357 }
1358 }
1359 *q++ = '"';
1360 *q = 0;
1361
1362 *lenp = len;
1363 return buf;
1364}
1365
1366 static size_t
1367add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1368{
1369 size_t result;
1370
1371 result = STRLEN(mod_str);
1372 if (*multi_mods)
1373 result += 1;
1374 if (buf != NULL)
1375 {
1376 if (*multi_mods)
1377 STRCAT(buf, " ");
1378 STRCAT(buf, mod_str);
1379 }
1380
1381 *multi_mods = 1;
1382
1383 return result;
1384}
1385
1386/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001387 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001388 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001389 */
1390 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001391add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001392{
1393 size_t result = 0;
1394
1395 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001396 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001397 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1398 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001399 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001400 result += add_cmd_modifier(buf, "belowright", multi_mods);
1401 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001402 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001403 result += add_cmd_modifier(buf, "botright", multi_mods);
1404
1405 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001406 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001407 result += add_cmd_modifier(buf, "tab", multi_mods);
1408 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001409 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001410 result += add_cmd_modifier(buf, "topleft", multi_mods);
1411 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001412 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001413 result += add_cmd_modifier(buf, "vertical", multi_mods);
1414 return result;
1415}
1416
1417/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001418 * Generate text for the "cmod" command modifiers.
1419 * If "buf" is NULL just return the length.
1420 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001421 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001422produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1423{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001424 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001425 int multi_mods = 0;
1426 int i;
1427 typedef struct {
1428 int flag;
1429 char *name;
1430 } mod_entry_T;
1431 static mod_entry_T mod_entries[] = {
1432#ifdef FEAT_BROWSE_CMD
1433 {CMOD_BROWSE, "browse"},
1434#endif
1435#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1436 {CMOD_CONFIRM, "confirm"},
1437#endif
1438 {CMOD_HIDE, "hide"},
1439 {CMOD_KEEPALT, "keepalt"},
1440 {CMOD_KEEPJUMPS, "keepjumps"},
1441 {CMOD_KEEPMARKS, "keepmarks"},
1442 {CMOD_KEEPPATTERNS, "keeppatterns"},
1443 {CMOD_LOCKMARKS, "lockmarks"},
1444 {CMOD_NOSWAPFILE, "noswapfile"},
1445 {CMOD_UNSILENT, "unsilent"},
1446 {CMOD_NOAUTOCMD, "noautocmd"},
1447#ifdef HAVE_SANDBOX
1448 {CMOD_SANDBOX, "sandbox"},
1449#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001450 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001451 {0, NULL}
1452 };
1453
1454 result = quote ? 2 : 0;
1455 if (buf != NULL)
1456 {
1457 if (quote)
1458 *buf++ = '"';
1459 *buf = '\0';
1460 }
1461
1462 // the modifiers that are simple flags
1463 for (i = 0; mod_entries[i].name != NULL; ++i)
1464 if (cmod->cmod_flags & mod_entries[i].flag)
1465 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1466
1467 // :silent
1468 if (cmod->cmod_flags & CMOD_SILENT)
1469 result += add_cmd_modifier(buf,
1470 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1471 : "silent", &multi_mods);
1472 // :verbose
1473 if (p_verbose > 0)
1474 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1475 // flags from cmod->cmod_split
1476 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1477 if (quote && buf != NULL)
1478 {
1479 buf += result - 2;
1480 *buf = '"';
1481 }
1482 return result;
1483}
1484
1485/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001486 * Check for a <> code in a user command.
1487 * "code" points to the '<'. "len" the length of the <> (inclusive).
1488 * "buf" is where the result is to be added.
1489 * "split_buf" points to a buffer used for splitting, caller should free it.
1490 * "split_len" is the length of what "split_buf" contains.
1491 * Returns the length of the replacement, which has been added to "buf".
1492 * Returns -1 if there was no match, and only the "<" has been copied.
1493 */
1494 static size_t
1495uc_check_code(
1496 char_u *code,
1497 size_t len,
1498 char_u *buf,
1499 ucmd_T *cmd, // the user command we're expanding
1500 exarg_T *eap, // ex arguments
1501 char_u **split_buf,
1502 size_t *split_len)
1503{
1504 size_t result = 0;
1505 char_u *p = code + 1;
1506 size_t l = len - 2;
1507 int quote = 0;
1508 enum {
1509 ct_ARGS,
1510 ct_BANG,
1511 ct_COUNT,
1512 ct_LINE1,
1513 ct_LINE2,
1514 ct_RANGE,
1515 ct_MODS,
1516 ct_REGISTER,
1517 ct_LT,
1518 ct_NONE
1519 } type = ct_NONE;
1520
1521 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1522 {
1523 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1524 p += 2;
1525 l -= 2;
1526 }
1527
1528 ++l;
1529 if (l <= 1)
1530 type = ct_NONE;
1531 else if (STRNICMP(p, "args>", l) == 0)
1532 type = ct_ARGS;
1533 else if (STRNICMP(p, "bang>", l) == 0)
1534 type = ct_BANG;
1535 else if (STRNICMP(p, "count>", l) == 0)
1536 type = ct_COUNT;
1537 else if (STRNICMP(p, "line1>", l) == 0)
1538 type = ct_LINE1;
1539 else if (STRNICMP(p, "line2>", l) == 0)
1540 type = ct_LINE2;
1541 else if (STRNICMP(p, "range>", l) == 0)
1542 type = ct_RANGE;
1543 else if (STRNICMP(p, "lt>", l) == 0)
1544 type = ct_LT;
1545 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1546 type = ct_REGISTER;
1547 else if (STRNICMP(p, "mods>", l) == 0)
1548 type = ct_MODS;
1549
1550 switch (type)
1551 {
1552 case ct_ARGS:
1553 // Simple case first
1554 if (*eap->arg == NUL)
1555 {
1556 if (quote == 1)
1557 {
1558 result = 2;
1559 if (buf != NULL)
1560 STRCPY(buf, "''");
1561 }
1562 else
1563 result = 0;
1564 break;
1565 }
1566
1567 // When specified there is a single argument don't split it.
1568 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001569 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001570 quote = 1;
1571
1572 switch (quote)
1573 {
1574 case 0: // No quoting, no splitting
1575 result = STRLEN(eap->arg);
1576 if (buf != NULL)
1577 STRCPY(buf, eap->arg);
1578 break;
1579 case 1: // Quote, but don't split
1580 result = STRLEN(eap->arg) + 2;
1581 for (p = eap->arg; *p; ++p)
1582 {
1583 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1584 // DBCS can contain \ in a trail byte, skip the
1585 // double-byte character.
1586 ++p;
1587 else
1588 if (*p == '\\' || *p == '"')
1589 ++result;
1590 }
1591
1592 if (buf != NULL)
1593 {
1594 *buf++ = '"';
1595 for (p = eap->arg; *p; ++p)
1596 {
1597 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1598 // DBCS can contain \ in a trail byte, copy the
1599 // double-byte character to avoid escaping.
1600 *buf++ = *p++;
1601 else
1602 if (*p == '\\' || *p == '"')
1603 *buf++ = '\\';
1604 *buf++ = *p;
1605 }
1606 *buf = '"';
1607 }
1608
1609 break;
1610 case 2: // Quote and split (<f-args>)
1611 // This is hard, so only do it once, and cache the result
1612 if (*split_buf == NULL)
1613 *split_buf = uc_split_args(eap->arg, split_len);
1614
1615 result = *split_len;
1616 if (buf != NULL && result != 0)
1617 STRCPY(buf, *split_buf);
1618
1619 break;
1620 }
1621 break;
1622
1623 case ct_BANG:
1624 result = eap->forceit ? 1 : 0;
1625 if (quote)
1626 result += 2;
1627 if (buf != NULL)
1628 {
1629 if (quote)
1630 *buf++ = '"';
1631 if (eap->forceit)
1632 *buf++ = '!';
1633 if (quote)
1634 *buf = '"';
1635 }
1636 break;
1637
1638 case ct_LINE1:
1639 case ct_LINE2:
1640 case ct_RANGE:
1641 case ct_COUNT:
1642 {
1643 char num_buf[20];
1644 long num = (type == ct_LINE1) ? eap->line1 :
1645 (type == ct_LINE2) ? eap->line2 :
1646 (type == ct_RANGE) ? eap->addr_count :
1647 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1648 size_t num_len;
1649
1650 sprintf(num_buf, "%ld", num);
1651 num_len = STRLEN(num_buf);
1652 result = num_len;
1653
1654 if (quote)
1655 result += 2;
1656
1657 if (buf != NULL)
1658 {
1659 if (quote)
1660 *buf++ = '"';
1661 STRCPY(buf, num_buf);
1662 buf += num_len;
1663 if (quote)
1664 *buf = '"';
1665 }
1666
1667 break;
1668 }
1669
1670 case ct_MODS:
1671 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001672 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001673 break;
1674 }
1675
1676 case ct_REGISTER:
1677 result = eap->regname ? 1 : 0;
1678 if (quote)
1679 result += 2;
1680 if (buf != NULL)
1681 {
1682 if (quote)
1683 *buf++ = '\'';
1684 if (eap->regname)
1685 *buf++ = eap->regname;
1686 if (quote)
1687 *buf = '\'';
1688 }
1689 break;
1690
1691 case ct_LT:
1692 result = 1;
1693 if (buf != NULL)
1694 *buf = '<';
1695 break;
1696
1697 default:
1698 // Not recognized: just copy the '<' and return -1.
1699 result = (size_t)-1;
1700 if (buf != NULL)
1701 *buf = '<';
1702 break;
1703 }
1704
1705 return result;
1706}
1707
1708/*
1709 * Execute a user defined command.
1710 */
1711 void
1712do_ucmd(exarg_T *eap)
1713{
1714 char_u *buf;
1715 char_u *p;
1716 char_u *q;
1717
1718 char_u *start;
1719 char_u *end = NULL;
1720 char_u *ksp;
1721 size_t len, totlen;
1722
1723 size_t split_len = 0;
1724 char_u *split_buf = NULL;
1725 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001726 sctx_T save_current_sctx;
1727 int restore_current_sctx = FALSE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001728
1729 if (eap->cmdidx == CMD_USER)
1730 cmd = USER_CMD(eap->useridx);
1731 else
1732 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1733
1734 /*
1735 * Replace <> in the command by the arguments.
1736 * First round: "buf" is NULL, compute length, allocate "buf".
1737 * Second round: copy result into "buf".
1738 */
1739 buf = NULL;
1740 for (;;)
1741 {
1742 p = cmd->uc_rep; // source
1743 q = buf; // destination
1744 totlen = 0;
1745
1746 for (;;)
1747 {
1748 start = vim_strchr(p, '<');
1749 if (start != NULL)
1750 end = vim_strchr(start + 1, '>');
1751 if (buf != NULL)
1752 {
1753 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1754 ;
1755 if (*ksp == K_SPECIAL
1756 && (start == NULL || ksp < start || end == NULL)
1757 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1758# ifdef FEAT_GUI
1759 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1760# endif
1761 ))
1762 {
1763 // K_SPECIAL has been put in the buffer as K_SPECIAL
1764 // KS_SPECIAL KE_FILLER, like for mappings, but
1765 // do_cmdline() doesn't handle that, so convert it back.
1766 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1767 len = ksp - p;
1768 if (len > 0)
1769 {
1770 mch_memmove(q, p, len);
1771 q += len;
1772 }
1773 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1774 p = ksp + 3;
1775 continue;
1776 }
1777 }
1778
1779 // break if no <item> is found
1780 if (start == NULL || end == NULL)
1781 break;
1782
1783 // Include the '>'
1784 ++end;
1785
1786 // Take everything up to the '<'
1787 len = start - p;
1788 if (buf == NULL)
1789 totlen += len;
1790 else
1791 {
1792 mch_memmove(q, p, len);
1793 q += len;
1794 }
1795
1796 len = uc_check_code(start, end - start, q, cmd, eap,
1797 &split_buf, &split_len);
1798 if (len == (size_t)-1)
1799 {
1800 // no match, continue after '<'
1801 p = start + 1;
1802 len = 1;
1803 }
1804 else
1805 p = end;
1806 if (buf == NULL)
1807 totlen += len;
1808 else
1809 q += len;
1810 }
1811 if (buf != NULL) // second time here, finished
1812 {
1813 STRCPY(q, p);
1814 break;
1815 }
1816
1817 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001818 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001819 if (buf == NULL)
1820 {
1821 vim_free(split_buf);
1822 return;
1823 }
1824 }
1825
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001826 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1827 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001828 restore_current_sctx = TRUE;
1829 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001830 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001831#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001832 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001833#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001834 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001835
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001836 (void)do_cmdline(buf, eap->getline, eap->cookie,
1837 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001838
1839 // Careful: Do not use "cmd" here, it may have become invalid if a user
1840 // command was added.
1841 if (restore_current_sctx)
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001842 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001843 vim_free(buf);
1844 vim_free(split_buf);
1845}