blob: d4a7a9c150744004d87270b328e35775cd7e7c0a [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"},
93 {0, NULL}
94};
95
96/*
97 * List of names of address types. Must be alphabetical for completion.
98 */
99static struct
100{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200101 cmd_addr_T expand;
102 char *name;
103 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200104} addr_type_complete[] =
105{
106 {ADDR_ARGUMENTS, "arguments", "arg"},
107 {ADDR_LINES, "lines", "line"},
108 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
109 {ADDR_TABS, "tabs", "tab"},
110 {ADDR_BUFFERS, "buffers", "buf"},
111 {ADDR_WINDOWS, "windows", "win"},
112 {ADDR_QUICKFIX, "quickfix", "qf"},
113 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200114 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200115};
116
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200117/*
118 * Search for a user command that matches "eap->cmd".
119 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
120 * Return a pointer to just after the command.
121 * Return NULL if there is no matching command.
122 */
123 char_u *
124find_ucmd(
125 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000126 char_u *p, // end of the command (possibly including count)
127 int *full, // set to TRUE for a full match
128 expand_T *xp, // used for completion, NULL otherwise
129 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200130{
131 int len = (int)(p - eap->cmd);
132 int j, k, matchlen = 0;
133 ucmd_T *uc;
134 int found = FALSE;
135 int possible = FALSE;
136 char_u *cp, *np; // Point into typed cmd and test name
137 garray_T *gap;
138 int amb_local = FALSE; // Found ambiguous buffer-local command,
139 // only full match global is accepted.
140
141 /*
142 * Look for buffer-local user commands first, then global ones.
143 */
mityua1198122021-11-20 19:13:39 +0000144 gap =
145#ifdef FEAT_CMDWIN
146 is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds :
147#endif
148 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200149 for (;;)
150 {
151 for (j = 0; j < gap->ga_len; ++j)
152 {
153 uc = USER_CMD_GA(gap, j);
154 cp = eap->cmd;
155 np = uc->uc_name;
156 k = 0;
157 while (k < len && *np != NUL && *cp++ == *np++)
158 k++;
159 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
160 {
161 // If finding a second match, the command is ambiguous. But
162 // not if a buffer-local command wasn't a full match and a
163 // global command is a full match.
164 if (k == len && found && *np != NUL)
165 {
166 if (gap == &ucmds)
167 return NULL;
168 amb_local = TRUE;
169 }
170
171 if (!found || (k == len && *np == NUL))
172 {
173 // If we matched up to a digit, then there could
174 // be another command including the digit that we
175 // should use instead.
176 if (k == len)
177 found = TRUE;
178 else
179 possible = TRUE;
180
181 if (gap == &ucmds)
182 eap->cmdidx = CMD_USER;
183 else
184 eap->cmdidx = CMD_USER_BUF;
185 eap->argt = (long)uc->uc_argt;
186 eap->useridx = j;
187 eap->addr_type = uc->uc_addr_type;
188
Bram Moolenaar52111f82019-04-29 21:30:45 +0200189 if (complp != NULL)
190 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200191# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200192 if (xp != NULL)
193 {
194 xp->xp_arg = uc->uc_compl_arg;
195 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100196 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200197 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200198# endif
199 // Do not search for further abbreviations
200 // if this is an exact match.
201 matchlen = k;
202 if (k == len && *np == NUL)
203 {
204 if (full != NULL)
205 *full = TRUE;
206 amb_local = FALSE;
207 break;
208 }
209 }
210 }
211 }
212
213 // Stop if we found a full match or searched all.
214 if (j < gap->ga_len || gap == &ucmds)
215 break;
216 gap = &ucmds;
217 }
218
219 // Only found ambiguous matches.
220 if (amb_local)
221 {
222 if (xp != NULL)
223 xp->xp_context = EXPAND_UNSUCCESSFUL;
224 return NULL;
225 }
226
227 // The match we found may be followed immediately by a number. Move "p"
228 // back to point to it.
229 if (found || possible)
230 return p + (matchlen - len);
231 return p;
232}
233
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000234/*
235 * Set completion context for :command
236 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200237 char_u *
238set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
239{
240 char_u *arg = arg_in;
241 char_u *p;
242
243 // Check for attributes
244 while (*arg == '-')
245 {
246 arg++; // Skip "-"
247 p = skiptowhite(arg);
248 if (*p == NUL)
249 {
250 // Cursor is still in the attribute
251 p = vim_strchr(arg, '=');
252 if (p == NULL)
253 {
254 // No "=", so complete attribute names
255 xp->xp_context = EXPAND_USER_CMD_FLAGS;
256 xp->xp_pattern = arg;
257 return NULL;
258 }
259
260 // For the -complete, -nargs and -addr attributes, we complete
261 // their arguments as well.
262 if (STRNICMP(arg, "complete", p - arg) == 0)
263 {
264 xp->xp_context = EXPAND_USER_COMPLETE;
265 xp->xp_pattern = p + 1;
266 return NULL;
267 }
268 else if (STRNICMP(arg, "nargs", p - arg) == 0)
269 {
270 xp->xp_context = EXPAND_USER_NARGS;
271 xp->xp_pattern = p + 1;
272 return NULL;
273 }
274 else if (STRNICMP(arg, "addr", p - arg) == 0)
275 {
276 xp->xp_context = EXPAND_USER_ADDR_TYPE;
277 xp->xp_pattern = p + 1;
278 return NULL;
279 }
280 return NULL;
281 }
282 arg = skipwhite(p);
283 }
284
285 // After the attributes comes the new command name
286 p = skiptowhite(arg);
287 if (*p == NUL)
288 {
289 xp->xp_context = EXPAND_USER_COMMANDS;
290 xp->xp_pattern = arg;
291 return NULL;
292 }
293
294 // And finally comes a normal command
295 return skipwhite(p);
296}
297
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000298/*
299 * Set the completion context for the argument of a user defined command.
300 */
301 char_u *
302set_context_in_user_cmdarg(
303 char_u *cmd UNUSED,
304 char_u *arg,
305 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000306 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000307 expand_T *xp,
308 int forceit)
309{
310 char_u *p;
311
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000312 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000313 return NULL;
314
315 if (argt & EX_XFILE)
316 {
317 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000318 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000319 return NULL;
320 }
321
322#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000323 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000324 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
325#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000326 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000327 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000328 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000329 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
330 FALSE, CMD_map);
331 // Find start of last argument.
332 p = arg;
333 while (*p)
334 {
335 if (*p == ' ')
336 // argument starts after a space
337 arg = p + 1;
338 else if (*p == '\\' && *(p + 1) != NUL)
339 ++p; // skip over escaped character
340 MB_PTR_ADV(p);
341 }
342 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000343 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000344
345 return NULL;
346}
347
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200348 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200349expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200350{
351 return get_user_commands(NULL, idx - (int)CMD_SIZE);
352}
353
354/*
355 * Function given to ExpandGeneric() to obtain the list of user command names.
356 */
357 char_u *
358get_user_commands(expand_T *xp UNUSED, int idx)
359{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200360 // In cmdwin, the alternative buffer should be used.
361 buf_T *buf =
362#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000363 is_in_cmdwin() ? prevwin->w_buffer :
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200364#endif
365 curbuf;
366
367 if (idx < buf->b_ucmds.ga_len)
368 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
369 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200370 if (idx < ucmds.ga_len)
371 return USER_CMD(idx)->uc_name;
372 return NULL;
373}
374
Dominique Pelle748b3082022-01-08 12:41:16 +0000375#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200376/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200377 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
378 * CMD_USER_BUF.
379 * Returns NULL if the command is not found.
380 */
381 char_u *
382get_user_command_name(int idx, int cmdidx)
383{
384 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
385 return USER_CMD(idx)->uc_name;
386 if (cmdidx == CMD_USER_BUF)
387 {
388 // In cmdwin, the alternative buffer should be used.
389 buf_T *buf =
390#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000391 is_in_cmdwin() ? prevwin->w_buffer :
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200392#endif
mityua1198122021-11-20 19:13:39 +0000393 curbuf;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200394
395 if (idx < buf->b_ucmds.ga_len)
396 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
397 }
398 return NULL;
399}
Dominique Pelle748b3082022-01-08 12:41:16 +0000400#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200401
402/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200403 * Function given to ExpandGeneric() to obtain the list of user address type
404 * names.
405 */
406 char_u *
407get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
408{
409 return (char_u *)addr_type_complete[idx].name;
410}
411
412/*
413 * Function given to ExpandGeneric() to obtain the list of user command
414 * attributes.
415 */
416 char_u *
417get_user_cmd_flags(expand_T *xp UNUSED, int idx)
418{
419 static char *user_cmd_flags[] = {
420 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000421 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200422 };
423
K.Takataeeec2542021-06-02 13:28:16 +0200424 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200425 return NULL;
426 return (char_u *)user_cmd_flags[idx];
427}
428
429/*
430 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
431 */
432 char_u *
433get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
434{
435 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
436
K.Takataeeec2542021-06-02 13:28:16 +0200437 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200438 return NULL;
439 return (char_u *)user_cmd_nargs[idx];
440}
441
442/*
443 * Function given to ExpandGeneric() to obtain the list of values for
444 * -complete.
445 */
446 char_u *
447get_user_cmd_complete(expand_T *xp UNUSED, int idx)
448{
449 return (char_u *)command_complete[idx].name;
450}
451
Dominique Pelle748b3082022-01-08 12:41:16 +0000452#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200453 int
454cmdcomplete_str_to_type(char_u *complete_str)
455{
456 int i;
457
458 for (i = 0; command_complete[i].expand != 0; ++i)
459 if (STRCMP(complete_str, command_complete[i].name) == 0)
460 return command_complete[i].expand;
461
462 return EXPAND_NOTHING;
463}
Dominique Pelle748b3082022-01-08 12:41:16 +0000464#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200465
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200466/*
467 * List user commands starting with "name[name_len]".
468 */
469 static void
470uc_list(char_u *name, size_t name_len)
471{
472 int i, j;
473 int found = FALSE;
474 ucmd_T *cmd;
475 int len;
476 int over;
477 long a;
478 garray_T *gap;
479
Bram Moolenaare38eab22019-12-05 21:50:01 +0100480 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200481 gap =
482#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000483 is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds :
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200484#endif
mityua1198122021-11-20 19:13:39 +0000485 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200486 for (;;)
487 {
488 for (i = 0; i < gap->ga_len; ++i)
489 {
490 cmd = USER_CMD_GA(gap, i);
491 a = (long)cmd->uc_argt;
492
493 // Skip commands which don't match the requested prefix and
494 // commands filtered out.
495 if (STRNCMP(name, cmd->uc_name, name_len) != 0
496 || message_filtered(cmd->uc_name))
497 continue;
498
499 // Put out the title first time
500 if (!found)
501 msg_puts_title(_("\n Name Args Address Complete Definition"));
502 found = TRUE;
503 msg_putchar('\n');
504 if (got_int)
505 break;
506
507 // Special cases
508 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200509 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510 {
511 msg_putchar('!');
512 --len;
513 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200514 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200515 {
516 msg_putchar('"');
517 --len;
518 }
519 if (gap != &ucmds)
520 {
521 msg_putchar('b');
522 --len;
523 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200524 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200525 {
526 msg_putchar('|');
527 --len;
528 }
529 while (len-- > 0)
530 msg_putchar(' ');
531
532 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
533 len = (int)STRLEN(cmd->uc_name) + 4;
534
535 do {
536 msg_putchar(' ');
537 ++len;
538 } while (len < 22);
539
540 // "over" is how much longer the name is than the column width for
541 // the name, we'll try to align what comes after.
542 over = len - 22;
543 len = 0;
544
545 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200546 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200547 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200548 case 0: IObuff[len++] = '0'; break;
549 case (EX_EXTRA): IObuff[len++] = '*'; break;
550 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
551 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
552 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200553 }
554
555 do {
556 IObuff[len++] = ' ';
557 } while (len < 5 - over);
558
559 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200560 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200561 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200562 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200563 {
564 // -count=N
565 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
566 len += (int)STRLEN(IObuff + len);
567 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200568 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200569 IObuff[len++] = '%';
570 else if (cmd->uc_def >= 0)
571 {
572 // -range=N
573 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
574 len += (int)STRLEN(IObuff + len);
575 }
576 else
577 IObuff[len++] = '.';
578 }
579
580 do {
581 IObuff[len++] = ' ';
582 } while (len < 8 - over);
583
584 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200585 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200586 if (addr_type_complete[j].expand != ADDR_LINES
587 && addr_type_complete[j].expand == cmd->uc_addr_type)
588 {
589 STRCPY(IObuff + len, addr_type_complete[j].shortname);
590 len += (int)STRLEN(IObuff + len);
591 break;
592 }
593
594 do {
595 IObuff[len++] = ' ';
596 } while (len < 13 - over);
597
598 // Completion
599 for (j = 0; command_complete[j].expand != 0; ++j)
600 if (command_complete[j].expand == cmd->uc_compl)
601 {
602 STRCPY(IObuff + len, command_complete[j].name);
603 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000604#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000605 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
606 && STRLEN(cmd->uc_compl_arg) < 200)
607 {
608 IObuff[len] = ',';
609 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
610 len += (int)STRLEN(IObuff + len);
611 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000612#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200613 break;
614 }
615
616 do {
617 IObuff[len++] = ' ';
618 } while (len < 25 - over);
619
620 IObuff[len] = '\0';
621 msg_outtrans(IObuff);
622
623 msg_outtrans_special(cmd->uc_rep, FALSE,
624 name_len == 0 ? Columns - 47 : 0);
625#ifdef FEAT_EVAL
626 if (p_verbose > 0)
627 last_set_msg(cmd->uc_script_ctx);
628#endif
629 out_flush();
630 ui_breakcheck();
631 if (got_int)
632 break;
633 }
634 if (gap == &ucmds || i < gap->ga_len)
635 break;
636 gap = &ucmds;
637 }
638
639 if (!found)
640 msg(_("No user-defined commands found"));
641}
642
643 char *
644uc_fun_cmd(void)
645{
646 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
647 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
648 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
649 0xb9, 0x7f, 0};
650 int i;
651
652 for (i = 0; fcmd[i]; ++i)
653 IObuff[i] = fcmd[i] - 0x40;
654 IObuff[i] = 0;
655 return (char *)IObuff;
656}
657
658/*
659 * Parse address type argument
660 */
661 static int
662parse_addr_type_arg(
663 char_u *value,
664 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200665 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200666{
667 int i, a, b;
668
Bram Moolenaarb7316892019-05-01 18:08:42 +0200669 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200670 {
671 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
672 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
673 if (a && b)
674 {
675 *addr_type_arg = addr_type_complete[i].expand;
676 break;
677 }
678 }
679
Bram Moolenaarb7316892019-05-01 18:08:42 +0200680 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200681 {
682 char_u *err = value;
683
684 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
685 ;
686 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000687 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200688 return FAIL;
689 }
690
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200691 return OK;
692}
693
694/*
695 * Parse a completion argument "value[vallen]".
696 * The detected completion goes in "*complp", argument type in "*argt".
697 * When there is an argument, for function and user defined completion, it's
698 * copied to allocated memory and stored in "*compl_arg".
699 * Returns FAIL if something is wrong.
700 */
701 int
702parse_compl_arg(
703 char_u *value,
704 int vallen,
705 int *complp,
706 long *argt,
707 char_u **compl_arg UNUSED)
708{
709 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200710# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200711 size_t arglen = 0;
712# endif
713 int i;
714 int valend = vallen;
715
716 // Look for any argument part - which is the part after any ','
717 for (i = 0; i < vallen; ++i)
718 {
719 if (value[i] == ',')
720 {
721 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200722# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200723 arglen = vallen - i - 1;
724# endif
725 valend = i;
726 break;
727 }
728 }
729
730 for (i = 0; command_complete[i].expand != 0; ++i)
731 {
732 if ((int)STRLEN(command_complete[i].name) == valend
733 && STRNCMP(value, command_complete[i].name, valend) == 0)
734 {
735 *complp = command_complete[i].expand;
736 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200737 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200738 else if (command_complete[i].expand == EXPAND_DIRECTORIES
739 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200740 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200741 break;
742 }
743 }
744
745 if (command_complete[i].expand == 0)
746 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000747 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200748 return FAIL;
749 }
750
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200751# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200752 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
753 && arg != NULL)
754# else
755 if (arg != NULL)
756# endif
757 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000758 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200759 return FAIL;
760 }
761
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200762# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200763 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
764 && arg == NULL)
765 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000766 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200767 return FAIL;
768 }
769
770 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200771 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200772# endif
773 return OK;
774}
775
776/*
777 * Scan attributes in the ":command" command.
778 * Return FAIL when something is wrong.
779 */
780 static int
781uc_scan_attr(
782 char_u *attr,
783 size_t len,
784 long *argt,
785 long *def,
786 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200787 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200788 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200789 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200790{
791 char_u *p;
792
793 if (len == 0)
794 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000795 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200796 return FAIL;
797 }
798
799 // First, try the simple attributes (no arguments)
800 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200801 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200802 else if (STRNICMP(attr, "buffer", len) == 0)
803 *flags |= UC_BUFFER;
804 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200805 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000806 else if (STRNICMP(attr, "keepscript", len) == 0)
807 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200808 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200809 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200810 else
811 {
812 int i;
813 char_u *val = NULL;
814 size_t vallen = 0;
815 size_t attrlen = len;
816
817 // Look for the attribute name - which is the part before any '='
818 for (i = 0; i < (int)len; ++i)
819 {
820 if (attr[i] == '=')
821 {
822 val = &attr[i + 1];
823 vallen = len - i - 1;
824 attrlen = i;
825 break;
826 }
827 }
828
829 if (STRNICMP(attr, "nargs", attrlen) == 0)
830 {
831 if (vallen == 1)
832 {
833 if (*val == '0')
834 // Do nothing - this is the default
835 ;
836 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200837 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200838 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200839 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200840 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200841 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200842 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200843 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200844 else
845 goto wrong_nargs;
846 }
847 else
848 {
849wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000850 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200851 return FAIL;
852 }
853 }
854 else if (STRNICMP(attr, "range", attrlen) == 0)
855 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200856 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200857 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200858 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200859 else if (val != NULL)
860 {
861 p = val;
862 if (*def >= 0)
863 {
864two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000865 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200866 return FAIL;
867 }
868
869 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200870 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200871
872 if (p != val + vallen || vallen == 0)
873 {
874invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000875 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200876 return FAIL;
877 }
878 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200879 // default for -range is using buffer lines
880 if (*addr_type_arg == ADDR_NONE)
881 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200882 }
883 else if (STRNICMP(attr, "count", attrlen) == 0)
884 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200885 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200886 // default for -count is using any number
887 if (*addr_type_arg == ADDR_NONE)
888 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200889
890 if (val != NULL)
891 {
892 p = val;
893 if (*def >= 0)
894 goto two_count;
895
896 *def = getdigits(&p);
897
898 if (p != val + vallen)
899 goto invalid_count;
900 }
901
902 if (*def < 0)
903 *def = 0;
904 }
905 else if (STRNICMP(attr, "complete", attrlen) == 0)
906 {
907 if (val == NULL)
908 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000909 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200910 return FAIL;
911 }
912
Bram Moolenaar52111f82019-04-29 21:30:45 +0200913 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 == FAIL)
915 return FAIL;
916 }
917 else if (STRNICMP(attr, "addr", attrlen) == 0)
918 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200919 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200920 if (val == NULL)
921 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000922 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200923 return FAIL;
924 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200925 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200926 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200927 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200928 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200929 }
930 else
931 {
932 char_u ch = attr[len];
933 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000934 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200935 attr[len] = ch;
936 return FAIL;
937 }
938 }
939
940 return OK;
941}
942
943/*
944 * Add a user command to the list or replace an existing one.
945 */
946 static int
947uc_add_command(
948 char_u *name,
949 size_t name_len,
950 char_u *rep,
951 long argt,
952 long def,
953 int flags,
954 int compl,
955 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200956 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 int force)
958{
959 ucmd_T *cmd = NULL;
960 char_u *p;
961 int i;
962 int cmp = 1;
963 char_u *rep_buf = NULL;
964 garray_T *gap;
965
Bram Moolenaar459fd782019-10-13 16:43:39 +0200966 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200967 if (rep_buf == NULL)
968 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200969 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200970 rep_buf = vim_strsave(rep);
971
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200972 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200973 if (rep_buf == NULL)
974 return FAIL;
975 }
976
977 // get address of growarray: global or in curbuf
978 if (flags & UC_BUFFER)
979 {
980 gap = &curbuf->b_ucmds;
981 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +0000982 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200983 }
984 else
985 gap = &ucmds;
986
987 // Search for the command in the already defined commands.
988 for (i = 0; i < gap->ga_len; ++i)
989 {
990 size_t len;
991
992 cmd = USER_CMD_GA(gap, i);
993 len = STRLEN(cmd->uc_name);
994 cmp = STRNCMP(name, cmd->uc_name, name_len);
995 if (cmp == 0)
996 {
997 if (name_len < len)
998 cmp = -1;
999 else if (name_len > len)
1000 cmp = 1;
1001 }
1002
1003 if (cmp == 0)
1004 {
1005 // Command can be replaced with "command!" and when sourcing the
1006 // same script again, but only once.
1007 if (!force
1008#ifdef FEAT_EVAL
1009 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1010 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1011#endif
1012 )
1013 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001014 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001015 name);
1016 goto fail;
1017 }
1018
1019 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001020#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001021 VIM_CLEAR(cmd->uc_compl_arg);
1022#endif
1023 break;
1024 }
1025
1026 // Stop as soon as we pass the name to add
1027 if (cmp < 0)
1028 break;
1029 }
1030
1031 // Extend the array unless we're replacing an existing command
1032 if (cmp != 0)
1033 {
1034 if (ga_grow(gap, 1) != OK)
1035 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001036 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001037 goto fail;
1038
1039 cmd = USER_CMD_GA(gap, i);
1040 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1041
1042 ++gap->ga_len;
1043
1044 cmd->uc_name = p;
1045 }
1046
1047 cmd->uc_rep = rep_buf;
1048 cmd->uc_argt = argt;
1049 cmd->uc_def = def;
1050 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001051 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001052 if (flags & UC_VIM9)
1053 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001054#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001055 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001056 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001057#endif
1058 cmd->uc_addr_type = addr_type;
1059
1060 return OK;
1061
1062fail:
1063 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001064#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001065 vim_free(compl_arg);
1066#endif
1067 return FAIL;
1068}
1069
1070/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001071 * If "p" starts with "{" then read a block of commands until "}".
1072 * Used for ":command" and ":autocmd".
1073 */
1074 char_u *
1075may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1076{
1077 char_u *retp = p;
1078
1079 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1080 && eap->getline != NULL)
1081 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001082 garray_T ga;
1083 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001084
1085 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001086 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001087 return retp;
1088
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001089 // If the argument ends in "}" it must have been concatenated already
1090 // for ISN_EXEC.
1091 if (p[STRLEN(p) - 1] != '}')
1092 // Read lines between '{' and '}'. Does not support nesting or
1093 // here-doc constructs.
1094 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001095 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001096 vim_free(line);
1097 if ((line = eap->getline(':', eap->cookie,
1098 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1099 {
1100 emsg(_(e_missing_rcurly));
1101 break;
1102 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001103 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001104 break;
1105 if (*skipwhite(line) == '}')
1106 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001107 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001108 vim_free(line);
1109 retp = *tofree = ga_concat_strings(&ga, "\n");
1110 ga_clear_strings(&ga);
1111 *flags |= UC_VIM9;
1112 }
1113 return retp;
1114}
1115
1116/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001117 * ":command ..." implementation
1118 */
1119 void
1120ex_command(exarg_T *eap)
1121{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001122 char_u *name;
1123 char_u *end;
1124 char_u *p;
1125 long argt = 0;
1126 long def = -1;
1127 int flags = 0;
1128 int compl = EXPAND_NOTHING;
1129 char_u *compl_arg = NULL;
1130 cmd_addr_T addr_type_arg = ADDR_NONE;
1131 int has_attr = (eap->arg[0] == '-');
1132 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001133
1134 p = eap->arg;
1135
1136 // Check for attributes
1137 while (*p == '-')
1138 {
1139 ++p;
1140 end = skiptowhite(p);
1141 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1142 &compl_arg, &addr_type_arg) == FAIL)
1143 return;
1144 p = skipwhite(end);
1145 }
1146
1147 // Get the name (if any) and skip to the following argument
1148 name = p;
1149 if (ASCII_ISALPHA(*p))
1150 while (ASCII_ISALNUM(*p))
1151 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001152 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001153 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001154 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001155 return;
1156 }
1157 end = p;
1158 name_len = (int)(end - name);
1159
1160 // If there is nothing after the name, and no attributes were specified,
1161 // we are listing commands
1162 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001163 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001164 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001165 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001166 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001167 else if ((name_len == 1 && *name == 'X')
1168 || (name_len <= 4
1169 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001170 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001171 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001172 {
1173 // Some plugins rely on silently ignoring the mistake, only make this
1174 // an error in Vim9 script.
1175 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001176 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001177 else
1178 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001179 (char_u *)_(e_complete_used_without_allowing_arguments),
1180 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001181 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001182 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001183 {
1184 char_u *tofree = NULL;
1185
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001186 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001187
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001188 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1189 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001190 vim_free(tofree);
1191 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001192}
1193
1194/*
1195 * ":comclear" implementation
1196 * Clear all user commands, global and for current buffer.
1197 */
1198 void
1199ex_comclear(exarg_T *eap UNUSED)
1200{
1201 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001202 if (curbuf != NULL)
1203 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001204}
1205
1206/*
1207 * Clear all user commands for "gap".
1208 */
1209 void
1210uc_clear(garray_T *gap)
1211{
1212 int i;
1213 ucmd_T *cmd;
1214
1215 for (i = 0; i < gap->ga_len; ++i)
1216 {
1217 cmd = USER_CMD_GA(gap, i);
1218 vim_free(cmd->uc_name);
1219 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001220# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001221 vim_free(cmd->uc_compl_arg);
1222# endif
1223 }
1224 ga_clear(gap);
1225}
1226
1227/*
1228 * ":delcommand" implementation
1229 */
1230 void
1231ex_delcommand(exarg_T *eap)
1232{
1233 int i = 0;
1234 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001235 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001236 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001237 char_u *arg = eap->arg;
1238 int buffer_only = FALSE;
1239
1240 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1241 {
1242 buffer_only = TRUE;
1243 arg = skipwhite(arg + 7);
1244 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001245
1246 gap = &curbuf->b_ucmds;
1247 for (;;)
1248 {
1249 for (i = 0; i < gap->ga_len; ++i)
1250 {
1251 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001252 res = STRCMP(arg, cmd->uc_name);
1253 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001254 break;
1255 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001256 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001257 break;
1258 gap = &ucmds;
1259 }
1260
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001261 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001262 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001263 semsg(_(buffer_only
1264 ? e_no_such_user_defined_command_in_current_buffer_str
1265 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001266 return;
1267 }
1268
1269 vim_free(cmd->uc_name);
1270 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001271# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001272 vim_free(cmd->uc_compl_arg);
1273# endif
1274
1275 --gap->ga_len;
1276
1277 if (i < gap->ga_len)
1278 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1279}
1280
1281/*
1282 * Split and quote args for <f-args>.
1283 */
1284 static char_u *
1285uc_split_args(char_u *arg, size_t *lenp)
1286{
1287 char_u *buf;
1288 char_u *p;
1289 char_u *q;
1290 int len;
1291
1292 // Precalculate length
1293 p = arg;
1294 len = 2; // Initial and final quotes
1295
1296 while (*p)
1297 {
1298 if (p[0] == '\\' && p[1] == '\\')
1299 {
1300 len += 2;
1301 p += 2;
1302 }
1303 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1304 {
1305 len += 1;
1306 p += 2;
1307 }
1308 else if (*p == '\\' || *p == '"')
1309 {
1310 len += 2;
1311 p += 1;
1312 }
1313 else if (VIM_ISWHITE(*p))
1314 {
1315 p = skipwhite(p);
1316 if (*p == NUL)
1317 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001318 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001319 }
1320 else
1321 {
1322 int charlen = (*mb_ptr2len)(p);
1323
1324 len += charlen;
1325 p += charlen;
1326 }
1327 }
1328
1329 buf = alloc(len + 1);
1330 if (buf == NULL)
1331 {
1332 *lenp = 0;
1333 return buf;
1334 }
1335
1336 p = arg;
1337 q = buf;
1338 *q++ = '"';
1339 while (*p)
1340 {
1341 if (p[0] == '\\' && p[1] == '\\')
1342 {
1343 *q++ = '\\';
1344 *q++ = '\\';
1345 p += 2;
1346 }
1347 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1348 {
1349 *q++ = p[1];
1350 p += 2;
1351 }
1352 else if (*p == '\\' || *p == '"')
1353 {
1354 *q++ = '\\';
1355 *q++ = *p++;
1356 }
1357 else if (VIM_ISWHITE(*p))
1358 {
1359 p = skipwhite(p);
1360 if (*p == NUL)
1361 break;
1362 *q++ = '"';
1363 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001364 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001365 *q++ = '"';
1366 }
1367 else
1368 {
1369 MB_COPY_CHAR(p, q);
1370 }
1371 }
1372 *q++ = '"';
1373 *q = 0;
1374
1375 *lenp = len;
1376 return buf;
1377}
1378
1379 static size_t
1380add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1381{
1382 size_t result;
1383
1384 result = STRLEN(mod_str);
1385 if (*multi_mods)
1386 result += 1;
1387 if (buf != NULL)
1388 {
1389 if (*multi_mods)
1390 STRCAT(buf, " ");
1391 STRCAT(buf, mod_str);
1392 }
1393
1394 *multi_mods = 1;
1395
1396 return result;
1397}
1398
1399/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001400 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001401 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001402 */
1403 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001404add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001405{
1406 size_t result = 0;
1407
1408 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001409 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001410 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1411 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001412 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001413 result += add_cmd_modifier(buf, "belowright", multi_mods);
1414 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001415 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001416 result += add_cmd_modifier(buf, "botright", multi_mods);
1417
1418 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001419 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001420 result += add_cmd_modifier(buf, "tab", multi_mods);
1421 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001422 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001423 result += add_cmd_modifier(buf, "topleft", multi_mods);
1424 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001425 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001426 result += add_cmd_modifier(buf, "vertical", multi_mods);
1427 return result;
1428}
1429
1430/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001431 * Generate text for the "cmod" command modifiers.
1432 * If "buf" is NULL just return the length.
1433 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001434 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001435produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1436{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001437 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001438 int multi_mods = 0;
1439 int i;
1440 typedef struct {
1441 int flag;
1442 char *name;
1443 } mod_entry_T;
1444 static mod_entry_T mod_entries[] = {
1445#ifdef FEAT_BROWSE_CMD
1446 {CMOD_BROWSE, "browse"},
1447#endif
1448#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1449 {CMOD_CONFIRM, "confirm"},
1450#endif
1451 {CMOD_HIDE, "hide"},
1452 {CMOD_KEEPALT, "keepalt"},
1453 {CMOD_KEEPJUMPS, "keepjumps"},
1454 {CMOD_KEEPMARKS, "keepmarks"},
1455 {CMOD_KEEPPATTERNS, "keeppatterns"},
1456 {CMOD_LOCKMARKS, "lockmarks"},
1457 {CMOD_NOSWAPFILE, "noswapfile"},
1458 {CMOD_UNSILENT, "unsilent"},
1459 {CMOD_NOAUTOCMD, "noautocmd"},
1460#ifdef HAVE_SANDBOX
1461 {CMOD_SANDBOX, "sandbox"},
1462#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001463 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001464 {0, NULL}
1465 };
1466
1467 result = quote ? 2 : 0;
1468 if (buf != NULL)
1469 {
1470 if (quote)
1471 *buf++ = '"';
1472 *buf = '\0';
1473 }
1474
1475 // the modifiers that are simple flags
1476 for (i = 0; mod_entries[i].name != NULL; ++i)
1477 if (cmod->cmod_flags & mod_entries[i].flag)
1478 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1479
1480 // :silent
1481 if (cmod->cmod_flags & CMOD_SILENT)
1482 result += add_cmd_modifier(buf,
1483 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1484 : "silent", &multi_mods);
1485 // :verbose
1486 if (p_verbose > 0)
1487 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1488 // flags from cmod->cmod_split
1489 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1490 if (quote && buf != NULL)
1491 {
1492 buf += result - 2;
1493 *buf = '"';
1494 }
1495 return result;
1496}
1497
1498/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001499 * Check for a <> code in a user command.
1500 * "code" points to the '<'. "len" the length of the <> (inclusive).
1501 * "buf" is where the result is to be added.
1502 * "split_buf" points to a buffer used for splitting, caller should free it.
1503 * "split_len" is the length of what "split_buf" contains.
1504 * Returns the length of the replacement, which has been added to "buf".
1505 * Returns -1 if there was no match, and only the "<" has been copied.
1506 */
1507 static size_t
1508uc_check_code(
1509 char_u *code,
1510 size_t len,
1511 char_u *buf,
1512 ucmd_T *cmd, // the user command we're expanding
1513 exarg_T *eap, // ex arguments
1514 char_u **split_buf,
1515 size_t *split_len)
1516{
1517 size_t result = 0;
1518 char_u *p = code + 1;
1519 size_t l = len - 2;
1520 int quote = 0;
1521 enum {
1522 ct_ARGS,
1523 ct_BANG,
1524 ct_COUNT,
1525 ct_LINE1,
1526 ct_LINE2,
1527 ct_RANGE,
1528 ct_MODS,
1529 ct_REGISTER,
1530 ct_LT,
1531 ct_NONE
1532 } type = ct_NONE;
1533
1534 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1535 {
1536 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1537 p += 2;
1538 l -= 2;
1539 }
1540
1541 ++l;
1542 if (l <= 1)
1543 type = ct_NONE;
1544 else if (STRNICMP(p, "args>", l) == 0)
1545 type = ct_ARGS;
1546 else if (STRNICMP(p, "bang>", l) == 0)
1547 type = ct_BANG;
1548 else if (STRNICMP(p, "count>", l) == 0)
1549 type = ct_COUNT;
1550 else if (STRNICMP(p, "line1>", l) == 0)
1551 type = ct_LINE1;
1552 else if (STRNICMP(p, "line2>", l) == 0)
1553 type = ct_LINE2;
1554 else if (STRNICMP(p, "range>", l) == 0)
1555 type = ct_RANGE;
1556 else if (STRNICMP(p, "lt>", l) == 0)
1557 type = ct_LT;
1558 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1559 type = ct_REGISTER;
1560 else if (STRNICMP(p, "mods>", l) == 0)
1561 type = ct_MODS;
1562
1563 switch (type)
1564 {
1565 case ct_ARGS:
1566 // Simple case first
1567 if (*eap->arg == NUL)
1568 {
1569 if (quote == 1)
1570 {
1571 result = 2;
1572 if (buf != NULL)
1573 STRCPY(buf, "''");
1574 }
1575 else
1576 result = 0;
1577 break;
1578 }
1579
1580 // When specified there is a single argument don't split it.
1581 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001582 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001583 quote = 1;
1584
1585 switch (quote)
1586 {
1587 case 0: // No quoting, no splitting
1588 result = STRLEN(eap->arg);
1589 if (buf != NULL)
1590 STRCPY(buf, eap->arg);
1591 break;
1592 case 1: // Quote, but don't split
1593 result = STRLEN(eap->arg) + 2;
1594 for (p = eap->arg; *p; ++p)
1595 {
1596 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1597 // DBCS can contain \ in a trail byte, skip the
1598 // double-byte character.
1599 ++p;
1600 else
1601 if (*p == '\\' || *p == '"')
1602 ++result;
1603 }
1604
1605 if (buf != NULL)
1606 {
1607 *buf++ = '"';
1608 for (p = eap->arg; *p; ++p)
1609 {
1610 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1611 // DBCS can contain \ in a trail byte, copy the
1612 // double-byte character to avoid escaping.
1613 *buf++ = *p++;
1614 else
1615 if (*p == '\\' || *p == '"')
1616 *buf++ = '\\';
1617 *buf++ = *p;
1618 }
1619 *buf = '"';
1620 }
1621
1622 break;
1623 case 2: // Quote and split (<f-args>)
1624 // This is hard, so only do it once, and cache the result
1625 if (*split_buf == NULL)
1626 *split_buf = uc_split_args(eap->arg, split_len);
1627
1628 result = *split_len;
1629 if (buf != NULL && result != 0)
1630 STRCPY(buf, *split_buf);
1631
1632 break;
1633 }
1634 break;
1635
1636 case ct_BANG:
1637 result = eap->forceit ? 1 : 0;
1638 if (quote)
1639 result += 2;
1640 if (buf != NULL)
1641 {
1642 if (quote)
1643 *buf++ = '"';
1644 if (eap->forceit)
1645 *buf++ = '!';
1646 if (quote)
1647 *buf = '"';
1648 }
1649 break;
1650
1651 case ct_LINE1:
1652 case ct_LINE2:
1653 case ct_RANGE:
1654 case ct_COUNT:
1655 {
1656 char num_buf[20];
1657 long num = (type == ct_LINE1) ? eap->line1 :
1658 (type == ct_LINE2) ? eap->line2 :
1659 (type == ct_RANGE) ? eap->addr_count :
1660 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1661 size_t num_len;
1662
1663 sprintf(num_buf, "%ld", num);
1664 num_len = STRLEN(num_buf);
1665 result = num_len;
1666
1667 if (quote)
1668 result += 2;
1669
1670 if (buf != NULL)
1671 {
1672 if (quote)
1673 *buf++ = '"';
1674 STRCPY(buf, num_buf);
1675 buf += num_len;
1676 if (quote)
1677 *buf = '"';
1678 }
1679
1680 break;
1681 }
1682
1683 case ct_MODS:
1684 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001685 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001686 break;
1687 }
1688
1689 case ct_REGISTER:
1690 result = eap->regname ? 1 : 0;
1691 if (quote)
1692 result += 2;
1693 if (buf != NULL)
1694 {
1695 if (quote)
1696 *buf++ = '\'';
1697 if (eap->regname)
1698 *buf++ = eap->regname;
1699 if (quote)
1700 *buf = '\'';
1701 }
1702 break;
1703
1704 case ct_LT:
1705 result = 1;
1706 if (buf != NULL)
1707 *buf = '<';
1708 break;
1709
1710 default:
1711 // Not recognized: just copy the '<' and return -1.
1712 result = (size_t)-1;
1713 if (buf != NULL)
1714 *buf = '<';
1715 break;
1716 }
1717
1718 return result;
1719}
1720
1721/*
1722 * Execute a user defined command.
1723 */
1724 void
1725do_ucmd(exarg_T *eap)
1726{
1727 char_u *buf;
1728 char_u *p;
1729 char_u *q;
1730
1731 char_u *start;
1732 char_u *end = NULL;
1733 char_u *ksp;
1734 size_t len, totlen;
1735
1736 size_t split_len = 0;
1737 char_u *split_buf = NULL;
1738 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001739 sctx_T save_current_sctx;
1740 int restore_current_sctx = FALSE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001741
1742 if (eap->cmdidx == CMD_USER)
1743 cmd = USER_CMD(eap->useridx);
1744 else
1745 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1746
1747 /*
1748 * Replace <> in the command by the arguments.
1749 * First round: "buf" is NULL, compute length, allocate "buf".
1750 * Second round: copy result into "buf".
1751 */
1752 buf = NULL;
1753 for (;;)
1754 {
1755 p = cmd->uc_rep; // source
1756 q = buf; // destination
1757 totlen = 0;
1758
1759 for (;;)
1760 {
1761 start = vim_strchr(p, '<');
1762 if (start != NULL)
1763 end = vim_strchr(start + 1, '>');
1764 if (buf != NULL)
1765 {
1766 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1767 ;
1768 if (*ksp == K_SPECIAL
1769 && (start == NULL || ksp < start || end == NULL)
1770 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1771# ifdef FEAT_GUI
1772 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1773# endif
1774 ))
1775 {
1776 // K_SPECIAL has been put in the buffer as K_SPECIAL
1777 // KS_SPECIAL KE_FILLER, like for mappings, but
1778 // do_cmdline() doesn't handle that, so convert it back.
1779 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1780 len = ksp - p;
1781 if (len > 0)
1782 {
1783 mch_memmove(q, p, len);
1784 q += len;
1785 }
1786 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1787 p = ksp + 3;
1788 continue;
1789 }
1790 }
1791
1792 // break if no <item> is found
1793 if (start == NULL || end == NULL)
1794 break;
1795
1796 // Include the '>'
1797 ++end;
1798
1799 // Take everything up to the '<'
1800 len = start - p;
1801 if (buf == NULL)
1802 totlen += len;
1803 else
1804 {
1805 mch_memmove(q, p, len);
1806 q += len;
1807 }
1808
1809 len = uc_check_code(start, end - start, q, cmd, eap,
1810 &split_buf, &split_len);
1811 if (len == (size_t)-1)
1812 {
1813 // no match, continue after '<'
1814 p = start + 1;
1815 len = 1;
1816 }
1817 else
1818 p = end;
1819 if (buf == NULL)
1820 totlen += len;
1821 else
1822 q += len;
1823 }
1824 if (buf != NULL) // second time here, finished
1825 {
1826 STRCPY(q, p);
1827 break;
1828 }
1829
1830 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001831 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001832 if (buf == NULL)
1833 {
1834 vim_free(split_buf);
1835 return;
1836 }
1837 }
1838
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001839 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1840 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001841 restore_current_sctx = TRUE;
1842 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001843 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001844#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001845 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001846#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001847 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001848
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001849 (void)do_cmdline(buf, eap->getline, eap->cookie,
1850 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001851
1852 // Careful: Do not use "cmd" here, it may have become invalid if a user
1853 // command was added.
1854 if (restore_current_sctx)
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001855 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001856 vim_free(buf);
1857 vim_free(split_buf);
1858}