blob: 40d951f6dcf5882710861fc9e84f0aeb95aae0c0 [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000025 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010026# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028# endif
29} ucmd_T;
30
31// List of all user commands.
32static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
33
34#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
35#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
36
37/*
38 * List of names for completion for ":command" with the EXPAND_ flag.
39 * Must be alphabetical for completion.
40 */
41static struct
42{
43 int expand;
44 char *name;
45} command_complete[] =
46{
47 {EXPAND_ARGLIST, "arglist"},
48 {EXPAND_AUGROUP, "augroup"},
49 {EXPAND_BEHAVE, "behave"},
50 {EXPAND_BUFFERS, "buffer"},
51 {EXPAND_COLORS, "color"},
52 {EXPAND_COMMANDS, "command"},
53 {EXPAND_COMPILER, "compiler"},
54#if defined(FEAT_CSCOPE)
55 {EXPAND_CSCOPE, "cscope"},
56#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020057#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020058 {EXPAND_USER_DEFINED, "custom"},
59 {EXPAND_USER_LIST, "customlist"},
60#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010061 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020062 {EXPAND_DIRECTORIES, "dir"},
63 {EXPAND_ENV_VARS, "environment"},
64 {EXPAND_EVENTS, "event"},
65 {EXPAND_EXPRESSION, "expression"},
66 {EXPAND_FILES, "file"},
67 {EXPAND_FILES_IN_PATH, "file_in_path"},
68 {EXPAND_FILETYPE, "filetype"},
69 {EXPAND_FUNCTIONS, "function"},
70 {EXPAND_HELP, "help"},
71 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020072 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020073#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
74 {EXPAND_LOCALES, "locale"},
75#endif
76 {EXPAND_MAPCLEAR, "mapclear"},
77 {EXPAND_MAPPINGS, "mapping"},
78 {EXPAND_MENUS, "menu"},
79 {EXPAND_MESSAGES, "messages"},
80 {EXPAND_OWNSYNTAX, "syntax"},
81#if defined(FEAT_PROFILE)
82 {EXPAND_SYNTIME, "syntime"},
83#endif
84 {EXPAND_SETTINGS, "option"},
85 {EXPAND_PACKADD, "packadd"},
86 {EXPAND_SHELLCMD, "shellcmd"},
87#if defined(FEAT_SIGNS)
88 {EXPAND_SIGN, "sign"},
89#endif
90 {EXPAND_TAGS, "tag"},
91 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
92 {EXPAND_USER, "user"},
93 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000094#if defined(FEAT_EVAL)
95 {EXPAND_BREAKPOINT, "breakpoint"},
96#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020097 {0, NULL}
98};
99
100/*
101 * List of names of address types. Must be alphabetical for completion.
102 */
103static struct
104{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200105 cmd_addr_T expand;
106 char *name;
107 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200108} addr_type_complete[] =
109{
110 {ADDR_ARGUMENTS, "arguments", "arg"},
111 {ADDR_LINES, "lines", "line"},
112 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
113 {ADDR_TABS, "tabs", "tab"},
114 {ADDR_BUFFERS, "buffers", "buf"},
115 {ADDR_WINDOWS, "windows", "win"},
116 {ADDR_QUICKFIX, "quickfix", "qf"},
117 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200118 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200119};
120
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200121/*
122 * Search for a user command that matches "eap->cmd".
123 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
124 * Return a pointer to just after the command.
125 * Return NULL if there is no matching command.
126 */
127 char_u *
128find_ucmd(
129 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000130 char_u *p, // end of the command (possibly including count)
131 int *full, // set to TRUE for a full match
132 expand_T *xp, // used for completion, NULL otherwise
133 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200134{
135 int len = (int)(p - eap->cmd);
136 int j, k, matchlen = 0;
137 ucmd_T *uc;
138 int found = FALSE;
139 int possible = FALSE;
140 char_u *cp, *np; // Point into typed cmd and test name
141 garray_T *gap;
142 int amb_local = FALSE; // Found ambiguous buffer-local command,
143 // only full match global is accepted.
144
145 /*
146 * Look for buffer-local user commands first, then global ones.
147 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000148 gap = &prevwin_curwin()->w_buffer->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.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000361 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200362
363 if (idx < buf->b_ucmds.ga_len)
364 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
365 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200366 if (idx < ucmds.ga_len)
367 return USER_CMD(idx)->uc_name;
368 return NULL;
369}
370
Dominique Pelle748b3082022-01-08 12:41:16 +0000371#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200372/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200373 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
374 * CMD_USER_BUF.
375 * Returns NULL if the command is not found.
376 */
377 char_u *
378get_user_command_name(int idx, int cmdidx)
379{
380 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
381 return USER_CMD(idx)->uc_name;
382 if (cmdidx == CMD_USER_BUF)
383 {
384 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000385 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200386
387 if (idx < buf->b_ucmds.ga_len)
388 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
389 }
390 return NULL;
391}
Dominique Pelle748b3082022-01-08 12:41:16 +0000392#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200393
394/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200395 * Function given to ExpandGeneric() to obtain the list of user address type
396 * names.
397 */
398 char_u *
399get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
400{
401 return (char_u *)addr_type_complete[idx].name;
402}
403
404/*
405 * Function given to ExpandGeneric() to obtain the list of user command
406 * attributes.
407 */
408 char_u *
409get_user_cmd_flags(expand_T *xp UNUSED, int idx)
410{
411 static char *user_cmd_flags[] = {
412 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000413 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200414 };
415
K.Takataeeec2542021-06-02 13:28:16 +0200416 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200417 return NULL;
418 return (char_u *)user_cmd_flags[idx];
419}
420
421/*
422 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
423 */
424 char_u *
425get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
426{
427 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
428
K.Takataeeec2542021-06-02 13:28:16 +0200429 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200430 return NULL;
431 return (char_u *)user_cmd_nargs[idx];
432}
433
434/*
435 * Function given to ExpandGeneric() to obtain the list of values for
436 * -complete.
437 */
438 char_u *
439get_user_cmd_complete(expand_T *xp UNUSED, int idx)
440{
441 return (char_u *)command_complete[idx].name;
442}
443
Dominique Pelle748b3082022-01-08 12:41:16 +0000444#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200445 int
446cmdcomplete_str_to_type(char_u *complete_str)
447{
448 int i;
449
450 for (i = 0; command_complete[i].expand != 0; ++i)
451 if (STRCMP(complete_str, command_complete[i].name) == 0)
452 return command_complete[i].expand;
453
454 return EXPAND_NOTHING;
455}
Dominique Pelle748b3082022-01-08 12:41:16 +0000456#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200457
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200458/*
459 * List user commands starting with "name[name_len]".
460 */
461 static void
462uc_list(char_u *name, size_t name_len)
463{
464 int i, j;
465 int found = FALSE;
466 ucmd_T *cmd;
467 int len;
468 int over;
469 long a;
470 garray_T *gap;
471
Bram Moolenaare38eab22019-12-05 21:50:01 +0100472 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000473 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200474 for (;;)
475 {
476 for (i = 0; i < gap->ga_len; ++i)
477 {
478 cmd = USER_CMD_GA(gap, i);
479 a = (long)cmd->uc_argt;
480
481 // Skip commands which don't match the requested prefix and
482 // commands filtered out.
483 if (STRNCMP(name, cmd->uc_name, name_len) != 0
484 || message_filtered(cmd->uc_name))
485 continue;
486
487 // Put out the title first time
488 if (!found)
489 msg_puts_title(_("\n Name Args Address Complete Definition"));
490 found = TRUE;
491 msg_putchar('\n');
492 if (got_int)
493 break;
494
495 // Special cases
496 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200497 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200498 {
499 msg_putchar('!');
500 --len;
501 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200502 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200503 {
504 msg_putchar('"');
505 --len;
506 }
507 if (gap != &ucmds)
508 {
509 msg_putchar('b');
510 --len;
511 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200512 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200513 {
514 msg_putchar('|');
515 --len;
516 }
517 while (len-- > 0)
518 msg_putchar(' ');
519
520 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
521 len = (int)STRLEN(cmd->uc_name) + 4;
522
523 do {
524 msg_putchar(' ');
525 ++len;
526 } while (len < 22);
527
528 // "over" is how much longer the name is than the column width for
529 // the name, we'll try to align what comes after.
530 over = len - 22;
531 len = 0;
532
533 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200534 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200535 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200536 case 0: IObuff[len++] = '0'; break;
537 case (EX_EXTRA): IObuff[len++] = '*'; break;
538 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
539 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
540 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200541 }
542
543 do {
544 IObuff[len++] = ' ';
545 } while (len < 5 - over);
546
547 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200548 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200549 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200550 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200551 {
552 // -count=N
553 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
554 len += (int)STRLEN(IObuff + len);
555 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200556 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200557 IObuff[len++] = '%';
558 else if (cmd->uc_def >= 0)
559 {
560 // -range=N
561 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
562 len += (int)STRLEN(IObuff + len);
563 }
564 else
565 IObuff[len++] = '.';
566 }
567
568 do {
569 IObuff[len++] = ' ';
570 } while (len < 8 - over);
571
572 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200573 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200574 if (addr_type_complete[j].expand != ADDR_LINES
575 && addr_type_complete[j].expand == cmd->uc_addr_type)
576 {
577 STRCPY(IObuff + len, addr_type_complete[j].shortname);
578 len += (int)STRLEN(IObuff + len);
579 break;
580 }
581
582 do {
583 IObuff[len++] = ' ';
584 } while (len < 13 - over);
585
586 // Completion
587 for (j = 0; command_complete[j].expand != 0; ++j)
588 if (command_complete[j].expand == cmd->uc_compl)
589 {
590 STRCPY(IObuff + len, command_complete[j].name);
591 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000592#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000593 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
594 && STRLEN(cmd->uc_compl_arg) < 200)
595 {
596 IObuff[len] = ',';
597 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
598 len += (int)STRLEN(IObuff + len);
599 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000600#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200601 break;
602 }
603
604 do {
605 IObuff[len++] = ' ';
606 } while (len < 25 - over);
607
608 IObuff[len] = '\0';
609 msg_outtrans(IObuff);
610
611 msg_outtrans_special(cmd->uc_rep, FALSE,
612 name_len == 0 ? Columns - 47 : 0);
613#ifdef FEAT_EVAL
614 if (p_verbose > 0)
615 last_set_msg(cmd->uc_script_ctx);
616#endif
617 out_flush();
618 ui_breakcheck();
619 if (got_int)
620 break;
621 }
622 if (gap == &ucmds || i < gap->ga_len)
623 break;
624 gap = &ucmds;
625 }
626
627 if (!found)
628 msg(_("No user-defined commands found"));
629}
630
631 char *
632uc_fun_cmd(void)
633{
634 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
635 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
636 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
637 0xb9, 0x7f, 0};
638 int i;
639
640 for (i = 0; fcmd[i]; ++i)
641 IObuff[i] = fcmd[i] - 0x40;
642 IObuff[i] = 0;
643 return (char *)IObuff;
644}
645
646/*
647 * Parse address type argument
648 */
649 static int
650parse_addr_type_arg(
651 char_u *value,
652 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200653 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200654{
655 int i, a, b;
656
Bram Moolenaarb7316892019-05-01 18:08:42 +0200657 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200658 {
659 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
660 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
661 if (a && b)
662 {
663 *addr_type_arg = addr_type_complete[i].expand;
664 break;
665 }
666 }
667
Bram Moolenaarb7316892019-05-01 18:08:42 +0200668 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200669 {
670 char_u *err = value;
671
672 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
673 ;
674 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000675 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200676 return FAIL;
677 }
678
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200679 return OK;
680}
681
682/*
683 * Parse a completion argument "value[vallen]".
684 * The detected completion goes in "*complp", argument type in "*argt".
685 * When there is an argument, for function and user defined completion, it's
686 * copied to allocated memory and stored in "*compl_arg".
687 * Returns FAIL if something is wrong.
688 */
689 int
690parse_compl_arg(
691 char_u *value,
692 int vallen,
693 int *complp,
694 long *argt,
695 char_u **compl_arg UNUSED)
696{
697 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200698# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200699 size_t arglen = 0;
700# endif
701 int i;
702 int valend = vallen;
703
704 // Look for any argument part - which is the part after any ','
705 for (i = 0; i < vallen; ++i)
706 {
707 if (value[i] == ',')
708 {
709 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200710# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200711 arglen = vallen - i - 1;
712# endif
713 valend = i;
714 break;
715 }
716 }
717
718 for (i = 0; command_complete[i].expand != 0; ++i)
719 {
720 if ((int)STRLEN(command_complete[i].name) == valend
721 && STRNCMP(value, command_complete[i].name, valend) == 0)
722 {
723 *complp = command_complete[i].expand;
724 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200725 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200726 else if (command_complete[i].expand == EXPAND_DIRECTORIES
727 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200728 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200729 break;
730 }
731 }
732
733 if (command_complete[i].expand == 0)
734 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000735 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200736 return FAIL;
737 }
738
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200739# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200740 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
741 && arg != NULL)
742# else
743 if (arg != NULL)
744# endif
745 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000746 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200747 return FAIL;
748 }
749
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200750# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200751 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
752 && arg == NULL)
753 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000754 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200755 return FAIL;
756 }
757
758 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200759 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200760# endif
761 return OK;
762}
763
764/*
765 * Scan attributes in the ":command" command.
766 * Return FAIL when something is wrong.
767 */
768 static int
769uc_scan_attr(
770 char_u *attr,
771 size_t len,
772 long *argt,
773 long *def,
774 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200775 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200777 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200778{
779 char_u *p;
780
781 if (len == 0)
782 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000783 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200784 return FAIL;
785 }
786
787 // First, try the simple attributes (no arguments)
788 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200789 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200790 else if (STRNICMP(attr, "buffer", len) == 0)
791 *flags |= UC_BUFFER;
792 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200793 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000794 else if (STRNICMP(attr, "keepscript", len) == 0)
795 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200796 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200797 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200798 else
799 {
800 int i;
801 char_u *val = NULL;
802 size_t vallen = 0;
803 size_t attrlen = len;
804
805 // Look for the attribute name - which is the part before any '='
806 for (i = 0; i < (int)len; ++i)
807 {
808 if (attr[i] == '=')
809 {
810 val = &attr[i + 1];
811 vallen = len - i - 1;
812 attrlen = i;
813 break;
814 }
815 }
816
817 if (STRNICMP(attr, "nargs", attrlen) == 0)
818 {
819 if (vallen == 1)
820 {
821 if (*val == '0')
822 // Do nothing - this is the default
823 ;
824 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200825 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200826 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200827 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200828 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200829 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200830 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200831 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200832 else
833 goto wrong_nargs;
834 }
835 else
836 {
837wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000838 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200839 return FAIL;
840 }
841 }
842 else if (STRNICMP(attr, "range", attrlen) == 0)
843 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200844 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200845 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200846 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200847 else if (val != NULL)
848 {
849 p = val;
850 if (*def >= 0)
851 {
852two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000853 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200854 return FAIL;
855 }
856
857 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200858 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200859
860 if (p != val + vallen || vallen == 0)
861 {
862invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000863 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200864 return FAIL;
865 }
866 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200867 // default for -range is using buffer lines
868 if (*addr_type_arg == ADDR_NONE)
869 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870 }
871 else if (STRNICMP(attr, "count", attrlen) == 0)
872 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200873 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200874 // default for -count is using any number
875 if (*addr_type_arg == ADDR_NONE)
876 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877
878 if (val != NULL)
879 {
880 p = val;
881 if (*def >= 0)
882 goto two_count;
883
884 *def = getdigits(&p);
885
886 if (p != val + vallen)
887 goto invalid_count;
888 }
889
890 if (*def < 0)
891 *def = 0;
892 }
893 else if (STRNICMP(attr, "complete", attrlen) == 0)
894 {
895 if (val == NULL)
896 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000897 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200898 return FAIL;
899 }
900
Bram Moolenaar52111f82019-04-29 21:30:45 +0200901 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 == FAIL)
903 return FAIL;
904 }
905 else if (STRNICMP(attr, "addr", attrlen) == 0)
906 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200907 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 if (val == NULL)
909 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000910 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200911 return FAIL;
912 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200913 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200915 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200916 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200917 }
918 else
919 {
920 char_u ch = attr[len];
921 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000922 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200923 attr[len] = ch;
924 return FAIL;
925 }
926 }
927
928 return OK;
929}
930
931/*
932 * Add a user command to the list or replace an existing one.
933 */
934 static int
935uc_add_command(
936 char_u *name,
937 size_t name_len,
938 char_u *rep,
939 long argt,
940 long def,
941 int flags,
942 int compl,
943 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200944 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200945 int force)
946{
947 ucmd_T *cmd = NULL;
948 char_u *p;
949 int i;
950 int cmp = 1;
951 char_u *rep_buf = NULL;
952 garray_T *gap;
953
Bram Moolenaar459fd782019-10-13 16:43:39 +0200954 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200955 if (rep_buf == NULL)
956 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200957 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 rep_buf = vim_strsave(rep);
959
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200960 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961 if (rep_buf == NULL)
962 return FAIL;
963 }
964
965 // get address of growarray: global or in curbuf
966 if (flags & UC_BUFFER)
967 {
968 gap = &curbuf->b_ucmds;
969 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +0000970 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200971 }
972 else
973 gap = &ucmds;
974
975 // Search for the command in the already defined commands.
976 for (i = 0; i < gap->ga_len; ++i)
977 {
978 size_t len;
979
980 cmd = USER_CMD_GA(gap, i);
981 len = STRLEN(cmd->uc_name);
982 cmp = STRNCMP(name, cmd->uc_name, name_len);
983 if (cmp == 0)
984 {
985 if (name_len < len)
986 cmp = -1;
987 else if (name_len > len)
988 cmp = 1;
989 }
990
991 if (cmp == 0)
992 {
993 // Command can be replaced with "command!" and when sourcing the
994 // same script again, but only once.
995 if (!force
996#ifdef FEAT_EVAL
997 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
998 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
999#endif
1000 )
1001 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001002 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001003 name);
1004 goto fail;
1005 }
1006
1007 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001008#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001009 VIM_CLEAR(cmd->uc_compl_arg);
1010#endif
1011 break;
1012 }
1013
1014 // Stop as soon as we pass the name to add
1015 if (cmp < 0)
1016 break;
1017 }
1018
1019 // Extend the array unless we're replacing an existing command
1020 if (cmp != 0)
1021 {
1022 if (ga_grow(gap, 1) != OK)
1023 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001024 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001025 goto fail;
1026
1027 cmd = USER_CMD_GA(gap, i);
1028 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1029
1030 ++gap->ga_len;
1031
1032 cmd->uc_name = p;
1033 }
1034
1035 cmd->uc_rep = rep_buf;
1036 cmd->uc_argt = argt;
1037 cmd->uc_def = def;
1038 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001039 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001040 if (flags & UC_VIM9)
1041 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001042 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001043#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001044 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001045 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001046#endif
1047 cmd->uc_addr_type = addr_type;
1048
1049 return OK;
1050
1051fail:
1052 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001053#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001054 vim_free(compl_arg);
1055#endif
1056 return FAIL;
1057}
1058
1059/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001060 * If "p" starts with "{" then read a block of commands until "}".
1061 * Used for ":command" and ":autocmd".
1062 */
1063 char_u *
1064may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1065{
1066 char_u *retp = p;
1067
1068 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1069 && eap->getline != NULL)
1070 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001071 garray_T ga;
1072 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001073
1074 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001075 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001076 return retp;
1077
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001078 // If the argument ends in "}" it must have been concatenated already
1079 // for ISN_EXEC.
1080 if (p[STRLEN(p) - 1] != '}')
1081 // Read lines between '{' and '}'. Does not support nesting or
1082 // here-doc constructs.
1083 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001084 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001085 vim_free(line);
1086 if ((line = eap->getline(':', eap->cookie,
1087 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1088 {
1089 emsg(_(e_missing_rcurly));
1090 break;
1091 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001092 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001093 break;
1094 if (*skipwhite(line) == '}')
1095 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001096 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001097 vim_free(line);
1098 retp = *tofree = ga_concat_strings(&ga, "\n");
1099 ga_clear_strings(&ga);
1100 *flags |= UC_VIM9;
1101 }
1102 return retp;
1103}
1104
1105/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001106 * ":command ..." implementation
1107 */
1108 void
1109ex_command(exarg_T *eap)
1110{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001111 char_u *name;
1112 char_u *end;
1113 char_u *p;
1114 long argt = 0;
1115 long def = -1;
1116 int flags = 0;
1117 int compl = EXPAND_NOTHING;
1118 char_u *compl_arg = NULL;
1119 cmd_addr_T addr_type_arg = ADDR_NONE;
1120 int has_attr = (eap->arg[0] == '-');
1121 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001122
1123 p = eap->arg;
1124
1125 // Check for attributes
1126 while (*p == '-')
1127 {
1128 ++p;
1129 end = skiptowhite(p);
1130 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1131 &compl_arg, &addr_type_arg) == FAIL)
1132 return;
1133 p = skipwhite(end);
1134 }
1135
1136 // Get the name (if any) and skip to the following argument
1137 name = p;
1138 if (ASCII_ISALPHA(*p))
1139 while (ASCII_ISALNUM(*p))
1140 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001141 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001142 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001143 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001144 return;
1145 }
1146 end = p;
1147 name_len = (int)(end - name);
1148
1149 // If there is nothing after the name, and no attributes were specified,
1150 // we are listing commands
1151 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001152 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001153 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001154 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001155 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001156 else if ((name_len == 1 && *name == 'X')
1157 || (name_len <= 4
1158 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001159 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001160 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001161 {
1162 // Some plugins rely on silently ignoring the mistake, only make this
1163 // an error in Vim9 script.
1164 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001165 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001166 else
1167 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001168 (char_u *)_(e_complete_used_without_allowing_arguments),
1169 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001170 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001171 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001172 {
1173 char_u *tofree = NULL;
1174
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001175 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001176
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001177 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1178 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001179 vim_free(tofree);
1180 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001181}
1182
1183/*
1184 * ":comclear" implementation
1185 * Clear all user commands, global and for current buffer.
1186 */
1187 void
1188ex_comclear(exarg_T *eap UNUSED)
1189{
1190 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001191 if (curbuf != NULL)
1192 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001193}
1194
1195/*
1196 * Clear all user commands for "gap".
1197 */
1198 void
1199uc_clear(garray_T *gap)
1200{
1201 int i;
1202 ucmd_T *cmd;
1203
1204 for (i = 0; i < gap->ga_len; ++i)
1205 {
1206 cmd = USER_CMD_GA(gap, i);
1207 vim_free(cmd->uc_name);
1208 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001209# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001210 vim_free(cmd->uc_compl_arg);
1211# endif
1212 }
1213 ga_clear(gap);
1214}
1215
1216/*
1217 * ":delcommand" implementation
1218 */
1219 void
1220ex_delcommand(exarg_T *eap)
1221{
1222 int i = 0;
1223 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001224 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001225 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001226 char_u *arg = eap->arg;
1227 int buffer_only = FALSE;
1228
1229 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1230 {
1231 buffer_only = TRUE;
1232 arg = skipwhite(arg + 7);
1233 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001234
1235 gap = &curbuf->b_ucmds;
1236 for (;;)
1237 {
1238 for (i = 0; i < gap->ga_len; ++i)
1239 {
1240 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001241 res = STRCMP(arg, cmd->uc_name);
1242 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001243 break;
1244 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001245 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001246 break;
1247 gap = &ucmds;
1248 }
1249
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001250 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001251 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001252 semsg(_(buffer_only
1253 ? e_no_such_user_defined_command_in_current_buffer_str
1254 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001255 return;
1256 }
1257
1258 vim_free(cmd->uc_name);
1259 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001260# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001261 vim_free(cmd->uc_compl_arg);
1262# endif
1263
1264 --gap->ga_len;
1265
1266 if (i < gap->ga_len)
1267 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1268}
1269
1270/*
1271 * Split and quote args for <f-args>.
1272 */
1273 static char_u *
1274uc_split_args(char_u *arg, size_t *lenp)
1275{
1276 char_u *buf;
1277 char_u *p;
1278 char_u *q;
1279 int len;
1280
1281 // Precalculate length
1282 p = arg;
1283 len = 2; // Initial and final quotes
1284
1285 while (*p)
1286 {
1287 if (p[0] == '\\' && p[1] == '\\')
1288 {
1289 len += 2;
1290 p += 2;
1291 }
1292 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1293 {
1294 len += 1;
1295 p += 2;
1296 }
1297 else if (*p == '\\' || *p == '"')
1298 {
1299 len += 2;
1300 p += 1;
1301 }
1302 else if (VIM_ISWHITE(*p))
1303 {
1304 p = skipwhite(p);
1305 if (*p == NUL)
1306 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001307 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001308 }
1309 else
1310 {
1311 int charlen = (*mb_ptr2len)(p);
1312
1313 len += charlen;
1314 p += charlen;
1315 }
1316 }
1317
1318 buf = alloc(len + 1);
1319 if (buf == NULL)
1320 {
1321 *lenp = 0;
1322 return buf;
1323 }
1324
1325 p = arg;
1326 q = buf;
1327 *q++ = '"';
1328 while (*p)
1329 {
1330 if (p[0] == '\\' && p[1] == '\\')
1331 {
1332 *q++ = '\\';
1333 *q++ = '\\';
1334 p += 2;
1335 }
1336 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1337 {
1338 *q++ = p[1];
1339 p += 2;
1340 }
1341 else if (*p == '\\' || *p == '"')
1342 {
1343 *q++ = '\\';
1344 *q++ = *p++;
1345 }
1346 else if (VIM_ISWHITE(*p))
1347 {
1348 p = skipwhite(p);
1349 if (*p == NUL)
1350 break;
1351 *q++ = '"';
1352 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001353 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001354 *q++ = '"';
1355 }
1356 else
1357 {
1358 MB_COPY_CHAR(p, q);
1359 }
1360 }
1361 *q++ = '"';
1362 *q = 0;
1363
1364 *lenp = len;
1365 return buf;
1366}
1367
1368 static size_t
1369add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1370{
1371 size_t result;
1372
1373 result = STRLEN(mod_str);
1374 if (*multi_mods)
1375 result += 1;
1376 if (buf != NULL)
1377 {
1378 if (*multi_mods)
1379 STRCAT(buf, " ");
1380 STRCAT(buf, mod_str);
1381 }
1382
1383 *multi_mods = 1;
1384
1385 return result;
1386}
1387
1388/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001389 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001390 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001391 */
1392 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001393add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001394{
1395 size_t result = 0;
1396
1397 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001398 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001399 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1400 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001401 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001402 result += add_cmd_modifier(buf, "belowright", multi_mods);
1403 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001404 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001405 result += add_cmd_modifier(buf, "botright", multi_mods);
1406
1407 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001408 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001409 result += add_cmd_modifier(buf, "tab", multi_mods);
1410 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001411 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001412 result += add_cmd_modifier(buf, "topleft", multi_mods);
1413 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001414 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001415 result += add_cmd_modifier(buf, "vertical", multi_mods);
1416 return result;
1417}
1418
1419/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001420 * Generate text for the "cmod" command modifiers.
1421 * If "buf" is NULL just return the length.
1422 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001423 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001424produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1425{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001426 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001427 int multi_mods = 0;
1428 int i;
1429 typedef struct {
1430 int flag;
1431 char *name;
1432 } mod_entry_T;
1433 static mod_entry_T mod_entries[] = {
1434#ifdef FEAT_BROWSE_CMD
1435 {CMOD_BROWSE, "browse"},
1436#endif
1437#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1438 {CMOD_CONFIRM, "confirm"},
1439#endif
1440 {CMOD_HIDE, "hide"},
1441 {CMOD_KEEPALT, "keepalt"},
1442 {CMOD_KEEPJUMPS, "keepjumps"},
1443 {CMOD_KEEPMARKS, "keepmarks"},
1444 {CMOD_KEEPPATTERNS, "keeppatterns"},
1445 {CMOD_LOCKMARKS, "lockmarks"},
1446 {CMOD_NOSWAPFILE, "noswapfile"},
1447 {CMOD_UNSILENT, "unsilent"},
1448 {CMOD_NOAUTOCMD, "noautocmd"},
1449#ifdef HAVE_SANDBOX
1450 {CMOD_SANDBOX, "sandbox"},
1451#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001452 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001453 {0, NULL}
1454 };
1455
1456 result = quote ? 2 : 0;
1457 if (buf != NULL)
1458 {
1459 if (quote)
1460 *buf++ = '"';
1461 *buf = '\0';
1462 }
1463
1464 // the modifiers that are simple flags
1465 for (i = 0; mod_entries[i].name != NULL; ++i)
1466 if (cmod->cmod_flags & mod_entries[i].flag)
1467 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1468
1469 // :silent
1470 if (cmod->cmod_flags & CMOD_SILENT)
1471 result += add_cmd_modifier(buf,
1472 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1473 : "silent", &multi_mods);
1474 // :verbose
1475 if (p_verbose > 0)
1476 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1477 // flags from cmod->cmod_split
1478 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1479 if (quote && buf != NULL)
1480 {
1481 buf += result - 2;
1482 *buf = '"';
1483 }
1484 return result;
1485}
1486
1487/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001488 * Check for a <> code in a user command.
1489 * "code" points to the '<'. "len" the length of the <> (inclusive).
1490 * "buf" is where the result is to be added.
1491 * "split_buf" points to a buffer used for splitting, caller should free it.
1492 * "split_len" is the length of what "split_buf" contains.
1493 * Returns the length of the replacement, which has been added to "buf".
1494 * Returns -1 if there was no match, and only the "<" has been copied.
1495 */
1496 static size_t
1497uc_check_code(
1498 char_u *code,
1499 size_t len,
1500 char_u *buf,
1501 ucmd_T *cmd, // the user command we're expanding
1502 exarg_T *eap, // ex arguments
1503 char_u **split_buf,
1504 size_t *split_len)
1505{
1506 size_t result = 0;
1507 char_u *p = code + 1;
1508 size_t l = len - 2;
1509 int quote = 0;
1510 enum {
1511 ct_ARGS,
1512 ct_BANG,
1513 ct_COUNT,
1514 ct_LINE1,
1515 ct_LINE2,
1516 ct_RANGE,
1517 ct_MODS,
1518 ct_REGISTER,
1519 ct_LT,
1520 ct_NONE
1521 } type = ct_NONE;
1522
1523 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1524 {
1525 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1526 p += 2;
1527 l -= 2;
1528 }
1529
1530 ++l;
1531 if (l <= 1)
1532 type = ct_NONE;
1533 else if (STRNICMP(p, "args>", l) == 0)
1534 type = ct_ARGS;
1535 else if (STRNICMP(p, "bang>", l) == 0)
1536 type = ct_BANG;
1537 else if (STRNICMP(p, "count>", l) == 0)
1538 type = ct_COUNT;
1539 else if (STRNICMP(p, "line1>", l) == 0)
1540 type = ct_LINE1;
1541 else if (STRNICMP(p, "line2>", l) == 0)
1542 type = ct_LINE2;
1543 else if (STRNICMP(p, "range>", l) == 0)
1544 type = ct_RANGE;
1545 else if (STRNICMP(p, "lt>", l) == 0)
1546 type = ct_LT;
1547 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1548 type = ct_REGISTER;
1549 else if (STRNICMP(p, "mods>", l) == 0)
1550 type = ct_MODS;
1551
1552 switch (type)
1553 {
1554 case ct_ARGS:
1555 // Simple case first
1556 if (*eap->arg == NUL)
1557 {
1558 if (quote == 1)
1559 {
1560 result = 2;
1561 if (buf != NULL)
1562 STRCPY(buf, "''");
1563 }
1564 else
1565 result = 0;
1566 break;
1567 }
1568
1569 // When specified there is a single argument don't split it.
1570 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001571 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001572 quote = 1;
1573
1574 switch (quote)
1575 {
1576 case 0: // No quoting, no splitting
1577 result = STRLEN(eap->arg);
1578 if (buf != NULL)
1579 STRCPY(buf, eap->arg);
1580 break;
1581 case 1: // Quote, but don't split
1582 result = STRLEN(eap->arg) + 2;
1583 for (p = eap->arg; *p; ++p)
1584 {
1585 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1586 // DBCS can contain \ in a trail byte, skip the
1587 // double-byte character.
1588 ++p;
1589 else
1590 if (*p == '\\' || *p == '"')
1591 ++result;
1592 }
1593
1594 if (buf != NULL)
1595 {
1596 *buf++ = '"';
1597 for (p = eap->arg; *p; ++p)
1598 {
1599 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1600 // DBCS can contain \ in a trail byte, copy the
1601 // double-byte character to avoid escaping.
1602 *buf++ = *p++;
1603 else
1604 if (*p == '\\' || *p == '"')
1605 *buf++ = '\\';
1606 *buf++ = *p;
1607 }
1608 *buf = '"';
1609 }
1610
1611 break;
1612 case 2: // Quote and split (<f-args>)
1613 // This is hard, so only do it once, and cache the result
1614 if (*split_buf == NULL)
1615 *split_buf = uc_split_args(eap->arg, split_len);
1616
1617 result = *split_len;
1618 if (buf != NULL && result != 0)
1619 STRCPY(buf, *split_buf);
1620
1621 break;
1622 }
1623 break;
1624
1625 case ct_BANG:
1626 result = eap->forceit ? 1 : 0;
1627 if (quote)
1628 result += 2;
1629 if (buf != NULL)
1630 {
1631 if (quote)
1632 *buf++ = '"';
1633 if (eap->forceit)
1634 *buf++ = '!';
1635 if (quote)
1636 *buf = '"';
1637 }
1638 break;
1639
1640 case ct_LINE1:
1641 case ct_LINE2:
1642 case ct_RANGE:
1643 case ct_COUNT:
1644 {
1645 char num_buf[20];
1646 long num = (type == ct_LINE1) ? eap->line1 :
1647 (type == ct_LINE2) ? eap->line2 :
1648 (type == ct_RANGE) ? eap->addr_count :
1649 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1650 size_t num_len;
1651
1652 sprintf(num_buf, "%ld", num);
1653 num_len = STRLEN(num_buf);
1654 result = num_len;
1655
1656 if (quote)
1657 result += 2;
1658
1659 if (buf != NULL)
1660 {
1661 if (quote)
1662 *buf++ = '"';
1663 STRCPY(buf, num_buf);
1664 buf += num_len;
1665 if (quote)
1666 *buf = '"';
1667 }
1668
1669 break;
1670 }
1671
1672 case ct_MODS:
1673 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001674 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001675 break;
1676 }
1677
1678 case ct_REGISTER:
1679 result = eap->regname ? 1 : 0;
1680 if (quote)
1681 result += 2;
1682 if (buf != NULL)
1683 {
1684 if (quote)
1685 *buf++ = '\'';
1686 if (eap->regname)
1687 *buf++ = eap->regname;
1688 if (quote)
1689 *buf = '\'';
1690 }
1691 break;
1692
1693 case ct_LT:
1694 result = 1;
1695 if (buf != NULL)
1696 *buf = '<';
1697 break;
1698
1699 default:
1700 // Not recognized: just copy the '<' and return -1.
1701 result = (size_t)-1;
1702 if (buf != NULL)
1703 *buf = '<';
1704 break;
1705 }
1706
1707 return result;
1708}
1709
1710/*
1711 * Execute a user defined command.
1712 */
1713 void
1714do_ucmd(exarg_T *eap)
1715{
1716 char_u *buf;
1717 char_u *p;
1718 char_u *q;
1719
1720 char_u *start;
1721 char_u *end = NULL;
1722 char_u *ksp;
1723 size_t len, totlen;
1724
1725 size_t split_len = 0;
1726 char_u *split_buf = NULL;
1727 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001728 sctx_T save_current_sctx;
1729 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001730#ifdef FEAT_EVAL
1731 int restore_script_version = 0;
1732#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001733
1734 if (eap->cmdidx == CMD_USER)
1735 cmd = USER_CMD(eap->useridx);
1736 else
1737 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1738
1739 /*
1740 * Replace <> in the command by the arguments.
1741 * First round: "buf" is NULL, compute length, allocate "buf".
1742 * Second round: copy result into "buf".
1743 */
1744 buf = NULL;
1745 for (;;)
1746 {
1747 p = cmd->uc_rep; // source
1748 q = buf; // destination
1749 totlen = 0;
1750
1751 for (;;)
1752 {
1753 start = vim_strchr(p, '<');
1754 if (start != NULL)
1755 end = vim_strchr(start + 1, '>');
1756 if (buf != NULL)
1757 {
1758 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1759 ;
1760 if (*ksp == K_SPECIAL
1761 && (start == NULL || ksp < start || end == NULL)
1762 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1763# ifdef FEAT_GUI
1764 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1765# endif
1766 ))
1767 {
1768 // K_SPECIAL has been put in the buffer as K_SPECIAL
1769 // KS_SPECIAL KE_FILLER, like for mappings, but
1770 // do_cmdline() doesn't handle that, so convert it back.
1771 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1772 len = ksp - p;
1773 if (len > 0)
1774 {
1775 mch_memmove(q, p, len);
1776 q += len;
1777 }
1778 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1779 p = ksp + 3;
1780 continue;
1781 }
1782 }
1783
1784 // break if no <item> is found
1785 if (start == NULL || end == NULL)
1786 break;
1787
1788 // Include the '>'
1789 ++end;
1790
1791 // Take everything up to the '<'
1792 len = start - p;
1793 if (buf == NULL)
1794 totlen += len;
1795 else
1796 {
1797 mch_memmove(q, p, len);
1798 q += len;
1799 }
1800
1801 len = uc_check_code(start, end - start, q, cmd, eap,
1802 &split_buf, &split_len);
1803 if (len == (size_t)-1)
1804 {
1805 // no match, continue after '<'
1806 p = start + 1;
1807 len = 1;
1808 }
1809 else
1810 p = end;
1811 if (buf == NULL)
1812 totlen += len;
1813 else
1814 q += len;
1815 }
1816 if (buf != NULL) // second time here, finished
1817 {
1818 STRCPY(q, p);
1819 break;
1820 }
1821
1822 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001823 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001824 if (buf == NULL)
1825 {
1826 vim_free(split_buf);
1827 return;
1828 }
1829 }
1830
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001831 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1832 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001833 restore_current_sctx = TRUE;
1834 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001835 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001836#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001837 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001838 if (cmd->uc_flags & UC_VIM9)
1839 {
1840 // In a {} block variables use Vim9 script rules, even in a legacy
1841 // script.
1842 restore_script_version =
1843 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1844 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1845 }
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 Moolenaar98b7fe72022-03-23 21:36:27 +00001855 {
1856#ifdef FEAT_EVAL
1857 if (restore_script_version != 0)
1858 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1859 restore_script_version;
1860#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001861 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001862 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001863 vim_free(buf);
1864 vim_free(split_buf);
1865}