blob: 015a9c19c120015004e35f3f819e77c92401cf57 [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010025# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020026 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027# endif
28} ucmd_T;
29
30// List of all user commands.
31static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
32
33#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
34#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
35
36/*
37 * List of names for completion for ":command" with the EXPAND_ flag.
38 * Must be alphabetical for completion.
39 */
40static struct
41{
42 int expand;
43 char *name;
44} command_complete[] =
45{
46 {EXPAND_ARGLIST, "arglist"},
47 {EXPAND_AUGROUP, "augroup"},
48 {EXPAND_BEHAVE, "behave"},
49 {EXPAND_BUFFERS, "buffer"},
50 {EXPAND_COLORS, "color"},
51 {EXPAND_COMMANDS, "command"},
52 {EXPAND_COMPILER, "compiler"},
53#if defined(FEAT_CSCOPE)
54 {EXPAND_CSCOPE, "cscope"},
55#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020056#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020057 {EXPAND_USER_DEFINED, "custom"},
58 {EXPAND_USER_LIST, "customlist"},
59#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010060 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_DIRECTORIES, "dir"},
62 {EXPAND_ENV_VARS, "environment"},
63 {EXPAND_EVENTS, "event"},
64 {EXPAND_EXPRESSION, "expression"},
65 {EXPAND_FILES, "file"},
66 {EXPAND_FILES_IN_PATH, "file_in_path"},
67 {EXPAND_FILETYPE, "filetype"},
68 {EXPAND_FUNCTIONS, "function"},
69 {EXPAND_HELP, "help"},
70 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020071 {EXPAND_HISTORY, "history"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020072#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
73 {EXPAND_LOCALES, "locale"},
74#endif
75 {EXPAND_MAPCLEAR, "mapclear"},
76 {EXPAND_MAPPINGS, "mapping"},
77 {EXPAND_MENUS, "menu"},
78 {EXPAND_MESSAGES, "messages"},
79 {EXPAND_OWNSYNTAX, "syntax"},
80#if defined(FEAT_PROFILE)
81 {EXPAND_SYNTIME, "syntime"},
82#endif
83 {EXPAND_SETTINGS, "option"},
84 {EXPAND_PACKADD, "packadd"},
85 {EXPAND_SHELLCMD, "shellcmd"},
86#if defined(FEAT_SIGNS)
87 {EXPAND_SIGN, "sign"},
88#endif
89 {EXPAND_TAGS, "tag"},
90 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
91 {EXPAND_USER, "user"},
92 {EXPAND_USER_VARS, "var"},
93 {0, NULL}
94};
95
96/*
97 * List of names of address types. Must be alphabetical for completion.
98 */
99static struct
100{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200101 cmd_addr_T expand;
102 char *name;
103 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200104} addr_type_complete[] =
105{
106 {ADDR_ARGUMENTS, "arguments", "arg"},
107 {ADDR_LINES, "lines", "line"},
108 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
109 {ADDR_TABS, "tabs", "tab"},
110 {ADDR_BUFFERS, "buffers", "buf"},
111 {ADDR_WINDOWS, "windows", "win"},
112 {ADDR_QUICKFIX, "quickfix", "qf"},
113 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200114 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200115};
116
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200117/*
118 * Search for a user command that matches "eap->cmd".
119 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
120 * Return a pointer to just after the command.
121 * Return NULL if there is no matching command.
122 */
123 char_u *
124find_ucmd(
125 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000126 char_u *p, // end of the command (possibly including count)
127 int *full, // set to TRUE for a full match
128 expand_T *xp, // used for completion, NULL otherwise
129 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200130{
131 int len = (int)(p - eap->cmd);
132 int j, k, matchlen = 0;
133 ucmd_T *uc;
134 int found = FALSE;
135 int possible = FALSE;
136 char_u *cp, *np; // Point into typed cmd and test name
137 garray_T *gap;
138 int amb_local = FALSE; // Found ambiguous buffer-local command,
139 // only full match global is accepted.
140
141 /*
142 * Look for buffer-local user commands first, then global ones.
143 */
mityua1198122021-11-20 19:13:39 +0000144 gap =
145#ifdef FEAT_CMDWIN
146 is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds :
147#endif
148 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200149 for (;;)
150 {
151 for (j = 0; j < gap->ga_len; ++j)
152 {
153 uc = USER_CMD_GA(gap, j);
154 cp = eap->cmd;
155 np = uc->uc_name;
156 k = 0;
157 while (k < len && *np != NUL && *cp++ == *np++)
158 k++;
159 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
160 {
161 // If finding a second match, the command is ambiguous. But
162 // not if a buffer-local command wasn't a full match and a
163 // global command is a full match.
164 if (k == len && found && *np != NUL)
165 {
166 if (gap == &ucmds)
167 return NULL;
168 amb_local = TRUE;
169 }
170
171 if (!found || (k == len && *np == NUL))
172 {
173 // If we matched up to a digit, then there could
174 // be another command including the digit that we
175 // should use instead.
176 if (k == len)
177 found = TRUE;
178 else
179 possible = TRUE;
180
181 if (gap == &ucmds)
182 eap->cmdidx = CMD_USER;
183 else
184 eap->cmdidx = CMD_USER_BUF;
185 eap->argt = (long)uc->uc_argt;
186 eap->useridx = j;
187 eap->addr_type = uc->uc_addr_type;
188
Bram Moolenaar52111f82019-04-29 21:30:45 +0200189 if (complp != NULL)
190 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200191# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200192 if (xp != NULL)
193 {
194 xp->xp_arg = uc->uc_compl_arg;
195 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100196 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200197 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200198# endif
199 // Do not search for further abbreviations
200 // if this is an exact match.
201 matchlen = k;
202 if (k == len && *np == NUL)
203 {
204 if (full != NULL)
205 *full = TRUE;
206 amb_local = FALSE;
207 break;
208 }
209 }
210 }
211 }
212
213 // Stop if we found a full match or searched all.
214 if (j < gap->ga_len || gap == &ucmds)
215 break;
216 gap = &ucmds;
217 }
218
219 // Only found ambiguous matches.
220 if (amb_local)
221 {
222 if (xp != NULL)
223 xp->xp_context = EXPAND_UNSUCCESSFUL;
224 return NULL;
225 }
226
227 // The match we found may be followed immediately by a number. Move "p"
228 // back to point to it.
229 if (found || possible)
230 return p + (matchlen - len);
231 return p;
232}
233
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200234 char_u *
235set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
236{
237 char_u *arg = arg_in;
238 char_u *p;
239
240 // Check for attributes
241 while (*arg == '-')
242 {
243 arg++; // Skip "-"
244 p = skiptowhite(arg);
245 if (*p == NUL)
246 {
247 // Cursor is still in the attribute
248 p = vim_strchr(arg, '=');
249 if (p == NULL)
250 {
251 // No "=", so complete attribute names
252 xp->xp_context = EXPAND_USER_CMD_FLAGS;
253 xp->xp_pattern = arg;
254 return NULL;
255 }
256
257 // For the -complete, -nargs and -addr attributes, we complete
258 // their arguments as well.
259 if (STRNICMP(arg, "complete", p - arg) == 0)
260 {
261 xp->xp_context = EXPAND_USER_COMPLETE;
262 xp->xp_pattern = p + 1;
263 return NULL;
264 }
265 else if (STRNICMP(arg, "nargs", p - arg) == 0)
266 {
267 xp->xp_context = EXPAND_USER_NARGS;
268 xp->xp_pattern = p + 1;
269 return NULL;
270 }
271 else if (STRNICMP(arg, "addr", p - arg) == 0)
272 {
273 xp->xp_context = EXPAND_USER_ADDR_TYPE;
274 xp->xp_pattern = p + 1;
275 return NULL;
276 }
277 return NULL;
278 }
279 arg = skipwhite(p);
280 }
281
282 // After the attributes comes the new command name
283 p = skiptowhite(arg);
284 if (*p == NUL)
285 {
286 xp->xp_context = EXPAND_USER_COMMANDS;
287 xp->xp_pattern = arg;
288 return NULL;
289 }
290
291 // And finally comes a normal command
292 return skipwhite(p);
293}
294
295 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200296expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200297{
298 return get_user_commands(NULL, idx - (int)CMD_SIZE);
299}
300
301/*
302 * Function given to ExpandGeneric() to obtain the list of user command names.
303 */
304 char_u *
305get_user_commands(expand_T *xp UNUSED, int idx)
306{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200307 // In cmdwin, the alternative buffer should be used.
308 buf_T *buf =
309#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000310 is_in_cmdwin() ? prevwin->w_buffer :
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200311#endif
312 curbuf;
313
314 if (idx < buf->b_ucmds.ga_len)
315 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
316 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200317 if (idx < ucmds.ga_len)
318 return USER_CMD(idx)->uc_name;
319 return NULL;
320}
321
Dominique Pelle748b3082022-01-08 12:41:16 +0000322#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200323/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200324 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
325 * CMD_USER_BUF.
326 * Returns NULL if the command is not found.
327 */
328 char_u *
329get_user_command_name(int idx, int cmdidx)
330{
331 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
332 return USER_CMD(idx)->uc_name;
333 if (cmdidx == CMD_USER_BUF)
334 {
335 // In cmdwin, the alternative buffer should be used.
336 buf_T *buf =
337#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000338 is_in_cmdwin() ? prevwin->w_buffer :
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200339#endif
mityua1198122021-11-20 19:13:39 +0000340 curbuf;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200341
342 if (idx < buf->b_ucmds.ga_len)
343 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
344 }
345 return NULL;
346}
Dominique Pelle748b3082022-01-08 12:41:16 +0000347#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200348
349/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200350 * Function given to ExpandGeneric() to obtain the list of user address type
351 * names.
352 */
353 char_u *
354get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
355{
356 return (char_u *)addr_type_complete[idx].name;
357}
358
359/*
360 * Function given to ExpandGeneric() to obtain the list of user command
361 * attributes.
362 */
363 char_u *
364get_user_cmd_flags(expand_T *xp UNUSED, int idx)
365{
366 static char *user_cmd_flags[] = {
367 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000368 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200369 };
370
K.Takataeeec2542021-06-02 13:28:16 +0200371 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200372 return NULL;
373 return (char_u *)user_cmd_flags[idx];
374}
375
376/*
377 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
378 */
379 char_u *
380get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
381{
382 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
383
K.Takataeeec2542021-06-02 13:28:16 +0200384 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200385 return NULL;
386 return (char_u *)user_cmd_nargs[idx];
387}
388
389/*
390 * Function given to ExpandGeneric() to obtain the list of values for
391 * -complete.
392 */
393 char_u *
394get_user_cmd_complete(expand_T *xp UNUSED, int idx)
395{
396 return (char_u *)command_complete[idx].name;
397}
398
Dominique Pelle748b3082022-01-08 12:41:16 +0000399#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200400 int
401cmdcomplete_str_to_type(char_u *complete_str)
402{
403 int i;
404
405 for (i = 0; command_complete[i].expand != 0; ++i)
406 if (STRCMP(complete_str, command_complete[i].name) == 0)
407 return command_complete[i].expand;
408
409 return EXPAND_NOTHING;
410}
Dominique Pelle748b3082022-01-08 12:41:16 +0000411#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200412
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200413/*
414 * List user commands starting with "name[name_len]".
415 */
416 static void
417uc_list(char_u *name, size_t name_len)
418{
419 int i, j;
420 int found = FALSE;
421 ucmd_T *cmd;
422 int len;
423 int over;
424 long a;
425 garray_T *gap;
426
Bram Moolenaare38eab22019-12-05 21:50:01 +0100427 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200428 gap =
429#ifdef FEAT_CMDWIN
mityua1198122021-11-20 19:13:39 +0000430 is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds :
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200431#endif
mityua1198122021-11-20 19:13:39 +0000432 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200433 for (;;)
434 {
435 for (i = 0; i < gap->ga_len; ++i)
436 {
437 cmd = USER_CMD_GA(gap, i);
438 a = (long)cmd->uc_argt;
439
440 // Skip commands which don't match the requested prefix and
441 // commands filtered out.
442 if (STRNCMP(name, cmd->uc_name, name_len) != 0
443 || message_filtered(cmd->uc_name))
444 continue;
445
446 // Put out the title first time
447 if (!found)
448 msg_puts_title(_("\n Name Args Address Complete Definition"));
449 found = TRUE;
450 msg_putchar('\n');
451 if (got_int)
452 break;
453
454 // Special cases
455 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200456 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200457 {
458 msg_putchar('!');
459 --len;
460 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200461 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200462 {
463 msg_putchar('"');
464 --len;
465 }
466 if (gap != &ucmds)
467 {
468 msg_putchar('b');
469 --len;
470 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200471 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200472 {
473 msg_putchar('|');
474 --len;
475 }
476 while (len-- > 0)
477 msg_putchar(' ');
478
479 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
480 len = (int)STRLEN(cmd->uc_name) + 4;
481
482 do {
483 msg_putchar(' ');
484 ++len;
485 } while (len < 22);
486
487 // "over" is how much longer the name is than the column width for
488 // the name, we'll try to align what comes after.
489 over = len - 22;
490 len = 0;
491
492 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200493 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200494 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200495 case 0: IObuff[len++] = '0'; break;
496 case (EX_EXTRA): IObuff[len++] = '*'; break;
497 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
498 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
499 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200500 }
501
502 do {
503 IObuff[len++] = ' ';
504 } while (len < 5 - over);
505
506 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200507 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200508 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200509 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510 {
511 // -count=N
512 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
513 len += (int)STRLEN(IObuff + len);
514 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200515 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200516 IObuff[len++] = '%';
517 else if (cmd->uc_def >= 0)
518 {
519 // -range=N
520 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
521 len += (int)STRLEN(IObuff + len);
522 }
523 else
524 IObuff[len++] = '.';
525 }
526
527 do {
528 IObuff[len++] = ' ';
529 } while (len < 8 - over);
530
531 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200532 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200533 if (addr_type_complete[j].expand != ADDR_LINES
534 && addr_type_complete[j].expand == cmd->uc_addr_type)
535 {
536 STRCPY(IObuff + len, addr_type_complete[j].shortname);
537 len += (int)STRLEN(IObuff + len);
538 break;
539 }
540
541 do {
542 IObuff[len++] = ' ';
543 } while (len < 13 - over);
544
545 // Completion
546 for (j = 0; command_complete[j].expand != 0; ++j)
547 if (command_complete[j].expand == cmd->uc_compl)
548 {
549 STRCPY(IObuff + len, command_complete[j].name);
550 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000551#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000552 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
553 && STRLEN(cmd->uc_compl_arg) < 200)
554 {
555 IObuff[len] = ',';
556 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
557 len += (int)STRLEN(IObuff + len);
558 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000559#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200560 break;
561 }
562
563 do {
564 IObuff[len++] = ' ';
565 } while (len < 25 - over);
566
567 IObuff[len] = '\0';
568 msg_outtrans(IObuff);
569
570 msg_outtrans_special(cmd->uc_rep, FALSE,
571 name_len == 0 ? Columns - 47 : 0);
572#ifdef FEAT_EVAL
573 if (p_verbose > 0)
574 last_set_msg(cmd->uc_script_ctx);
575#endif
576 out_flush();
577 ui_breakcheck();
578 if (got_int)
579 break;
580 }
581 if (gap == &ucmds || i < gap->ga_len)
582 break;
583 gap = &ucmds;
584 }
585
586 if (!found)
587 msg(_("No user-defined commands found"));
588}
589
590 char *
591uc_fun_cmd(void)
592{
593 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
594 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
595 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
596 0xb9, 0x7f, 0};
597 int i;
598
599 for (i = 0; fcmd[i]; ++i)
600 IObuff[i] = fcmd[i] - 0x40;
601 IObuff[i] = 0;
602 return (char *)IObuff;
603}
604
605/*
606 * Parse address type argument
607 */
608 static int
609parse_addr_type_arg(
610 char_u *value,
611 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200612 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200613{
614 int i, a, b;
615
Bram Moolenaarb7316892019-05-01 18:08:42 +0200616 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200617 {
618 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
619 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
620 if (a && b)
621 {
622 *addr_type_arg = addr_type_complete[i].expand;
623 break;
624 }
625 }
626
Bram Moolenaarb7316892019-05-01 18:08:42 +0200627 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200628 {
629 char_u *err = value;
630
631 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
632 ;
633 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000634 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200635 return FAIL;
636 }
637
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200638 return OK;
639}
640
641/*
642 * Parse a completion argument "value[vallen]".
643 * The detected completion goes in "*complp", argument type in "*argt".
644 * When there is an argument, for function and user defined completion, it's
645 * copied to allocated memory and stored in "*compl_arg".
646 * Returns FAIL if something is wrong.
647 */
648 int
649parse_compl_arg(
650 char_u *value,
651 int vallen,
652 int *complp,
653 long *argt,
654 char_u **compl_arg UNUSED)
655{
656 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200657# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200658 size_t arglen = 0;
659# endif
660 int i;
661 int valend = vallen;
662
663 // Look for any argument part - which is the part after any ','
664 for (i = 0; i < vallen; ++i)
665 {
666 if (value[i] == ',')
667 {
668 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200669# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200670 arglen = vallen - i - 1;
671# endif
672 valend = i;
673 break;
674 }
675 }
676
677 for (i = 0; command_complete[i].expand != 0; ++i)
678 {
679 if ((int)STRLEN(command_complete[i].name) == valend
680 && STRNCMP(value, command_complete[i].name, valend) == 0)
681 {
682 *complp = command_complete[i].expand;
683 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200684 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200685 else if (command_complete[i].expand == EXPAND_DIRECTORIES
686 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200687 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200688 break;
689 }
690 }
691
692 if (command_complete[i].expand == 0)
693 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000694 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 return FAIL;
696 }
697
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200698# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200699 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
700 && arg != NULL)
701# else
702 if (arg != NULL)
703# endif
704 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000705 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200706 return FAIL;
707 }
708
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200709# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200710 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
711 && arg == NULL)
712 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000713 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200714 return FAIL;
715 }
716
717 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200718 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200719# endif
720 return OK;
721}
722
723/*
724 * Scan attributes in the ":command" command.
725 * Return FAIL when something is wrong.
726 */
727 static int
728uc_scan_attr(
729 char_u *attr,
730 size_t len,
731 long *argt,
732 long *def,
733 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200734 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200735 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200736 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200737{
738 char_u *p;
739
740 if (len == 0)
741 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000742 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200743 return FAIL;
744 }
745
746 // First, try the simple attributes (no arguments)
747 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200748 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200749 else if (STRNICMP(attr, "buffer", len) == 0)
750 *flags |= UC_BUFFER;
751 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200752 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000753 else if (STRNICMP(attr, "keepscript", len) == 0)
754 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200755 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200756 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200757 else
758 {
759 int i;
760 char_u *val = NULL;
761 size_t vallen = 0;
762 size_t attrlen = len;
763
764 // Look for the attribute name - which is the part before any '='
765 for (i = 0; i < (int)len; ++i)
766 {
767 if (attr[i] == '=')
768 {
769 val = &attr[i + 1];
770 vallen = len - i - 1;
771 attrlen = i;
772 break;
773 }
774 }
775
776 if (STRNICMP(attr, "nargs", attrlen) == 0)
777 {
778 if (vallen == 1)
779 {
780 if (*val == '0')
781 // Do nothing - this is the default
782 ;
783 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200784 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200785 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200786 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200787 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200788 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200790 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200791 else
792 goto wrong_nargs;
793 }
794 else
795 {
796wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000797 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200798 return FAIL;
799 }
800 }
801 else if (STRNICMP(attr, "range", attrlen) == 0)
802 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200803 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200804 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200805 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200806 else if (val != NULL)
807 {
808 p = val;
809 if (*def >= 0)
810 {
811two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000812 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200813 return FAIL;
814 }
815
816 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200817 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200818
819 if (p != val + vallen || vallen == 0)
820 {
821invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000822 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200823 return FAIL;
824 }
825 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200826 // default for -range is using buffer lines
827 if (*addr_type_arg == ADDR_NONE)
828 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200829 }
830 else if (STRNICMP(attr, "count", attrlen) == 0)
831 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200832 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200833 // default for -count is using any number
834 if (*addr_type_arg == ADDR_NONE)
835 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200836
837 if (val != NULL)
838 {
839 p = val;
840 if (*def >= 0)
841 goto two_count;
842
843 *def = getdigits(&p);
844
845 if (p != val + vallen)
846 goto invalid_count;
847 }
848
849 if (*def < 0)
850 *def = 0;
851 }
852 else if (STRNICMP(attr, "complete", attrlen) == 0)
853 {
854 if (val == NULL)
855 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000856 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200857 return FAIL;
858 }
859
Bram Moolenaar52111f82019-04-29 21:30:45 +0200860 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200861 == FAIL)
862 return FAIL;
863 }
864 else if (STRNICMP(attr, "addr", attrlen) == 0)
865 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200866 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200867 if (val == NULL)
868 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000869 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200870 return FAIL;
871 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200872 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200873 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200874 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200875 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200876 }
877 else
878 {
879 char_u ch = attr[len];
880 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000881 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200882 attr[len] = ch;
883 return FAIL;
884 }
885 }
886
887 return OK;
888}
889
890/*
891 * Add a user command to the list or replace an existing one.
892 */
893 static int
894uc_add_command(
895 char_u *name,
896 size_t name_len,
897 char_u *rep,
898 long argt,
899 long def,
900 int flags,
901 int compl,
902 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200903 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200904 int force)
905{
906 ucmd_T *cmd = NULL;
907 char_u *p;
908 int i;
909 int cmp = 1;
910 char_u *rep_buf = NULL;
911 garray_T *gap;
912
Bram Moolenaar459fd782019-10-13 16:43:39 +0200913 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 if (rep_buf == NULL)
915 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200916 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200917 rep_buf = vim_strsave(rep);
918
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200919 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200920 if (rep_buf == NULL)
921 return FAIL;
922 }
923
924 // get address of growarray: global or in curbuf
925 if (flags & UC_BUFFER)
926 {
927 gap = &curbuf->b_ucmds;
928 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +0000929 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200930 }
931 else
932 gap = &ucmds;
933
934 // Search for the command in the already defined commands.
935 for (i = 0; i < gap->ga_len; ++i)
936 {
937 size_t len;
938
939 cmd = USER_CMD_GA(gap, i);
940 len = STRLEN(cmd->uc_name);
941 cmp = STRNCMP(name, cmd->uc_name, name_len);
942 if (cmp == 0)
943 {
944 if (name_len < len)
945 cmp = -1;
946 else if (name_len > len)
947 cmp = 1;
948 }
949
950 if (cmp == 0)
951 {
952 // Command can be replaced with "command!" and when sourcing the
953 // same script again, but only once.
954 if (!force
955#ifdef FEAT_EVAL
956 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
957 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
958#endif
959 )
960 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000961 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200962 name);
963 goto fail;
964 }
965
966 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200967#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200968 VIM_CLEAR(cmd->uc_compl_arg);
969#endif
970 break;
971 }
972
973 // Stop as soon as we pass the name to add
974 if (cmp < 0)
975 break;
976 }
977
978 // Extend the array unless we're replacing an existing command
979 if (cmp != 0)
980 {
981 if (ga_grow(gap, 1) != OK)
982 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200983 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200984 goto fail;
985
986 cmd = USER_CMD_GA(gap, i);
987 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
988
989 ++gap->ga_len;
990
991 cmd->uc_name = p;
992 }
993
994 cmd->uc_rep = rep_buf;
995 cmd->uc_argt = argt;
996 cmd->uc_def = def;
997 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200998 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +0200999 if (flags & UC_VIM9)
1000 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001001#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001002 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001003 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001004#endif
1005 cmd->uc_addr_type = addr_type;
1006
1007 return OK;
1008
1009fail:
1010 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001011#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001012 vim_free(compl_arg);
1013#endif
1014 return FAIL;
1015}
1016
1017/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001018 * If "p" starts with "{" then read a block of commands until "}".
1019 * Used for ":command" and ":autocmd".
1020 */
1021 char_u *
1022may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1023{
1024 char_u *retp = p;
1025
1026 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
1027 && eap->getline != NULL)
1028 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001029 garray_T ga;
1030 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001031
1032 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001033 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001034 return retp;
1035
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001036 // If the argument ends in "}" it must have been concatenated already
1037 // for ISN_EXEC.
1038 if (p[STRLEN(p) - 1] != '}')
1039 // Read lines between '{' and '}'. Does not support nesting or
1040 // here-doc constructs.
1041 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001042 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001043 vim_free(line);
1044 if ((line = eap->getline(':', eap->cookie,
1045 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1046 {
1047 emsg(_(e_missing_rcurly));
1048 break;
1049 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001050 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001051 break;
1052 if (*skipwhite(line) == '}')
1053 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001054 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001055 vim_free(line);
1056 retp = *tofree = ga_concat_strings(&ga, "\n");
1057 ga_clear_strings(&ga);
1058 *flags |= UC_VIM9;
1059 }
1060 return retp;
1061}
1062
1063/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001064 * ":command ..." implementation
1065 */
1066 void
1067ex_command(exarg_T *eap)
1068{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001069 char_u *name;
1070 char_u *end;
1071 char_u *p;
1072 long argt = 0;
1073 long def = -1;
1074 int flags = 0;
1075 int compl = EXPAND_NOTHING;
1076 char_u *compl_arg = NULL;
1077 cmd_addr_T addr_type_arg = ADDR_NONE;
1078 int has_attr = (eap->arg[0] == '-');
1079 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001080
1081 p = eap->arg;
1082
1083 // Check for attributes
1084 while (*p == '-')
1085 {
1086 ++p;
1087 end = skiptowhite(p);
1088 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1089 &compl_arg, &addr_type_arg) == FAIL)
1090 return;
1091 p = skipwhite(end);
1092 }
1093
1094 // Get the name (if any) and skip to the following argument
1095 name = p;
1096 if (ASCII_ISALPHA(*p))
1097 while (ASCII_ISALNUM(*p))
1098 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001099 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001100 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001101 emsg(_(e_invalid_command_name));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001102 return;
1103 }
1104 end = p;
1105 name_len = (int)(end - name);
1106
1107 // If there is nothing after the name, and no attributes were specified,
1108 // we are listing commands
1109 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001110 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001111 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001112 else if (!ASCII_ISUPPER(*name))
Bram Moolenaar1a992222021-12-31 17:25:48 +00001113 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001114 else if ((name_len == 1 && *name == 'X')
1115 || (name_len <= 4
1116 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001117 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
Martin Tournoijde69a732021-07-11 14:28:25 +02001118 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001119 {
1120 // Some plugins rely on silently ignoring the mistake, only make this
1121 // an error in Vim9 script.
1122 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001123 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001124 else
1125 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001126 (char_u *)_(e_complete_used_without_allowing_arguments),
1127 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001128 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001129 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001130 {
1131 char_u *tofree = NULL;
1132
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001133 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001134
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001135 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1136 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001137 vim_free(tofree);
1138 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001139}
1140
1141/*
1142 * ":comclear" implementation
1143 * Clear all user commands, global and for current buffer.
1144 */
1145 void
1146ex_comclear(exarg_T *eap UNUSED)
1147{
1148 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001149 if (curbuf != NULL)
1150 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001151}
1152
1153/*
1154 * Clear all user commands for "gap".
1155 */
1156 void
1157uc_clear(garray_T *gap)
1158{
1159 int i;
1160 ucmd_T *cmd;
1161
1162 for (i = 0; i < gap->ga_len; ++i)
1163 {
1164 cmd = USER_CMD_GA(gap, i);
1165 vim_free(cmd->uc_name);
1166 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001167# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001168 vim_free(cmd->uc_compl_arg);
1169# endif
1170 }
1171 ga_clear(gap);
1172}
1173
1174/*
1175 * ":delcommand" implementation
1176 */
1177 void
1178ex_delcommand(exarg_T *eap)
1179{
1180 int i = 0;
1181 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001182 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001183 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001184 char_u *arg = eap->arg;
1185 int buffer_only = FALSE;
1186
1187 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1188 {
1189 buffer_only = TRUE;
1190 arg = skipwhite(arg + 7);
1191 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001192
1193 gap = &curbuf->b_ucmds;
1194 for (;;)
1195 {
1196 for (i = 0; i < gap->ga_len; ++i)
1197 {
1198 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001199 res = STRCMP(arg, cmd->uc_name);
1200 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001201 break;
1202 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001203 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001204 break;
1205 gap = &ucmds;
1206 }
1207
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001208 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001209 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001210 semsg(_(buffer_only
1211 ? e_no_such_user_defined_command_in_current_buffer_str
1212 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001213 return;
1214 }
1215
1216 vim_free(cmd->uc_name);
1217 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001218# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001219 vim_free(cmd->uc_compl_arg);
1220# endif
1221
1222 --gap->ga_len;
1223
1224 if (i < gap->ga_len)
1225 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1226}
1227
1228/*
1229 * Split and quote args for <f-args>.
1230 */
1231 static char_u *
1232uc_split_args(char_u *arg, size_t *lenp)
1233{
1234 char_u *buf;
1235 char_u *p;
1236 char_u *q;
1237 int len;
1238
1239 // Precalculate length
1240 p = arg;
1241 len = 2; // Initial and final quotes
1242
1243 while (*p)
1244 {
1245 if (p[0] == '\\' && p[1] == '\\')
1246 {
1247 len += 2;
1248 p += 2;
1249 }
1250 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1251 {
1252 len += 1;
1253 p += 2;
1254 }
1255 else if (*p == '\\' || *p == '"')
1256 {
1257 len += 2;
1258 p += 1;
1259 }
1260 else if (VIM_ISWHITE(*p))
1261 {
1262 p = skipwhite(p);
1263 if (*p == NUL)
1264 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001265 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001266 }
1267 else
1268 {
1269 int charlen = (*mb_ptr2len)(p);
1270
1271 len += charlen;
1272 p += charlen;
1273 }
1274 }
1275
1276 buf = alloc(len + 1);
1277 if (buf == NULL)
1278 {
1279 *lenp = 0;
1280 return buf;
1281 }
1282
1283 p = arg;
1284 q = buf;
1285 *q++ = '"';
1286 while (*p)
1287 {
1288 if (p[0] == '\\' && p[1] == '\\')
1289 {
1290 *q++ = '\\';
1291 *q++ = '\\';
1292 p += 2;
1293 }
1294 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1295 {
1296 *q++ = p[1];
1297 p += 2;
1298 }
1299 else if (*p == '\\' || *p == '"')
1300 {
1301 *q++ = '\\';
1302 *q++ = *p++;
1303 }
1304 else if (VIM_ISWHITE(*p))
1305 {
1306 p = skipwhite(p);
1307 if (*p == NUL)
1308 break;
1309 *q++ = '"';
1310 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001311 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001312 *q++ = '"';
1313 }
1314 else
1315 {
1316 MB_COPY_CHAR(p, q);
1317 }
1318 }
1319 *q++ = '"';
1320 *q = 0;
1321
1322 *lenp = len;
1323 return buf;
1324}
1325
1326 static size_t
1327add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1328{
1329 size_t result;
1330
1331 result = STRLEN(mod_str);
1332 if (*multi_mods)
1333 result += 1;
1334 if (buf != NULL)
1335 {
1336 if (*multi_mods)
1337 STRCAT(buf, " ");
1338 STRCAT(buf, mod_str);
1339 }
1340
1341 *multi_mods = 1;
1342
1343 return result;
1344}
1345
1346/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001347 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001348 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001349 */
1350 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001351add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001352{
1353 size_t result = 0;
1354
1355 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001356 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001357 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1358 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001359 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001360 result += add_cmd_modifier(buf, "belowright", multi_mods);
1361 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001362 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001363 result += add_cmd_modifier(buf, "botright", multi_mods);
1364
1365 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001366 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001367 result += add_cmd_modifier(buf, "tab", multi_mods);
1368 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001369 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001370 result += add_cmd_modifier(buf, "topleft", multi_mods);
1371 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001372 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001373 result += add_cmd_modifier(buf, "vertical", multi_mods);
1374 return result;
1375}
1376
1377/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001378 * Generate text for the "cmod" command modifiers.
1379 * If "buf" is NULL just return the length.
1380 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001381 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001382produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1383{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001384 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001385 int multi_mods = 0;
1386 int i;
1387 typedef struct {
1388 int flag;
1389 char *name;
1390 } mod_entry_T;
1391 static mod_entry_T mod_entries[] = {
1392#ifdef FEAT_BROWSE_CMD
1393 {CMOD_BROWSE, "browse"},
1394#endif
1395#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1396 {CMOD_CONFIRM, "confirm"},
1397#endif
1398 {CMOD_HIDE, "hide"},
1399 {CMOD_KEEPALT, "keepalt"},
1400 {CMOD_KEEPJUMPS, "keepjumps"},
1401 {CMOD_KEEPMARKS, "keepmarks"},
1402 {CMOD_KEEPPATTERNS, "keeppatterns"},
1403 {CMOD_LOCKMARKS, "lockmarks"},
1404 {CMOD_NOSWAPFILE, "noswapfile"},
1405 {CMOD_UNSILENT, "unsilent"},
1406 {CMOD_NOAUTOCMD, "noautocmd"},
1407#ifdef HAVE_SANDBOX
1408 {CMOD_SANDBOX, "sandbox"},
1409#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001410 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001411 {0, NULL}
1412 };
1413
1414 result = quote ? 2 : 0;
1415 if (buf != NULL)
1416 {
1417 if (quote)
1418 *buf++ = '"';
1419 *buf = '\0';
1420 }
1421
1422 // the modifiers that are simple flags
1423 for (i = 0; mod_entries[i].name != NULL; ++i)
1424 if (cmod->cmod_flags & mod_entries[i].flag)
1425 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1426
1427 // :silent
1428 if (cmod->cmod_flags & CMOD_SILENT)
1429 result += add_cmd_modifier(buf,
1430 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1431 : "silent", &multi_mods);
1432 // :verbose
1433 if (p_verbose > 0)
1434 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1435 // flags from cmod->cmod_split
1436 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1437 if (quote && buf != NULL)
1438 {
1439 buf += result - 2;
1440 *buf = '"';
1441 }
1442 return result;
1443}
1444
1445/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001446 * Check for a <> code in a user command.
1447 * "code" points to the '<'. "len" the length of the <> (inclusive).
1448 * "buf" is where the result is to be added.
1449 * "split_buf" points to a buffer used for splitting, caller should free it.
1450 * "split_len" is the length of what "split_buf" contains.
1451 * Returns the length of the replacement, which has been added to "buf".
1452 * Returns -1 if there was no match, and only the "<" has been copied.
1453 */
1454 static size_t
1455uc_check_code(
1456 char_u *code,
1457 size_t len,
1458 char_u *buf,
1459 ucmd_T *cmd, // the user command we're expanding
1460 exarg_T *eap, // ex arguments
1461 char_u **split_buf,
1462 size_t *split_len)
1463{
1464 size_t result = 0;
1465 char_u *p = code + 1;
1466 size_t l = len - 2;
1467 int quote = 0;
1468 enum {
1469 ct_ARGS,
1470 ct_BANG,
1471 ct_COUNT,
1472 ct_LINE1,
1473 ct_LINE2,
1474 ct_RANGE,
1475 ct_MODS,
1476 ct_REGISTER,
1477 ct_LT,
1478 ct_NONE
1479 } type = ct_NONE;
1480
1481 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1482 {
1483 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1484 p += 2;
1485 l -= 2;
1486 }
1487
1488 ++l;
1489 if (l <= 1)
1490 type = ct_NONE;
1491 else if (STRNICMP(p, "args>", l) == 0)
1492 type = ct_ARGS;
1493 else if (STRNICMP(p, "bang>", l) == 0)
1494 type = ct_BANG;
1495 else if (STRNICMP(p, "count>", l) == 0)
1496 type = ct_COUNT;
1497 else if (STRNICMP(p, "line1>", l) == 0)
1498 type = ct_LINE1;
1499 else if (STRNICMP(p, "line2>", l) == 0)
1500 type = ct_LINE2;
1501 else if (STRNICMP(p, "range>", l) == 0)
1502 type = ct_RANGE;
1503 else if (STRNICMP(p, "lt>", l) == 0)
1504 type = ct_LT;
1505 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1506 type = ct_REGISTER;
1507 else if (STRNICMP(p, "mods>", l) == 0)
1508 type = ct_MODS;
1509
1510 switch (type)
1511 {
1512 case ct_ARGS:
1513 // Simple case first
1514 if (*eap->arg == NUL)
1515 {
1516 if (quote == 1)
1517 {
1518 result = 2;
1519 if (buf != NULL)
1520 STRCPY(buf, "''");
1521 }
1522 else
1523 result = 0;
1524 break;
1525 }
1526
1527 // When specified there is a single argument don't split it.
1528 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001529 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001530 quote = 1;
1531
1532 switch (quote)
1533 {
1534 case 0: // No quoting, no splitting
1535 result = STRLEN(eap->arg);
1536 if (buf != NULL)
1537 STRCPY(buf, eap->arg);
1538 break;
1539 case 1: // Quote, but don't split
1540 result = STRLEN(eap->arg) + 2;
1541 for (p = eap->arg; *p; ++p)
1542 {
1543 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1544 // DBCS can contain \ in a trail byte, skip the
1545 // double-byte character.
1546 ++p;
1547 else
1548 if (*p == '\\' || *p == '"')
1549 ++result;
1550 }
1551
1552 if (buf != NULL)
1553 {
1554 *buf++ = '"';
1555 for (p = eap->arg; *p; ++p)
1556 {
1557 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1558 // DBCS can contain \ in a trail byte, copy the
1559 // double-byte character to avoid escaping.
1560 *buf++ = *p++;
1561 else
1562 if (*p == '\\' || *p == '"')
1563 *buf++ = '\\';
1564 *buf++ = *p;
1565 }
1566 *buf = '"';
1567 }
1568
1569 break;
1570 case 2: // Quote and split (<f-args>)
1571 // This is hard, so only do it once, and cache the result
1572 if (*split_buf == NULL)
1573 *split_buf = uc_split_args(eap->arg, split_len);
1574
1575 result = *split_len;
1576 if (buf != NULL && result != 0)
1577 STRCPY(buf, *split_buf);
1578
1579 break;
1580 }
1581 break;
1582
1583 case ct_BANG:
1584 result = eap->forceit ? 1 : 0;
1585 if (quote)
1586 result += 2;
1587 if (buf != NULL)
1588 {
1589 if (quote)
1590 *buf++ = '"';
1591 if (eap->forceit)
1592 *buf++ = '!';
1593 if (quote)
1594 *buf = '"';
1595 }
1596 break;
1597
1598 case ct_LINE1:
1599 case ct_LINE2:
1600 case ct_RANGE:
1601 case ct_COUNT:
1602 {
1603 char num_buf[20];
1604 long num = (type == ct_LINE1) ? eap->line1 :
1605 (type == ct_LINE2) ? eap->line2 :
1606 (type == ct_RANGE) ? eap->addr_count :
1607 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1608 size_t num_len;
1609
1610 sprintf(num_buf, "%ld", num);
1611 num_len = STRLEN(num_buf);
1612 result = num_len;
1613
1614 if (quote)
1615 result += 2;
1616
1617 if (buf != NULL)
1618 {
1619 if (quote)
1620 *buf++ = '"';
1621 STRCPY(buf, num_buf);
1622 buf += num_len;
1623 if (quote)
1624 *buf = '"';
1625 }
1626
1627 break;
1628 }
1629
1630 case ct_MODS:
1631 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001632 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001633 break;
1634 }
1635
1636 case ct_REGISTER:
1637 result = eap->regname ? 1 : 0;
1638 if (quote)
1639 result += 2;
1640 if (buf != NULL)
1641 {
1642 if (quote)
1643 *buf++ = '\'';
1644 if (eap->regname)
1645 *buf++ = eap->regname;
1646 if (quote)
1647 *buf = '\'';
1648 }
1649 break;
1650
1651 case ct_LT:
1652 result = 1;
1653 if (buf != NULL)
1654 *buf = '<';
1655 break;
1656
1657 default:
1658 // Not recognized: just copy the '<' and return -1.
1659 result = (size_t)-1;
1660 if (buf != NULL)
1661 *buf = '<';
1662 break;
1663 }
1664
1665 return result;
1666}
1667
1668/*
1669 * Execute a user defined command.
1670 */
1671 void
1672do_ucmd(exarg_T *eap)
1673{
1674 char_u *buf;
1675 char_u *p;
1676 char_u *q;
1677
1678 char_u *start;
1679 char_u *end = NULL;
1680 char_u *ksp;
1681 size_t len, totlen;
1682
1683 size_t split_len = 0;
1684 char_u *split_buf = NULL;
1685 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001686 sctx_T save_current_sctx;
1687 int restore_current_sctx = FALSE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001688
1689 if (eap->cmdidx == CMD_USER)
1690 cmd = USER_CMD(eap->useridx);
1691 else
1692 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1693
1694 /*
1695 * Replace <> in the command by the arguments.
1696 * First round: "buf" is NULL, compute length, allocate "buf".
1697 * Second round: copy result into "buf".
1698 */
1699 buf = NULL;
1700 for (;;)
1701 {
1702 p = cmd->uc_rep; // source
1703 q = buf; // destination
1704 totlen = 0;
1705
1706 for (;;)
1707 {
1708 start = vim_strchr(p, '<');
1709 if (start != NULL)
1710 end = vim_strchr(start + 1, '>');
1711 if (buf != NULL)
1712 {
1713 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1714 ;
1715 if (*ksp == K_SPECIAL
1716 && (start == NULL || ksp < start || end == NULL)
1717 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1718# ifdef FEAT_GUI
1719 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1720# endif
1721 ))
1722 {
1723 // K_SPECIAL has been put in the buffer as K_SPECIAL
1724 // KS_SPECIAL KE_FILLER, like for mappings, but
1725 // do_cmdline() doesn't handle that, so convert it back.
1726 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1727 len = ksp - p;
1728 if (len > 0)
1729 {
1730 mch_memmove(q, p, len);
1731 q += len;
1732 }
1733 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1734 p = ksp + 3;
1735 continue;
1736 }
1737 }
1738
1739 // break if no <item> is found
1740 if (start == NULL || end == NULL)
1741 break;
1742
1743 // Include the '>'
1744 ++end;
1745
1746 // Take everything up to the '<'
1747 len = start - p;
1748 if (buf == NULL)
1749 totlen += len;
1750 else
1751 {
1752 mch_memmove(q, p, len);
1753 q += len;
1754 }
1755
1756 len = uc_check_code(start, end - start, q, cmd, eap,
1757 &split_buf, &split_len);
1758 if (len == (size_t)-1)
1759 {
1760 // no match, continue after '<'
1761 p = start + 1;
1762 len = 1;
1763 }
1764 else
1765 p = end;
1766 if (buf == NULL)
1767 totlen += len;
1768 else
1769 q += len;
1770 }
1771 if (buf != NULL) // second time here, finished
1772 {
1773 STRCPY(q, p);
1774 break;
1775 }
1776
1777 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001778 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001779 if (buf == NULL)
1780 {
1781 vim_free(split_buf);
1782 return;
1783 }
1784 }
1785
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001786 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1787 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001788 restore_current_sctx = TRUE;
1789 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001790 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001791#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001792 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001793#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001794 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001795
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001796 (void)do_cmdline(buf, eap->getline, eap->cookie,
1797 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001798
1799 // Careful: Do not use "cmd" here, it may have become invalid if a user
1800 // command was added.
1801 if (restore_current_sctx)
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001802 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001803 vim_free(buf);
1804 vim_free(split_buf);
1805}