blob: 1ff4d0d49221cc337b526ac69e7fbe0e62ed8017 [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 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000144 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200145 for (;;)
146 {
147 for (j = 0; j < gap->ga_len; ++j)
148 {
149 uc = USER_CMD_GA(gap, j);
150 cp = eap->cmd;
151 np = uc->uc_name;
152 k = 0;
153 while (k < len && *np != NUL && *cp++ == *np++)
154 k++;
155 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
156 {
157 // If finding a second match, the command is ambiguous. But
158 // not if a buffer-local command wasn't a full match and a
159 // global command is a full match.
160 if (k == len && found && *np != NUL)
161 {
162 if (gap == &ucmds)
163 return NULL;
164 amb_local = TRUE;
165 }
166
167 if (!found || (k == len && *np == NUL))
168 {
169 // If we matched up to a digit, then there could
170 // be another command including the digit that we
171 // should use instead.
172 if (k == len)
173 found = TRUE;
174 else
175 possible = TRUE;
176
177 if (gap == &ucmds)
178 eap->cmdidx = CMD_USER;
179 else
180 eap->cmdidx = CMD_USER_BUF;
181 eap->argt = (long)uc->uc_argt;
182 eap->useridx = j;
183 eap->addr_type = uc->uc_addr_type;
184
Bram Moolenaar52111f82019-04-29 21:30:45 +0200185 if (complp != NULL)
186 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200187# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200188 if (xp != NULL)
189 {
190 xp->xp_arg = uc->uc_compl_arg;
191 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100192 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200193 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200194# endif
195 // Do not search for further abbreviations
196 // if this is an exact match.
197 matchlen = k;
198 if (k == len && *np == NUL)
199 {
200 if (full != NULL)
201 *full = TRUE;
202 amb_local = FALSE;
203 break;
204 }
205 }
206 }
207 }
208
209 // Stop if we found a full match or searched all.
210 if (j < gap->ga_len || gap == &ucmds)
211 break;
212 gap = &ucmds;
213 }
214
215 // Only found ambiguous matches.
216 if (amb_local)
217 {
218 if (xp != NULL)
219 xp->xp_context = EXPAND_UNSUCCESSFUL;
220 return NULL;
221 }
222
223 // The match we found may be followed immediately by a number. Move "p"
224 // back to point to it.
225 if (found || possible)
226 return p + (matchlen - len);
227 return p;
228}
229
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000230/*
231 * Set completion context for :command
232 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200233 char_u *
234set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
235{
236 char_u *arg = arg_in;
237 char_u *p;
238
239 // Check for attributes
240 while (*arg == '-')
241 {
242 arg++; // Skip "-"
243 p = skiptowhite(arg);
244 if (*p == NUL)
245 {
246 // Cursor is still in the attribute
247 p = vim_strchr(arg, '=');
248 if (p == NULL)
249 {
250 // No "=", so complete attribute names
251 xp->xp_context = EXPAND_USER_CMD_FLAGS;
252 xp->xp_pattern = arg;
253 return NULL;
254 }
255
256 // For the -complete, -nargs and -addr attributes, we complete
257 // their arguments as well.
258 if (STRNICMP(arg, "complete", p - arg) == 0)
259 {
260 xp->xp_context = EXPAND_USER_COMPLETE;
261 xp->xp_pattern = p + 1;
262 return NULL;
263 }
264 else if (STRNICMP(arg, "nargs", p - arg) == 0)
265 {
266 xp->xp_context = EXPAND_USER_NARGS;
267 xp->xp_pattern = p + 1;
268 return NULL;
269 }
270 else if (STRNICMP(arg, "addr", p - arg) == 0)
271 {
272 xp->xp_context = EXPAND_USER_ADDR_TYPE;
273 xp->xp_pattern = p + 1;
274 return NULL;
275 }
276 return NULL;
277 }
278 arg = skipwhite(p);
279 }
280
281 // After the attributes comes the new command name
282 p = skiptowhite(arg);
283 if (*p == NUL)
284 {
285 xp->xp_context = EXPAND_USER_COMMANDS;
286 xp->xp_pattern = arg;
287 return NULL;
288 }
289
290 // And finally comes a normal command
291 return skipwhite(p);
292}
293
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000294/*
295 * Set the completion context for the argument of a user defined command.
296 */
297 char_u *
298set_context_in_user_cmdarg(
299 char_u *cmd UNUSED,
300 char_u *arg,
301 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000302 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000303 expand_T *xp,
304 int forceit)
305{
306 char_u *p;
307
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000308 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000309 return NULL;
310
311 if (argt & EX_XFILE)
312 {
313 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000314 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000315 return NULL;
316 }
317
318#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000319 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000320 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
321#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000322 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000323 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000324 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000325 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
326 FALSE, CMD_map);
327 // Find start of last argument.
328 p = arg;
329 while (*p)
330 {
331 if (*p == ' ')
332 // argument starts after a space
333 arg = p + 1;
334 else if (*p == '\\' && *(p + 1) != NUL)
335 ++p; // skip over escaped character
336 MB_PTR_ADV(p);
337 }
338 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000339 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000340
341 return NULL;
342}
343
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200344 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200345expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200346{
347 return get_user_commands(NULL, idx - (int)CMD_SIZE);
348}
349
350/*
351 * Function given to ExpandGeneric() to obtain the list of user command names.
352 */
353 char_u *
354get_user_commands(expand_T *xp UNUSED, int idx)
355{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200356 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000357 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200358
359 if (idx < buf->b_ucmds.ga_len)
360 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
361 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200362 if (idx < ucmds.ga_len)
363 return USER_CMD(idx)->uc_name;
364 return NULL;
365}
366
Dominique Pelle748b3082022-01-08 12:41:16 +0000367#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200368/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200369 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
370 * CMD_USER_BUF.
371 * Returns NULL if the command is not found.
372 */
373 char_u *
374get_user_command_name(int idx, int cmdidx)
375{
376 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
377 return USER_CMD(idx)->uc_name;
378 if (cmdidx == CMD_USER_BUF)
379 {
380 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000381 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200382
383 if (idx < buf->b_ucmds.ga_len)
384 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
385 }
386 return NULL;
387}
Dominique Pelle748b3082022-01-08 12:41:16 +0000388#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200389
390/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200391 * Function given to ExpandGeneric() to obtain the list of user address type
392 * names.
393 */
394 char_u *
395get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
396{
397 return (char_u *)addr_type_complete[idx].name;
398}
399
400/*
401 * Function given to ExpandGeneric() to obtain the list of user command
402 * attributes.
403 */
404 char_u *
405get_user_cmd_flags(expand_T *xp UNUSED, int idx)
406{
407 static char *user_cmd_flags[] = {
408 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000409 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200410 };
411
K.Takataeeec2542021-06-02 13:28:16 +0200412 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200413 return NULL;
414 return (char_u *)user_cmd_flags[idx];
415}
416
417/*
418 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
419 */
420 char_u *
421get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
422{
423 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
424
K.Takataeeec2542021-06-02 13:28:16 +0200425 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200426 return NULL;
427 return (char_u *)user_cmd_nargs[idx];
428}
429
430/*
431 * Function given to ExpandGeneric() to obtain the list of values for
432 * -complete.
433 */
434 char_u *
435get_user_cmd_complete(expand_T *xp UNUSED, int idx)
436{
437 return (char_u *)command_complete[idx].name;
438}
439
Dominique Pelle748b3082022-01-08 12:41:16 +0000440#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200441 int
442cmdcomplete_str_to_type(char_u *complete_str)
443{
444 int i;
445
446 for (i = 0; command_complete[i].expand != 0; ++i)
447 if (STRCMP(complete_str, command_complete[i].name) == 0)
448 return command_complete[i].expand;
449
450 return EXPAND_NOTHING;
451}
Dominique Pelle748b3082022-01-08 12:41:16 +0000452#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200453
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200454/*
455 * List user commands starting with "name[name_len]".
456 */
457 static void
458uc_list(char_u *name, size_t name_len)
459{
460 int i, j;
461 int found = FALSE;
462 ucmd_T *cmd;
463 int len;
464 int over;
465 long a;
466 garray_T *gap;
467
Bram Moolenaare38eab22019-12-05 21:50:01 +0100468 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000469 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200470 for (;;)
471 {
472 for (i = 0; i < gap->ga_len; ++i)
473 {
474 cmd = USER_CMD_GA(gap, i);
475 a = (long)cmd->uc_argt;
476
477 // Skip commands which don't match the requested prefix and
478 // commands filtered out.
479 if (STRNCMP(name, cmd->uc_name, name_len) != 0
480 || message_filtered(cmd->uc_name))
481 continue;
482
483 // Put out the title first time
484 if (!found)
485 msg_puts_title(_("\n Name Args Address Complete Definition"));
486 found = TRUE;
487 msg_putchar('\n');
488 if (got_int)
489 break;
490
491 // Special cases
492 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200493 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200494 {
495 msg_putchar('!');
496 --len;
497 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200498 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200499 {
500 msg_putchar('"');
501 --len;
502 }
503 if (gap != &ucmds)
504 {
505 msg_putchar('b');
506 --len;
507 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200508 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200509 {
510 msg_putchar('|');
511 --len;
512 }
513 while (len-- > 0)
514 msg_putchar(' ');
515
516 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
517 len = (int)STRLEN(cmd->uc_name) + 4;
518
519 do {
520 msg_putchar(' ');
521 ++len;
522 } while (len < 22);
523
524 // "over" is how much longer the name is than the column width for
525 // the name, we'll try to align what comes after.
526 over = len - 22;
527 len = 0;
528
529 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200530 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200531 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200532 case 0: IObuff[len++] = '0'; break;
533 case (EX_EXTRA): IObuff[len++] = '*'; break;
534 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
535 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
536 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200537 }
538
539 do {
540 IObuff[len++] = ' ';
541 } while (len < 5 - over);
542
543 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200544 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200545 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200546 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200547 {
548 // -count=N
549 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
550 len += (int)STRLEN(IObuff + len);
551 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200552 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200553 IObuff[len++] = '%';
554 else if (cmd->uc_def >= 0)
555 {
556 // -range=N
557 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
558 len += (int)STRLEN(IObuff + len);
559 }
560 else
561 IObuff[len++] = '.';
562 }
563
564 do {
565 IObuff[len++] = ' ';
566 } while (len < 8 - over);
567
568 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200569 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200570 if (addr_type_complete[j].expand != ADDR_LINES
571 && addr_type_complete[j].expand == cmd->uc_addr_type)
572 {
573 STRCPY(IObuff + len, addr_type_complete[j].shortname);
574 len += (int)STRLEN(IObuff + len);
575 break;
576 }
577
578 do {
579 IObuff[len++] = ' ';
580 } while (len < 13 - over);
581
582 // Completion
583 for (j = 0; command_complete[j].expand != 0; ++j)
584 if (command_complete[j].expand == cmd->uc_compl)
585 {
586 STRCPY(IObuff + len, command_complete[j].name);
587 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000588#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000589 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
590 && STRLEN(cmd->uc_compl_arg) < 200)
591 {
592 IObuff[len] = ',';
593 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
594 len += (int)STRLEN(IObuff + len);
595 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000596#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200597 break;
598 }
599
600 do {
601 IObuff[len++] = ' ';
602 } while (len < 25 - over);
603
604 IObuff[len] = '\0';
605 msg_outtrans(IObuff);
606
607 msg_outtrans_special(cmd->uc_rep, FALSE,
608 name_len == 0 ? Columns - 47 : 0);
609#ifdef FEAT_EVAL
610 if (p_verbose > 0)
611 last_set_msg(cmd->uc_script_ctx);
612#endif
613 out_flush();
614 ui_breakcheck();
615 if (got_int)
616 break;
617 }
618 if (gap == &ucmds || i < gap->ga_len)
619 break;
620 gap = &ucmds;
621 }
622
623 if (!found)
624 msg(_("No user-defined commands found"));
625}
626
627 char *
628uc_fun_cmd(void)
629{
630 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
631 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
632 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
633 0xb9, 0x7f, 0};
634 int i;
635
636 for (i = 0; fcmd[i]; ++i)
637 IObuff[i] = fcmd[i] - 0x40;
638 IObuff[i] = 0;
639 return (char *)IObuff;
640}
641
642/*
643 * Parse address type argument
644 */
645 static int
646parse_addr_type_arg(
647 char_u *value,
648 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200649 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200650{
651 int i, a, b;
652
Bram Moolenaarb7316892019-05-01 18:08:42 +0200653 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200654 {
655 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
656 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
657 if (a && b)
658 {
659 *addr_type_arg = addr_type_complete[i].expand;
660 break;
661 }
662 }
663
Bram Moolenaarb7316892019-05-01 18:08:42 +0200664 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200665 {
666 char_u *err = value;
667
668 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
669 ;
670 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000671 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200672 return FAIL;
673 }
674
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200675 return OK;
676}
677
678/*
679 * Parse a completion argument "value[vallen]".
680 * The detected completion goes in "*complp", argument type in "*argt".
681 * When there is an argument, for function and user defined completion, it's
682 * copied to allocated memory and stored in "*compl_arg".
683 * Returns FAIL if something is wrong.
684 */
685 int
686parse_compl_arg(
687 char_u *value,
688 int vallen,
689 int *complp,
690 long *argt,
691 char_u **compl_arg UNUSED)
692{
693 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200694# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 size_t arglen = 0;
696# endif
697 int i;
698 int valend = vallen;
699
700 // Look for any argument part - which is the part after any ','
701 for (i = 0; i < vallen; ++i)
702 {
703 if (value[i] == ',')
704 {
705 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200706# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200707 arglen = vallen - i - 1;
708# endif
709 valend = i;
710 break;
711 }
712 }
713
714 for (i = 0; command_complete[i].expand != 0; ++i)
715 {
716 if ((int)STRLEN(command_complete[i].name) == valend
717 && STRNCMP(value, command_complete[i].name, valend) == 0)
718 {
719 *complp = command_complete[i].expand;
720 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200721 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200722 else if (command_complete[i].expand == EXPAND_DIRECTORIES
723 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200724 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200725 break;
726 }
727 }
728
729 if (command_complete[i].expand == 0)
730 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000731 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200732 return FAIL;
733 }
734
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200735# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200736 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
737 && arg != NULL)
738# else
739 if (arg != NULL)
740# endif
741 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000742 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200743 return FAIL;
744 }
745
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200746# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200747 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
748 && arg == NULL)
749 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000750 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200751 return FAIL;
752 }
753
754 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200755 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200756# endif
757 return OK;
758}
759
760/*
761 * Scan attributes in the ":command" command.
762 * Return FAIL when something is wrong.
763 */
764 static int
765uc_scan_attr(
766 char_u *attr,
767 size_t len,
768 long *argt,
769 long *def,
770 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200771 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200772 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200773 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200774{
775 char_u *p;
776
777 if (len == 0)
778 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000779 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200780 return FAIL;
781 }
782
783 // First, try the simple attributes (no arguments)
784 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200785 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200786 else if (STRNICMP(attr, "buffer", len) == 0)
787 *flags |= UC_BUFFER;
788 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200789 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000790 else if (STRNICMP(attr, "keepscript", len) == 0)
791 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200792 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200793 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200794 else
795 {
796 int i;
797 char_u *val = NULL;
798 size_t vallen = 0;
799 size_t attrlen = len;
800
801 // Look for the attribute name - which is the part before any '='
802 for (i = 0; i < (int)len; ++i)
803 {
804 if (attr[i] == '=')
805 {
806 val = &attr[i + 1];
807 vallen = len - i - 1;
808 attrlen = i;
809 break;
810 }
811 }
812
813 if (STRNICMP(attr, "nargs", attrlen) == 0)
814 {
815 if (vallen == 1)
816 {
817 if (*val == '0')
818 // Do nothing - this is the default
819 ;
820 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200821 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200822 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200823 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200824 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200825 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200826 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200827 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200828 else
829 goto wrong_nargs;
830 }
831 else
832 {
833wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000834 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200835 return FAIL;
836 }
837 }
838 else if (STRNICMP(attr, "range", attrlen) == 0)
839 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200840 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200841 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200842 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200843 else if (val != NULL)
844 {
845 p = val;
846 if (*def >= 0)
847 {
848two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000849 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200850 return FAIL;
851 }
852
853 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200854 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200855
856 if (p != val + vallen || vallen == 0)
857 {
858invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000859 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200860 return FAIL;
861 }
862 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200863 // default for -range is using buffer lines
864 if (*addr_type_arg == ADDR_NONE)
865 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200866 }
867 else if (STRNICMP(attr, "count", attrlen) == 0)
868 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200869 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200870 // default for -count is using any number
871 if (*addr_type_arg == ADDR_NONE)
872 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200873
874 if (val != NULL)
875 {
876 p = val;
877 if (*def >= 0)
878 goto two_count;
879
880 *def = getdigits(&p);
881
882 if (p != val + vallen)
883 goto invalid_count;
884 }
885
886 if (*def < 0)
887 *def = 0;
888 }
889 else if (STRNICMP(attr, "complete", attrlen) == 0)
890 {
891 if (val == NULL)
892 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000893 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200894 return FAIL;
895 }
896
Bram Moolenaar52111f82019-04-29 21:30:45 +0200897 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200898 == FAIL)
899 return FAIL;
900 }
901 else if (STRNICMP(attr, "addr", attrlen) == 0)
902 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200903 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200904 if (val == NULL)
905 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000906 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200907 return FAIL;
908 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200909 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200910 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200911 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200912 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200913 }
914 else
915 {
916 char_u ch = attr[len];
917 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000918 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200919 attr[len] = ch;
920 return FAIL;
921 }
922 }
923
924 return OK;
925}
926
927/*
928 * Add a user command to the list or replace an existing one.
929 */
930 static int
931uc_add_command(
932 char_u *name,
933 size_t name_len,
934 char_u *rep,
935 long argt,
936 long def,
937 int flags,
938 int compl,
939 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200940 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200941 int force)
942{
943 ucmd_T *cmd = NULL;
944 char_u *p;
945 int i;
946 int cmp = 1;
947 char_u *rep_buf = NULL;
948 garray_T *gap;
949
Bram Moolenaar459fd782019-10-13 16:43:39 +0200950 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200951 if (rep_buf == NULL)
952 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200953 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200954 rep_buf = vim_strsave(rep);
955
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200956 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 if (rep_buf == NULL)
958 return FAIL;
959 }
960
961 // get address of growarray: global or in curbuf
962 if (flags & UC_BUFFER)
963 {
964 gap = &curbuf->b_ucmds;
965 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +0000966 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200967 }
968 else
969 gap = &ucmds;
970
971 // Search for the command in the already defined commands.
972 for (i = 0; i < gap->ga_len; ++i)
973 {
974 size_t len;
975
976 cmd = USER_CMD_GA(gap, i);
977 len = STRLEN(cmd->uc_name);
978 cmp = STRNCMP(name, cmd->uc_name, name_len);
979 if (cmp == 0)
980 {
981 if (name_len < len)
982 cmp = -1;
983 else if (name_len > len)
984 cmp = 1;
985 }
986
987 if (cmp == 0)
988 {
989 // Command can be replaced with "command!" and when sourcing the
990 // same script again, but only once.
991 if (!force
992#ifdef FEAT_EVAL
993 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
994 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
995#endif
996 )
997 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000998 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200999 name);
1000 goto fail;
1001 }
1002
1003 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001004#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001005 VIM_CLEAR(cmd->uc_compl_arg);
1006#endif
1007 break;
1008 }
1009
1010 // Stop as soon as we pass the name to add
1011 if (cmp < 0)
1012 break;
1013 }
1014
1015 // Extend the array unless we're replacing an existing command
1016 if (cmp != 0)
1017 {
1018 if (ga_grow(gap, 1) != OK)
1019 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001020 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001021 goto fail;
1022
1023 cmd = USER_CMD_GA(gap, i);
1024 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1025
1026 ++gap->ga_len;
1027
1028 cmd->uc_name = p;
1029 }
1030
1031 cmd->uc_rep = rep_buf;
1032 cmd->uc_argt = argt;
1033 cmd->uc_def = def;
1034 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001035 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001036 if (flags & UC_VIM9)
1037 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001038#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001039 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001040 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001041#endif
1042 cmd->uc_addr_type = addr_type;
1043
1044 return OK;
1045
1046fail:
1047 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001048#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001049 vim_free(compl_arg);
1050#endif
1051 return FAIL;
1052}
1053
1054/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001055 * If "p" starts with "{" then read a block of commands until "}".
1056 * Used for ":command" and ":autocmd".
1057 */
1058 char_u *
1059may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1060{
1061 char_u *retp = p;
1062
1063 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1064 && eap->getline != NULL)
1065 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001066 garray_T ga;
1067 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001068
1069 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001070 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001071 return retp;
1072
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001073 // If the argument ends in "}" it must have been concatenated already
1074 // for ISN_EXEC.
1075 if (p[STRLEN(p) - 1] != '}')
1076 // Read lines between '{' and '}'. Does not support nesting or
1077 // here-doc constructs.
1078 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001079 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001080 vim_free(line);
1081 if ((line = eap->getline(':', eap->cookie,
1082 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1083 {
1084 emsg(_(e_missing_rcurly));
1085 break;
1086 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001087 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001088 break;
1089 if (*skipwhite(line) == '}')
1090 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001091 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001092 vim_free(line);
1093 retp = *tofree = ga_concat_strings(&ga, "\n");
1094 ga_clear_strings(&ga);
1095 *flags |= UC_VIM9;
1096 }
1097 return retp;
1098}
1099
1100/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001101 * ":command ..." implementation
1102 */
1103 void
1104ex_command(exarg_T *eap)
1105{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001106 char_u *name;
1107 char_u *end;
1108 char_u *p;
1109 long argt = 0;
1110 long def = -1;
1111 int flags = 0;
1112 int compl = EXPAND_NOTHING;
1113 char_u *compl_arg = NULL;
1114 cmd_addr_T addr_type_arg = ADDR_NONE;
1115 int has_attr = (eap->arg[0] == '-');
1116 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001117
1118 p = eap->arg;
1119
1120 // Check for attributes
1121 while (*p == '-')
1122 {
1123 ++p;
1124 end = skiptowhite(p);
1125 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1126 &compl_arg, &addr_type_arg) == FAIL)
1127 return;
1128 p = skipwhite(end);
1129 }
1130
1131 // Get the name (if any) and skip to the following argument
1132 name = p;
1133 if (ASCII_ISALPHA(*p))
1134 while (ASCII_ISALNUM(*p))
1135 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001136 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001137 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001138 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001139 return;
1140 }
1141 end = p;
1142 name_len = (int)(end - name);
1143
1144 // If there is nothing after the name, and no attributes were specified,
1145 // we are listing commands
1146 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001147 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001148 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001149 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001150 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001151 else if ((name_len == 1 && *name == 'X')
1152 || (name_len <= 4
1153 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001154 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001155 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001156 {
1157 // Some plugins rely on silently ignoring the mistake, only make this
1158 // an error in Vim9 script.
1159 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001160 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001161 else
1162 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001163 (char_u *)_(e_complete_used_without_allowing_arguments),
1164 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001165 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001166 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001167 {
1168 char_u *tofree = NULL;
1169
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001170 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001171
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001172 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1173 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001174 vim_free(tofree);
1175 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001176}
1177
1178/*
1179 * ":comclear" implementation
1180 * Clear all user commands, global and for current buffer.
1181 */
1182 void
1183ex_comclear(exarg_T *eap UNUSED)
1184{
1185 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001186 if (curbuf != NULL)
1187 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001188}
1189
1190/*
1191 * Clear all user commands for "gap".
1192 */
1193 void
1194uc_clear(garray_T *gap)
1195{
1196 int i;
1197 ucmd_T *cmd;
1198
1199 for (i = 0; i < gap->ga_len; ++i)
1200 {
1201 cmd = USER_CMD_GA(gap, i);
1202 vim_free(cmd->uc_name);
1203 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001204# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001205 vim_free(cmd->uc_compl_arg);
1206# endif
1207 }
1208 ga_clear(gap);
1209}
1210
1211/*
1212 * ":delcommand" implementation
1213 */
1214 void
1215ex_delcommand(exarg_T *eap)
1216{
1217 int i = 0;
1218 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001219 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001220 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001221 char_u *arg = eap->arg;
1222 int buffer_only = FALSE;
1223
1224 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1225 {
1226 buffer_only = TRUE;
1227 arg = skipwhite(arg + 7);
1228 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001229
1230 gap = &curbuf->b_ucmds;
1231 for (;;)
1232 {
1233 for (i = 0; i < gap->ga_len; ++i)
1234 {
1235 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001236 res = STRCMP(arg, cmd->uc_name);
1237 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001238 break;
1239 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001240 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001241 break;
1242 gap = &ucmds;
1243 }
1244
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001245 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001246 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001247 semsg(_(buffer_only
1248 ? e_no_such_user_defined_command_in_current_buffer_str
1249 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001250 return;
1251 }
1252
1253 vim_free(cmd->uc_name);
1254 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001255# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001256 vim_free(cmd->uc_compl_arg);
1257# endif
1258
1259 --gap->ga_len;
1260
1261 if (i < gap->ga_len)
1262 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1263}
1264
1265/*
1266 * Split and quote args for <f-args>.
1267 */
1268 static char_u *
1269uc_split_args(char_u *arg, size_t *lenp)
1270{
1271 char_u *buf;
1272 char_u *p;
1273 char_u *q;
1274 int len;
1275
1276 // Precalculate length
1277 p = arg;
1278 len = 2; // Initial and final quotes
1279
1280 while (*p)
1281 {
1282 if (p[0] == '\\' && p[1] == '\\')
1283 {
1284 len += 2;
1285 p += 2;
1286 }
1287 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1288 {
1289 len += 1;
1290 p += 2;
1291 }
1292 else if (*p == '\\' || *p == '"')
1293 {
1294 len += 2;
1295 p += 1;
1296 }
1297 else if (VIM_ISWHITE(*p))
1298 {
1299 p = skipwhite(p);
1300 if (*p == NUL)
1301 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001302 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001303 }
1304 else
1305 {
1306 int charlen = (*mb_ptr2len)(p);
1307
1308 len += charlen;
1309 p += charlen;
1310 }
1311 }
1312
1313 buf = alloc(len + 1);
1314 if (buf == NULL)
1315 {
1316 *lenp = 0;
1317 return buf;
1318 }
1319
1320 p = arg;
1321 q = buf;
1322 *q++ = '"';
1323 while (*p)
1324 {
1325 if (p[0] == '\\' && p[1] == '\\')
1326 {
1327 *q++ = '\\';
1328 *q++ = '\\';
1329 p += 2;
1330 }
1331 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1332 {
1333 *q++ = p[1];
1334 p += 2;
1335 }
1336 else if (*p == '\\' || *p == '"')
1337 {
1338 *q++ = '\\';
1339 *q++ = *p++;
1340 }
1341 else if (VIM_ISWHITE(*p))
1342 {
1343 p = skipwhite(p);
1344 if (*p == NUL)
1345 break;
1346 *q++ = '"';
1347 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001348 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001349 *q++ = '"';
1350 }
1351 else
1352 {
1353 MB_COPY_CHAR(p, q);
1354 }
1355 }
1356 *q++ = '"';
1357 *q = 0;
1358
1359 *lenp = len;
1360 return buf;
1361}
1362
1363 static size_t
1364add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1365{
1366 size_t result;
1367
1368 result = STRLEN(mod_str);
1369 if (*multi_mods)
1370 result += 1;
1371 if (buf != NULL)
1372 {
1373 if (*multi_mods)
1374 STRCAT(buf, " ");
1375 STRCAT(buf, mod_str);
1376 }
1377
1378 *multi_mods = 1;
1379
1380 return result;
1381}
1382
1383/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001384 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001385 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001386 */
1387 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001388add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001389{
1390 size_t result = 0;
1391
1392 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001393 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001394 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1395 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001396 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001397 result += add_cmd_modifier(buf, "belowright", multi_mods);
1398 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001399 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001400 result += add_cmd_modifier(buf, "botright", multi_mods);
1401
1402 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001403 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001404 result += add_cmd_modifier(buf, "tab", multi_mods);
1405 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001406 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001407 result += add_cmd_modifier(buf, "topleft", multi_mods);
1408 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001409 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001410 result += add_cmd_modifier(buf, "vertical", multi_mods);
1411 return result;
1412}
1413
1414/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001415 * Generate text for the "cmod" command modifiers.
1416 * If "buf" is NULL just return the length.
1417 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001418 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001419produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1420{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001421 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001422 int multi_mods = 0;
1423 int i;
1424 typedef struct {
1425 int flag;
1426 char *name;
1427 } mod_entry_T;
1428 static mod_entry_T mod_entries[] = {
1429#ifdef FEAT_BROWSE_CMD
1430 {CMOD_BROWSE, "browse"},
1431#endif
1432#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1433 {CMOD_CONFIRM, "confirm"},
1434#endif
1435 {CMOD_HIDE, "hide"},
1436 {CMOD_KEEPALT, "keepalt"},
1437 {CMOD_KEEPJUMPS, "keepjumps"},
1438 {CMOD_KEEPMARKS, "keepmarks"},
1439 {CMOD_KEEPPATTERNS, "keeppatterns"},
1440 {CMOD_LOCKMARKS, "lockmarks"},
1441 {CMOD_NOSWAPFILE, "noswapfile"},
1442 {CMOD_UNSILENT, "unsilent"},
1443 {CMOD_NOAUTOCMD, "noautocmd"},
1444#ifdef HAVE_SANDBOX
1445 {CMOD_SANDBOX, "sandbox"},
1446#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001447 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001448 {0, NULL}
1449 };
1450
1451 result = quote ? 2 : 0;
1452 if (buf != NULL)
1453 {
1454 if (quote)
1455 *buf++ = '"';
1456 *buf = '\0';
1457 }
1458
1459 // the modifiers that are simple flags
1460 for (i = 0; mod_entries[i].name != NULL; ++i)
1461 if (cmod->cmod_flags & mod_entries[i].flag)
1462 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1463
1464 // :silent
1465 if (cmod->cmod_flags & CMOD_SILENT)
1466 result += add_cmd_modifier(buf,
1467 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1468 : "silent", &multi_mods);
1469 // :verbose
1470 if (p_verbose > 0)
1471 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1472 // flags from cmod->cmod_split
1473 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1474 if (quote && buf != NULL)
1475 {
1476 buf += result - 2;
1477 *buf = '"';
1478 }
1479 return result;
1480}
1481
1482/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001483 * Check for a <> code in a user command.
1484 * "code" points to the '<'. "len" the length of the <> (inclusive).
1485 * "buf" is where the result is to be added.
1486 * "split_buf" points to a buffer used for splitting, caller should free it.
1487 * "split_len" is the length of what "split_buf" contains.
1488 * Returns the length of the replacement, which has been added to "buf".
1489 * Returns -1 if there was no match, and only the "<" has been copied.
1490 */
1491 static size_t
1492uc_check_code(
1493 char_u *code,
1494 size_t len,
1495 char_u *buf,
1496 ucmd_T *cmd, // the user command we're expanding
1497 exarg_T *eap, // ex arguments
1498 char_u **split_buf,
1499 size_t *split_len)
1500{
1501 size_t result = 0;
1502 char_u *p = code + 1;
1503 size_t l = len - 2;
1504 int quote = 0;
1505 enum {
1506 ct_ARGS,
1507 ct_BANG,
1508 ct_COUNT,
1509 ct_LINE1,
1510 ct_LINE2,
1511 ct_RANGE,
1512 ct_MODS,
1513 ct_REGISTER,
1514 ct_LT,
1515 ct_NONE
1516 } type = ct_NONE;
1517
1518 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1519 {
1520 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1521 p += 2;
1522 l -= 2;
1523 }
1524
1525 ++l;
1526 if (l <= 1)
1527 type = ct_NONE;
1528 else if (STRNICMP(p, "args>", l) == 0)
1529 type = ct_ARGS;
1530 else if (STRNICMP(p, "bang>", l) == 0)
1531 type = ct_BANG;
1532 else if (STRNICMP(p, "count>", l) == 0)
1533 type = ct_COUNT;
1534 else if (STRNICMP(p, "line1>", l) == 0)
1535 type = ct_LINE1;
1536 else if (STRNICMP(p, "line2>", l) == 0)
1537 type = ct_LINE2;
1538 else if (STRNICMP(p, "range>", l) == 0)
1539 type = ct_RANGE;
1540 else if (STRNICMP(p, "lt>", l) == 0)
1541 type = ct_LT;
1542 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1543 type = ct_REGISTER;
1544 else if (STRNICMP(p, "mods>", l) == 0)
1545 type = ct_MODS;
1546
1547 switch (type)
1548 {
1549 case ct_ARGS:
1550 // Simple case first
1551 if (*eap->arg == NUL)
1552 {
1553 if (quote == 1)
1554 {
1555 result = 2;
1556 if (buf != NULL)
1557 STRCPY(buf, "''");
1558 }
1559 else
1560 result = 0;
1561 break;
1562 }
1563
1564 // When specified there is a single argument don't split it.
1565 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001566 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001567 quote = 1;
1568
1569 switch (quote)
1570 {
1571 case 0: // No quoting, no splitting
1572 result = STRLEN(eap->arg);
1573 if (buf != NULL)
1574 STRCPY(buf, eap->arg);
1575 break;
1576 case 1: // Quote, but don't split
1577 result = STRLEN(eap->arg) + 2;
1578 for (p = eap->arg; *p; ++p)
1579 {
1580 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1581 // DBCS can contain \ in a trail byte, skip the
1582 // double-byte character.
1583 ++p;
1584 else
1585 if (*p == '\\' || *p == '"')
1586 ++result;
1587 }
1588
1589 if (buf != NULL)
1590 {
1591 *buf++ = '"';
1592 for (p = eap->arg; *p; ++p)
1593 {
1594 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1595 // DBCS can contain \ in a trail byte, copy the
1596 // double-byte character to avoid escaping.
1597 *buf++ = *p++;
1598 else
1599 if (*p == '\\' || *p == '"')
1600 *buf++ = '\\';
1601 *buf++ = *p;
1602 }
1603 *buf = '"';
1604 }
1605
1606 break;
1607 case 2: // Quote and split (<f-args>)
1608 // This is hard, so only do it once, and cache the result
1609 if (*split_buf == NULL)
1610 *split_buf = uc_split_args(eap->arg, split_len);
1611
1612 result = *split_len;
1613 if (buf != NULL && result != 0)
1614 STRCPY(buf, *split_buf);
1615
1616 break;
1617 }
1618 break;
1619
1620 case ct_BANG:
1621 result = eap->forceit ? 1 : 0;
1622 if (quote)
1623 result += 2;
1624 if (buf != NULL)
1625 {
1626 if (quote)
1627 *buf++ = '"';
1628 if (eap->forceit)
1629 *buf++ = '!';
1630 if (quote)
1631 *buf = '"';
1632 }
1633 break;
1634
1635 case ct_LINE1:
1636 case ct_LINE2:
1637 case ct_RANGE:
1638 case ct_COUNT:
1639 {
1640 char num_buf[20];
1641 long num = (type == ct_LINE1) ? eap->line1 :
1642 (type == ct_LINE2) ? eap->line2 :
1643 (type == ct_RANGE) ? eap->addr_count :
1644 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1645 size_t num_len;
1646
1647 sprintf(num_buf, "%ld", num);
1648 num_len = STRLEN(num_buf);
1649 result = num_len;
1650
1651 if (quote)
1652 result += 2;
1653
1654 if (buf != NULL)
1655 {
1656 if (quote)
1657 *buf++ = '"';
1658 STRCPY(buf, num_buf);
1659 buf += num_len;
1660 if (quote)
1661 *buf = '"';
1662 }
1663
1664 break;
1665 }
1666
1667 case ct_MODS:
1668 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001669 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001670 break;
1671 }
1672
1673 case ct_REGISTER:
1674 result = eap->regname ? 1 : 0;
1675 if (quote)
1676 result += 2;
1677 if (buf != NULL)
1678 {
1679 if (quote)
1680 *buf++ = '\'';
1681 if (eap->regname)
1682 *buf++ = eap->regname;
1683 if (quote)
1684 *buf = '\'';
1685 }
1686 break;
1687
1688 case ct_LT:
1689 result = 1;
1690 if (buf != NULL)
1691 *buf = '<';
1692 break;
1693
1694 default:
1695 // Not recognized: just copy the '<' and return -1.
1696 result = (size_t)-1;
1697 if (buf != NULL)
1698 *buf = '<';
1699 break;
1700 }
1701
1702 return result;
1703}
1704
1705/*
1706 * Execute a user defined command.
1707 */
1708 void
1709do_ucmd(exarg_T *eap)
1710{
1711 char_u *buf;
1712 char_u *p;
1713 char_u *q;
1714
1715 char_u *start;
1716 char_u *end = NULL;
1717 char_u *ksp;
1718 size_t len, totlen;
1719
1720 size_t split_len = 0;
1721 char_u *split_buf = NULL;
1722 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001723 sctx_T save_current_sctx;
1724 int restore_current_sctx = FALSE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001725
1726 if (eap->cmdidx == CMD_USER)
1727 cmd = USER_CMD(eap->useridx);
1728 else
1729 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1730
1731 /*
1732 * Replace <> in the command by the arguments.
1733 * First round: "buf" is NULL, compute length, allocate "buf".
1734 * Second round: copy result into "buf".
1735 */
1736 buf = NULL;
1737 for (;;)
1738 {
1739 p = cmd->uc_rep; // source
1740 q = buf; // destination
1741 totlen = 0;
1742
1743 for (;;)
1744 {
1745 start = vim_strchr(p, '<');
1746 if (start != NULL)
1747 end = vim_strchr(start + 1, '>');
1748 if (buf != NULL)
1749 {
1750 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1751 ;
1752 if (*ksp == K_SPECIAL
1753 && (start == NULL || ksp < start || end == NULL)
1754 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1755# ifdef FEAT_GUI
1756 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1757# endif
1758 ))
1759 {
1760 // K_SPECIAL has been put in the buffer as K_SPECIAL
1761 // KS_SPECIAL KE_FILLER, like for mappings, but
1762 // do_cmdline() doesn't handle that, so convert it back.
1763 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1764 len = ksp - p;
1765 if (len > 0)
1766 {
1767 mch_memmove(q, p, len);
1768 q += len;
1769 }
1770 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1771 p = ksp + 3;
1772 continue;
1773 }
1774 }
1775
1776 // break if no <item> is found
1777 if (start == NULL || end == NULL)
1778 break;
1779
1780 // Include the '>'
1781 ++end;
1782
1783 // Take everything up to the '<'
1784 len = start - p;
1785 if (buf == NULL)
1786 totlen += len;
1787 else
1788 {
1789 mch_memmove(q, p, len);
1790 q += len;
1791 }
1792
1793 len = uc_check_code(start, end - start, q, cmd, eap,
1794 &split_buf, &split_len);
1795 if (len == (size_t)-1)
1796 {
1797 // no match, continue after '<'
1798 p = start + 1;
1799 len = 1;
1800 }
1801 else
1802 p = end;
1803 if (buf == NULL)
1804 totlen += len;
1805 else
1806 q += len;
1807 }
1808 if (buf != NULL) // second time here, finished
1809 {
1810 STRCPY(q, p);
1811 break;
1812 }
1813
1814 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001815 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001816 if (buf == NULL)
1817 {
1818 vim_free(split_buf);
1819 return;
1820 }
1821 }
1822
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001823 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1824 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001825 restore_current_sctx = TRUE;
1826 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001827 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001828#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001829 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001830#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001831 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001832
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001833 (void)do_cmdline(buf, eap->getline, eap->cookie,
1834 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001835
1836 // Careful: Do not use "cmd" here, it may have become invalid if a user
1837 // command was added.
1838 if (restore_current_sctx)
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001839 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001840 vim_free(buf);
1841 vim_free(split_buf);
1842}