blob: 42b9014f38e2605757e5add0f51323934f469058 [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
117#define UC_BUFFER 1 // -buffer: local to current buffer
118
119/*
120 * Search for a user command that matches "eap->cmd".
121 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
122 * Return a pointer to just after the command.
123 * Return NULL if there is no matching command.
124 */
125 char_u *
126find_ucmd(
127 exarg_T *eap,
128 char_u *p, // end of the command (possibly including count)
129 int *full, // set to TRUE for a full match
130 expand_T *xp, // used for completion, NULL otherwise
Bram Moolenaar52111f82019-04-29 21:30:45 +0200131 int *complp UNUSED) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200132{
133 int len = (int)(p - eap->cmd);
134 int j, k, matchlen = 0;
135 ucmd_T *uc;
136 int found = FALSE;
137 int possible = FALSE;
138 char_u *cp, *np; // Point into typed cmd and test name
139 garray_T *gap;
140 int amb_local = FALSE; // Found ambiguous buffer-local command,
141 // only full match global is accepted.
142
143 /*
144 * Look for buffer-local user commands first, then global ones.
145 */
146 gap = &curbuf->b_ucmds;
147 for (;;)
148 {
149 for (j = 0; j < gap->ga_len; ++j)
150 {
151 uc = USER_CMD_GA(gap, j);
152 cp = eap->cmd;
153 np = uc->uc_name;
154 k = 0;
155 while (k < len && *np != NUL && *cp++ == *np++)
156 k++;
157 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
158 {
159 // If finding a second match, the command is ambiguous. But
160 // not if a buffer-local command wasn't a full match and a
161 // global command is a full match.
162 if (k == len && found && *np != NUL)
163 {
164 if (gap == &ucmds)
165 return NULL;
166 amb_local = TRUE;
167 }
168
169 if (!found || (k == len && *np == NUL))
170 {
171 // If we matched up to a digit, then there could
172 // be another command including the digit that we
173 // should use instead.
174 if (k == len)
175 found = TRUE;
176 else
177 possible = TRUE;
178
179 if (gap == &ucmds)
180 eap->cmdidx = CMD_USER;
181 else
182 eap->cmdidx = CMD_USER_BUF;
183 eap->argt = (long)uc->uc_argt;
184 eap->useridx = j;
185 eap->addr_type = uc->uc_addr_type;
186
Bram Moolenaar52111f82019-04-29 21:30:45 +0200187 if (complp != NULL)
188 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200189# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200190 if (xp != NULL)
191 {
192 xp->xp_arg = uc->uc_compl_arg;
193 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100194 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200195 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200196# endif
197 // Do not search for further abbreviations
198 // if this is an exact match.
199 matchlen = k;
200 if (k == len && *np == NUL)
201 {
202 if (full != NULL)
203 *full = TRUE;
204 amb_local = FALSE;
205 break;
206 }
207 }
208 }
209 }
210
211 // Stop if we found a full match or searched all.
212 if (j < gap->ga_len || gap == &ucmds)
213 break;
214 gap = &ucmds;
215 }
216
217 // Only found ambiguous matches.
218 if (amb_local)
219 {
220 if (xp != NULL)
221 xp->xp_context = EXPAND_UNSUCCESSFUL;
222 return NULL;
223 }
224
225 // The match we found may be followed immediately by a number. Move "p"
226 // back to point to it.
227 if (found || possible)
228 return p + (matchlen - len);
229 return p;
230}
231
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200232 char_u *
233set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
234{
235 char_u *arg = arg_in;
236 char_u *p;
237
238 // Check for attributes
239 while (*arg == '-')
240 {
241 arg++; // Skip "-"
242 p = skiptowhite(arg);
243 if (*p == NUL)
244 {
245 // Cursor is still in the attribute
246 p = vim_strchr(arg, '=');
247 if (p == NULL)
248 {
249 // No "=", so complete attribute names
250 xp->xp_context = EXPAND_USER_CMD_FLAGS;
251 xp->xp_pattern = arg;
252 return NULL;
253 }
254
255 // For the -complete, -nargs and -addr attributes, we complete
256 // their arguments as well.
257 if (STRNICMP(arg, "complete", p - arg) == 0)
258 {
259 xp->xp_context = EXPAND_USER_COMPLETE;
260 xp->xp_pattern = p + 1;
261 return NULL;
262 }
263 else if (STRNICMP(arg, "nargs", p - arg) == 0)
264 {
265 xp->xp_context = EXPAND_USER_NARGS;
266 xp->xp_pattern = p + 1;
267 return NULL;
268 }
269 else if (STRNICMP(arg, "addr", p - arg) == 0)
270 {
271 xp->xp_context = EXPAND_USER_ADDR_TYPE;
272 xp->xp_pattern = p + 1;
273 return NULL;
274 }
275 return NULL;
276 }
277 arg = skipwhite(p);
278 }
279
280 // After the attributes comes the new command name
281 p = skiptowhite(arg);
282 if (*p == NUL)
283 {
284 xp->xp_context = EXPAND_USER_COMMANDS;
285 xp->xp_pattern = arg;
286 return NULL;
287 }
288
289 // And finally comes a normal command
290 return skipwhite(p);
291}
292
293 char_u *
294get_user_command_name(int idx)
295{
296 return get_user_commands(NULL, idx - (int)CMD_SIZE);
297}
298
299/*
300 * Function given to ExpandGeneric() to obtain the list of user command names.
301 */
302 char_u *
303get_user_commands(expand_T *xp UNUSED, int idx)
304{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200305 // In cmdwin, the alternative buffer should be used.
306 buf_T *buf =
307#ifdef FEAT_CMDWIN
308 (cmdwin_type != 0 && get_cmdline_type() == NUL) ? prevwin->w_buffer :
309#endif
310 curbuf;
311
312 if (idx < buf->b_ucmds.ga_len)
313 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
314 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200315 if (idx < ucmds.ga_len)
316 return USER_CMD(idx)->uc_name;
317 return NULL;
318}
319
320/*
321 * Function given to ExpandGeneric() to obtain the list of user address type
322 * names.
323 */
324 char_u *
325get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
326{
327 return (char_u *)addr_type_complete[idx].name;
328}
329
330/*
331 * Function given to ExpandGeneric() to obtain the list of user command
332 * attributes.
333 */
334 char_u *
335get_user_cmd_flags(expand_T *xp UNUSED, int idx)
336{
337 static char *user_cmd_flags[] = {
338 "addr", "bang", "bar", "buffer", "complete",
339 "count", "nargs", "range", "register"
340 };
341
K.Takataeeec2542021-06-02 13:28:16 +0200342 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200343 return NULL;
344 return (char_u *)user_cmd_flags[idx];
345}
346
347/*
348 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
349 */
350 char_u *
351get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
352{
353 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
354
K.Takataeeec2542021-06-02 13:28:16 +0200355 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200356 return NULL;
357 return (char_u *)user_cmd_nargs[idx];
358}
359
360/*
361 * Function given to ExpandGeneric() to obtain the list of values for
362 * -complete.
363 */
364 char_u *
365get_user_cmd_complete(expand_T *xp UNUSED, int idx)
366{
367 return (char_u *)command_complete[idx].name;
368}
369
370 int
371cmdcomplete_str_to_type(char_u *complete_str)
372{
373 int i;
374
375 for (i = 0; command_complete[i].expand != 0; ++i)
376 if (STRCMP(complete_str, command_complete[i].name) == 0)
377 return command_complete[i].expand;
378
379 return EXPAND_NOTHING;
380}
381
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200382/*
383 * List user commands starting with "name[name_len]".
384 */
385 static void
386uc_list(char_u *name, size_t name_len)
387{
388 int i, j;
389 int found = FALSE;
390 ucmd_T *cmd;
391 int len;
392 int over;
393 long a;
394 garray_T *gap;
395
Bram Moolenaare38eab22019-12-05 21:50:01 +0100396 // In cmdwin, the alternative buffer should be used.
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200397 gap =
398#ifdef FEAT_CMDWIN
399 (cmdwin_type != 0 && get_cmdline_type() == NUL) ?
400 &prevwin->w_buffer->b_ucmds :
401#endif
402 &curbuf->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200403 for (;;)
404 {
405 for (i = 0; i < gap->ga_len; ++i)
406 {
407 cmd = USER_CMD_GA(gap, i);
408 a = (long)cmd->uc_argt;
409
410 // Skip commands which don't match the requested prefix and
411 // commands filtered out.
412 if (STRNCMP(name, cmd->uc_name, name_len) != 0
413 || message_filtered(cmd->uc_name))
414 continue;
415
416 // Put out the title first time
417 if (!found)
418 msg_puts_title(_("\n Name Args Address Complete Definition"));
419 found = TRUE;
420 msg_putchar('\n');
421 if (got_int)
422 break;
423
424 // Special cases
425 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200426 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200427 {
428 msg_putchar('!');
429 --len;
430 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200431 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200432 {
433 msg_putchar('"');
434 --len;
435 }
436 if (gap != &ucmds)
437 {
438 msg_putchar('b');
439 --len;
440 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200441 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200442 {
443 msg_putchar('|');
444 --len;
445 }
446 while (len-- > 0)
447 msg_putchar(' ');
448
449 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
450 len = (int)STRLEN(cmd->uc_name) + 4;
451
452 do {
453 msg_putchar(' ');
454 ++len;
455 } while (len < 22);
456
457 // "over" is how much longer the name is than the column width for
458 // the name, we'll try to align what comes after.
459 over = len - 22;
460 len = 0;
461
462 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200463 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200464 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200465 case 0: IObuff[len++] = '0'; break;
466 case (EX_EXTRA): IObuff[len++] = '*'; break;
467 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
468 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
469 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200470 }
471
472 do {
473 IObuff[len++] = ' ';
474 } while (len < 5 - over);
475
476 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200477 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200478 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200479 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200480 {
481 // -count=N
482 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
483 len += (int)STRLEN(IObuff + len);
484 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200485 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200486 IObuff[len++] = '%';
487 else if (cmd->uc_def >= 0)
488 {
489 // -range=N
490 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
491 len += (int)STRLEN(IObuff + len);
492 }
493 else
494 IObuff[len++] = '.';
495 }
496
497 do {
498 IObuff[len++] = ' ';
499 } while (len < 8 - over);
500
501 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200502 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200503 if (addr_type_complete[j].expand != ADDR_LINES
504 && addr_type_complete[j].expand == cmd->uc_addr_type)
505 {
506 STRCPY(IObuff + len, addr_type_complete[j].shortname);
507 len += (int)STRLEN(IObuff + len);
508 break;
509 }
510
511 do {
512 IObuff[len++] = ' ';
513 } while (len < 13 - over);
514
515 // Completion
516 for (j = 0; command_complete[j].expand != 0; ++j)
517 if (command_complete[j].expand == cmd->uc_compl)
518 {
519 STRCPY(IObuff + len, command_complete[j].name);
520 len += (int)STRLEN(IObuff + len);
521 break;
522 }
523
524 do {
525 IObuff[len++] = ' ';
526 } while (len < 25 - over);
527
528 IObuff[len] = '\0';
529 msg_outtrans(IObuff);
530
531 msg_outtrans_special(cmd->uc_rep, FALSE,
532 name_len == 0 ? Columns - 47 : 0);
533#ifdef FEAT_EVAL
534 if (p_verbose > 0)
535 last_set_msg(cmd->uc_script_ctx);
536#endif
537 out_flush();
538 ui_breakcheck();
539 if (got_int)
540 break;
541 }
542 if (gap == &ucmds || i < gap->ga_len)
543 break;
544 gap = &ucmds;
545 }
546
547 if (!found)
548 msg(_("No user-defined commands found"));
549}
550
551 char *
552uc_fun_cmd(void)
553{
554 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
555 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
556 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
557 0xb9, 0x7f, 0};
558 int i;
559
560 for (i = 0; fcmd[i]; ++i)
561 IObuff[i] = fcmd[i] - 0x40;
562 IObuff[i] = 0;
563 return (char *)IObuff;
564}
565
566/*
567 * Parse address type argument
568 */
569 static int
570parse_addr_type_arg(
571 char_u *value,
572 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200573 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200574{
575 int i, a, b;
576
Bram Moolenaarb7316892019-05-01 18:08:42 +0200577 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200578 {
579 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
580 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
581 if (a && b)
582 {
583 *addr_type_arg = addr_type_complete[i].expand;
584 break;
585 }
586 }
587
Bram Moolenaarb7316892019-05-01 18:08:42 +0200588 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200589 {
590 char_u *err = value;
591
592 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
593 ;
594 err[i] = NUL;
595 semsg(_("E180: Invalid address type value: %s"), err);
596 return FAIL;
597 }
598
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200599 return OK;
600}
601
602/*
603 * Parse a completion argument "value[vallen]".
604 * The detected completion goes in "*complp", argument type in "*argt".
605 * When there is an argument, for function and user defined completion, it's
606 * copied to allocated memory and stored in "*compl_arg".
607 * Returns FAIL if something is wrong.
608 */
609 int
610parse_compl_arg(
611 char_u *value,
612 int vallen,
613 int *complp,
614 long *argt,
615 char_u **compl_arg UNUSED)
616{
617 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200618# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200619 size_t arglen = 0;
620# endif
621 int i;
622 int valend = vallen;
623
624 // Look for any argument part - which is the part after any ','
625 for (i = 0; i < vallen; ++i)
626 {
627 if (value[i] == ',')
628 {
629 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200630# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200631 arglen = vallen - i - 1;
632# endif
633 valend = i;
634 break;
635 }
636 }
637
638 for (i = 0; command_complete[i].expand != 0; ++i)
639 {
640 if ((int)STRLEN(command_complete[i].name) == valend
641 && STRNCMP(value, command_complete[i].name, valend) == 0)
642 {
643 *complp = command_complete[i].expand;
644 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200645 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200646 else if (command_complete[i].expand == EXPAND_DIRECTORIES
647 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200648 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200649 break;
650 }
651 }
652
653 if (command_complete[i].expand == 0)
654 {
655 semsg(_("E180: Invalid complete value: %s"), value);
656 return FAIL;
657 }
658
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200659# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200660 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
661 && arg != NULL)
662# else
663 if (arg != NULL)
664# endif
665 {
666 emsg(_("E468: Completion argument only allowed for custom completion"));
667 return FAIL;
668 }
669
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200670# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200671 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
672 && arg == NULL)
673 {
674 emsg(_("E467: Custom completion requires a function argument"));
675 return FAIL;
676 }
677
678 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200679 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200680# endif
681 return OK;
682}
683
684/*
685 * Scan attributes in the ":command" command.
686 * Return FAIL when something is wrong.
687 */
688 static int
689uc_scan_attr(
690 char_u *attr,
691 size_t len,
692 long *argt,
693 long *def,
694 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200695 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200696 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200697 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200698{
699 char_u *p;
700
701 if (len == 0)
702 {
703 emsg(_("E175: No attribute specified"));
704 return FAIL;
705 }
706
707 // First, try the simple attributes (no arguments)
708 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200709 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200710 else if (STRNICMP(attr, "buffer", len) == 0)
711 *flags |= UC_BUFFER;
712 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200713 *argt |= EX_REGSTR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200714 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200715 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200716 else
717 {
718 int i;
719 char_u *val = NULL;
720 size_t vallen = 0;
721 size_t attrlen = len;
722
723 // Look for the attribute name - which is the part before any '='
724 for (i = 0; i < (int)len; ++i)
725 {
726 if (attr[i] == '=')
727 {
728 val = &attr[i + 1];
729 vallen = len - i - 1;
730 attrlen = i;
731 break;
732 }
733 }
734
735 if (STRNICMP(attr, "nargs", attrlen) == 0)
736 {
737 if (vallen == 1)
738 {
739 if (*val == '0')
740 // Do nothing - this is the default
741 ;
742 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200743 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200744 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200745 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200747 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200748 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200749 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200750 else
751 goto wrong_nargs;
752 }
753 else
754 {
755wrong_nargs:
756 emsg(_("E176: Invalid number of arguments"));
757 return FAIL;
758 }
759 }
760 else if (STRNICMP(attr, "range", attrlen) == 0)
761 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200762 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200763 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200764 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200765 else if (val != NULL)
766 {
767 p = val;
768 if (*def >= 0)
769 {
770two_count:
771 emsg(_("E177: Count cannot be specified twice"));
772 return FAIL;
773 }
774
775 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200776 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777
778 if (p != val + vallen || vallen == 0)
779 {
780invalid_count:
781 emsg(_("E178: Invalid default value for count"));
782 return FAIL;
783 }
784 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200785 // default for -range is using buffer lines
786 if (*addr_type_arg == ADDR_NONE)
787 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200788 }
789 else if (STRNICMP(attr, "count", attrlen) == 0)
790 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200791 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200792 // default for -count is using any number
793 if (*addr_type_arg == ADDR_NONE)
794 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200795
796 if (val != NULL)
797 {
798 p = val;
799 if (*def >= 0)
800 goto two_count;
801
802 *def = getdigits(&p);
803
804 if (p != val + vallen)
805 goto invalid_count;
806 }
807
808 if (*def < 0)
809 *def = 0;
810 }
811 else if (STRNICMP(attr, "complete", attrlen) == 0)
812 {
813 if (val == NULL)
814 {
815 emsg(_("E179: argument required for -complete"));
816 return FAIL;
817 }
818
Bram Moolenaar52111f82019-04-29 21:30:45 +0200819 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200820 == FAIL)
821 return FAIL;
822 }
823 else if (STRNICMP(attr, "addr", attrlen) == 0)
824 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200825 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200826 if (val == NULL)
827 {
828 emsg(_("E179: argument required for -addr"));
829 return FAIL;
830 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200831 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200832 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200833 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200834 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200835 }
836 else
837 {
838 char_u ch = attr[len];
839 attr[len] = '\0';
840 semsg(_("E181: Invalid attribute: %s"), attr);
841 attr[len] = ch;
842 return FAIL;
843 }
844 }
845
846 return OK;
847}
848
849/*
850 * Add a user command to the list or replace an existing one.
851 */
852 static int
853uc_add_command(
854 char_u *name,
855 size_t name_len,
856 char_u *rep,
857 long argt,
858 long def,
859 int flags,
860 int compl,
861 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200862 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200863 int force)
864{
865 ucmd_T *cmd = NULL;
866 char_u *p;
867 int i;
868 int cmp = 1;
869 char_u *rep_buf = NULL;
870 garray_T *gap;
871
Bram Moolenaar459fd782019-10-13 16:43:39 +0200872 replace_termcodes(rep, &rep_buf, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200873 if (rep_buf == NULL)
874 {
875 // Can't replace termcodes - try using the string as is
876 rep_buf = vim_strsave(rep);
877
878 // Give up if out of memory
879 if (rep_buf == NULL)
880 return FAIL;
881 }
882
883 // get address of growarray: global or in curbuf
884 if (flags & UC_BUFFER)
885 {
886 gap = &curbuf->b_ucmds;
887 if (gap->ga_itemsize == 0)
888 ga_init2(gap, (int)sizeof(ucmd_T), 4);
889 }
890 else
891 gap = &ucmds;
892
893 // Search for the command in the already defined commands.
894 for (i = 0; i < gap->ga_len; ++i)
895 {
896 size_t len;
897
898 cmd = USER_CMD_GA(gap, i);
899 len = STRLEN(cmd->uc_name);
900 cmp = STRNCMP(name, cmd->uc_name, name_len);
901 if (cmp == 0)
902 {
903 if (name_len < len)
904 cmp = -1;
905 else if (name_len > len)
906 cmp = 1;
907 }
908
909 if (cmp == 0)
910 {
911 // Command can be replaced with "command!" and when sourcing the
912 // same script again, but only once.
913 if (!force
914#ifdef FEAT_EVAL
915 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
916 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
917#endif
918 )
919 {
920 semsg(_("E174: Command already exists: add ! to replace it: %s"),
921 name);
922 goto fail;
923 }
924
925 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200926#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200927 VIM_CLEAR(cmd->uc_compl_arg);
928#endif
929 break;
930 }
931
932 // Stop as soon as we pass the name to add
933 if (cmp < 0)
934 break;
935 }
936
937 // Extend the array unless we're replacing an existing command
938 if (cmp != 0)
939 {
940 if (ga_grow(gap, 1) != OK)
941 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200942 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200943 goto fail;
944
945 cmd = USER_CMD_GA(gap, i);
946 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
947
948 ++gap->ga_len;
949
950 cmd->uc_name = p;
951 }
952
953 cmd->uc_rep = rep_buf;
954 cmd->uc_argt = argt;
955 cmd->uc_def = def;
956 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +0100958#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100959 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200960 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961#endif
962 cmd->uc_addr_type = addr_type;
963
964 return OK;
965
966fail:
967 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200968#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200969 vim_free(compl_arg);
970#endif
971 return FAIL;
972}
973
974/*
975 * ":command ..." implementation
976 */
977 void
978ex_command(exarg_T *eap)
979{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200980 char_u *name;
981 char_u *end;
982 char_u *p;
983 long argt = 0;
984 long def = -1;
985 int flags = 0;
986 int compl = EXPAND_NOTHING;
987 char_u *compl_arg = NULL;
988 cmd_addr_T addr_type_arg = ADDR_NONE;
989 int has_attr = (eap->arg[0] == '-');
990 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200991
992 p = eap->arg;
993
994 // Check for attributes
995 while (*p == '-')
996 {
997 ++p;
998 end = skiptowhite(p);
999 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1000 &compl_arg, &addr_type_arg) == FAIL)
1001 return;
1002 p = skipwhite(end);
1003 }
1004
1005 // Get the name (if any) and skip to the following argument
1006 name = p;
1007 if (ASCII_ISALPHA(*p))
1008 while (ASCII_ISALNUM(*p))
1009 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001010 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001011 {
1012 emsg(_("E182: Invalid command name"));
1013 return;
1014 }
1015 end = p;
1016 name_len = (int)(end - name);
1017
1018 // If there is nothing after the name, and no attributes were specified,
1019 // we are listing commands
1020 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001021 if (!has_attr && ends_excmd2(eap->arg, p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001022 uc_list(name, end - name);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001023 else if (!ASCII_ISUPPER(*name))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001024 emsg(_("E183: User defined commands must start with an uppercase letter"));
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001025 else if ((name_len == 1 && *name == 'X')
1026 || (name_len <= 4
1027 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001028 emsg(_("E841: Reserved name, cannot be used for user defined command"));
Martin Tournoijde69a732021-07-11 14:28:25 +02001029 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001030 {
1031 // Some plugins rely on silently ignoring the mistake, only make this
1032 // an error in Vim9 script.
1033 if (in_vim9script())
1034 emsg(_(e_complete_used_without_nargs));
1035 else
1036 give_warning_with_source(
1037 (char_u *)_(e_complete_used_without_nargs), TRUE, TRUE);
1038 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001039 else
1040 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1041 addr_type_arg, eap->forceit);
1042}
1043
1044/*
1045 * ":comclear" implementation
1046 * Clear all user commands, global and for current buffer.
1047 */
1048 void
1049ex_comclear(exarg_T *eap UNUSED)
1050{
1051 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001052 if (curbuf != NULL)
1053 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001054}
1055
1056/*
1057 * Clear all user commands for "gap".
1058 */
1059 void
1060uc_clear(garray_T *gap)
1061{
1062 int i;
1063 ucmd_T *cmd;
1064
1065 for (i = 0; i < gap->ga_len; ++i)
1066 {
1067 cmd = USER_CMD_GA(gap, i);
1068 vim_free(cmd->uc_name);
1069 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001070# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001071 vim_free(cmd->uc_compl_arg);
1072# endif
1073 }
1074 ga_clear(gap);
1075}
1076
1077/*
1078 * ":delcommand" implementation
1079 */
1080 void
1081ex_delcommand(exarg_T *eap)
1082{
1083 int i = 0;
1084 ucmd_T *cmd = NULL;
1085 int cmp = -1;
1086 garray_T *gap;
1087
1088 gap = &curbuf->b_ucmds;
1089 for (;;)
1090 {
1091 for (i = 0; i < gap->ga_len; ++i)
1092 {
1093 cmd = USER_CMD_GA(gap, i);
1094 cmp = STRCMP(eap->arg, cmd->uc_name);
1095 if (cmp <= 0)
1096 break;
1097 }
1098 if (gap == &ucmds || cmp == 0)
1099 break;
1100 gap = &ucmds;
1101 }
1102
1103 if (cmp != 0)
1104 {
1105 semsg(_("E184: No such user-defined command: %s"), eap->arg);
1106 return;
1107 }
1108
1109 vim_free(cmd->uc_name);
1110 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001111# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001112 vim_free(cmd->uc_compl_arg);
1113# endif
1114
1115 --gap->ga_len;
1116
1117 if (i < gap->ga_len)
1118 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1119}
1120
1121/*
1122 * Split and quote args for <f-args>.
1123 */
1124 static char_u *
1125uc_split_args(char_u *arg, size_t *lenp)
1126{
1127 char_u *buf;
1128 char_u *p;
1129 char_u *q;
1130 int len;
1131
1132 // Precalculate length
1133 p = arg;
1134 len = 2; // Initial and final quotes
1135
1136 while (*p)
1137 {
1138 if (p[0] == '\\' && p[1] == '\\')
1139 {
1140 len += 2;
1141 p += 2;
1142 }
1143 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1144 {
1145 len += 1;
1146 p += 2;
1147 }
1148 else if (*p == '\\' || *p == '"')
1149 {
1150 len += 2;
1151 p += 1;
1152 }
1153 else if (VIM_ISWHITE(*p))
1154 {
1155 p = skipwhite(p);
1156 if (*p == NUL)
1157 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001158 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001159 }
1160 else
1161 {
1162 int charlen = (*mb_ptr2len)(p);
1163
1164 len += charlen;
1165 p += charlen;
1166 }
1167 }
1168
1169 buf = alloc(len + 1);
1170 if (buf == NULL)
1171 {
1172 *lenp = 0;
1173 return buf;
1174 }
1175
1176 p = arg;
1177 q = buf;
1178 *q++ = '"';
1179 while (*p)
1180 {
1181 if (p[0] == '\\' && p[1] == '\\')
1182 {
1183 *q++ = '\\';
1184 *q++ = '\\';
1185 p += 2;
1186 }
1187 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1188 {
1189 *q++ = p[1];
1190 p += 2;
1191 }
1192 else if (*p == '\\' || *p == '"')
1193 {
1194 *q++ = '\\';
1195 *q++ = *p++;
1196 }
1197 else if (VIM_ISWHITE(*p))
1198 {
1199 p = skipwhite(p);
1200 if (*p == NUL)
1201 break;
1202 *q++ = '"';
1203 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001204 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001205 *q++ = '"';
1206 }
1207 else
1208 {
1209 MB_COPY_CHAR(p, q);
1210 }
1211 }
1212 *q++ = '"';
1213 *q = 0;
1214
1215 *lenp = len;
1216 return buf;
1217}
1218
1219 static size_t
1220add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1221{
1222 size_t result;
1223
1224 result = STRLEN(mod_str);
1225 if (*multi_mods)
1226 result += 1;
1227 if (buf != NULL)
1228 {
1229 if (*multi_mods)
1230 STRCAT(buf, " ");
1231 STRCAT(buf, mod_str);
1232 }
1233
1234 *multi_mods = 1;
1235
1236 return result;
1237}
1238
1239/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001240 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001241 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001242 */
1243 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001244add_win_cmd_modifers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001245{
1246 size_t result = 0;
1247
1248 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001249 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001250 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1251 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001252 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001253 result += add_cmd_modifier(buf, "belowright", multi_mods);
1254 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001255 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001256 result += add_cmd_modifier(buf, "botright", multi_mods);
1257
1258 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001259 if (cmod->cmod_tab > 0)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001260 result += add_cmd_modifier(buf, "tab", multi_mods);
1261 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001262 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001263 result += add_cmd_modifier(buf, "topleft", multi_mods);
1264 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001265 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001266 result += add_cmd_modifier(buf, "vertical", multi_mods);
1267 return result;
1268}
1269
1270/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001271 * Generate text for the "cmod" command modifiers.
1272 * If "buf" is NULL just return the length.
1273 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001274 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001275produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1276{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001277 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001278 int multi_mods = 0;
1279 int i;
1280 typedef struct {
1281 int flag;
1282 char *name;
1283 } mod_entry_T;
1284 static mod_entry_T mod_entries[] = {
1285#ifdef FEAT_BROWSE_CMD
1286 {CMOD_BROWSE, "browse"},
1287#endif
1288#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1289 {CMOD_CONFIRM, "confirm"},
1290#endif
1291 {CMOD_HIDE, "hide"},
1292 {CMOD_KEEPALT, "keepalt"},
1293 {CMOD_KEEPJUMPS, "keepjumps"},
1294 {CMOD_KEEPMARKS, "keepmarks"},
1295 {CMOD_KEEPPATTERNS, "keeppatterns"},
1296 {CMOD_LOCKMARKS, "lockmarks"},
1297 {CMOD_NOSWAPFILE, "noswapfile"},
1298 {CMOD_UNSILENT, "unsilent"},
1299 {CMOD_NOAUTOCMD, "noautocmd"},
1300#ifdef HAVE_SANDBOX
1301 {CMOD_SANDBOX, "sandbox"},
1302#endif
1303 {0, NULL}
1304 };
1305
1306 result = quote ? 2 : 0;
1307 if (buf != NULL)
1308 {
1309 if (quote)
1310 *buf++ = '"';
1311 *buf = '\0';
1312 }
1313
1314 // the modifiers that are simple flags
1315 for (i = 0; mod_entries[i].name != NULL; ++i)
1316 if (cmod->cmod_flags & mod_entries[i].flag)
1317 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1318
1319 // :silent
1320 if (cmod->cmod_flags & CMOD_SILENT)
1321 result += add_cmd_modifier(buf,
1322 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1323 : "silent", &multi_mods);
1324 // :verbose
1325 if (p_verbose > 0)
1326 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1327 // flags from cmod->cmod_split
1328 result += add_win_cmd_modifers(buf, cmod, &multi_mods);
1329 if (quote && buf != NULL)
1330 {
1331 buf += result - 2;
1332 *buf = '"';
1333 }
1334 return result;
1335}
1336
1337/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001338 * Check for a <> code in a user command.
1339 * "code" points to the '<'. "len" the length of the <> (inclusive).
1340 * "buf" is where the result is to be added.
1341 * "split_buf" points to a buffer used for splitting, caller should free it.
1342 * "split_len" is the length of what "split_buf" contains.
1343 * Returns the length of the replacement, which has been added to "buf".
1344 * Returns -1 if there was no match, and only the "<" has been copied.
1345 */
1346 static size_t
1347uc_check_code(
1348 char_u *code,
1349 size_t len,
1350 char_u *buf,
1351 ucmd_T *cmd, // the user command we're expanding
1352 exarg_T *eap, // ex arguments
1353 char_u **split_buf,
1354 size_t *split_len)
1355{
1356 size_t result = 0;
1357 char_u *p = code + 1;
1358 size_t l = len - 2;
1359 int quote = 0;
1360 enum {
1361 ct_ARGS,
1362 ct_BANG,
1363 ct_COUNT,
1364 ct_LINE1,
1365 ct_LINE2,
1366 ct_RANGE,
1367 ct_MODS,
1368 ct_REGISTER,
1369 ct_LT,
1370 ct_NONE
1371 } type = ct_NONE;
1372
1373 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1374 {
1375 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1376 p += 2;
1377 l -= 2;
1378 }
1379
1380 ++l;
1381 if (l <= 1)
1382 type = ct_NONE;
1383 else if (STRNICMP(p, "args>", l) == 0)
1384 type = ct_ARGS;
1385 else if (STRNICMP(p, "bang>", l) == 0)
1386 type = ct_BANG;
1387 else if (STRNICMP(p, "count>", l) == 0)
1388 type = ct_COUNT;
1389 else if (STRNICMP(p, "line1>", l) == 0)
1390 type = ct_LINE1;
1391 else if (STRNICMP(p, "line2>", l) == 0)
1392 type = ct_LINE2;
1393 else if (STRNICMP(p, "range>", l) == 0)
1394 type = ct_RANGE;
1395 else if (STRNICMP(p, "lt>", l) == 0)
1396 type = ct_LT;
1397 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1398 type = ct_REGISTER;
1399 else if (STRNICMP(p, "mods>", l) == 0)
1400 type = ct_MODS;
1401
1402 switch (type)
1403 {
1404 case ct_ARGS:
1405 // Simple case first
1406 if (*eap->arg == NUL)
1407 {
1408 if (quote == 1)
1409 {
1410 result = 2;
1411 if (buf != NULL)
1412 STRCPY(buf, "''");
1413 }
1414 else
1415 result = 0;
1416 break;
1417 }
1418
1419 // When specified there is a single argument don't split it.
1420 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001421 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001422 quote = 1;
1423
1424 switch (quote)
1425 {
1426 case 0: // No quoting, no splitting
1427 result = STRLEN(eap->arg);
1428 if (buf != NULL)
1429 STRCPY(buf, eap->arg);
1430 break;
1431 case 1: // Quote, but don't split
1432 result = STRLEN(eap->arg) + 2;
1433 for (p = eap->arg; *p; ++p)
1434 {
1435 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1436 // DBCS can contain \ in a trail byte, skip the
1437 // double-byte character.
1438 ++p;
1439 else
1440 if (*p == '\\' || *p == '"')
1441 ++result;
1442 }
1443
1444 if (buf != NULL)
1445 {
1446 *buf++ = '"';
1447 for (p = eap->arg; *p; ++p)
1448 {
1449 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1450 // DBCS can contain \ in a trail byte, copy the
1451 // double-byte character to avoid escaping.
1452 *buf++ = *p++;
1453 else
1454 if (*p == '\\' || *p == '"')
1455 *buf++ = '\\';
1456 *buf++ = *p;
1457 }
1458 *buf = '"';
1459 }
1460
1461 break;
1462 case 2: // Quote and split (<f-args>)
1463 // This is hard, so only do it once, and cache the result
1464 if (*split_buf == NULL)
1465 *split_buf = uc_split_args(eap->arg, split_len);
1466
1467 result = *split_len;
1468 if (buf != NULL && result != 0)
1469 STRCPY(buf, *split_buf);
1470
1471 break;
1472 }
1473 break;
1474
1475 case ct_BANG:
1476 result = eap->forceit ? 1 : 0;
1477 if (quote)
1478 result += 2;
1479 if (buf != NULL)
1480 {
1481 if (quote)
1482 *buf++ = '"';
1483 if (eap->forceit)
1484 *buf++ = '!';
1485 if (quote)
1486 *buf = '"';
1487 }
1488 break;
1489
1490 case ct_LINE1:
1491 case ct_LINE2:
1492 case ct_RANGE:
1493 case ct_COUNT:
1494 {
1495 char num_buf[20];
1496 long num = (type == ct_LINE1) ? eap->line1 :
1497 (type == ct_LINE2) ? eap->line2 :
1498 (type == ct_RANGE) ? eap->addr_count :
1499 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1500 size_t num_len;
1501
1502 sprintf(num_buf, "%ld", num);
1503 num_len = STRLEN(num_buf);
1504 result = num_len;
1505
1506 if (quote)
1507 result += 2;
1508
1509 if (buf != NULL)
1510 {
1511 if (quote)
1512 *buf++ = '"';
1513 STRCPY(buf, num_buf);
1514 buf += num_len;
1515 if (quote)
1516 *buf = '"';
1517 }
1518
1519 break;
1520 }
1521
1522 case ct_MODS:
1523 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001524 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001525 break;
1526 }
1527
1528 case ct_REGISTER:
1529 result = eap->regname ? 1 : 0;
1530 if (quote)
1531 result += 2;
1532 if (buf != NULL)
1533 {
1534 if (quote)
1535 *buf++ = '\'';
1536 if (eap->regname)
1537 *buf++ = eap->regname;
1538 if (quote)
1539 *buf = '\'';
1540 }
1541 break;
1542
1543 case ct_LT:
1544 result = 1;
1545 if (buf != NULL)
1546 *buf = '<';
1547 break;
1548
1549 default:
1550 // Not recognized: just copy the '<' and return -1.
1551 result = (size_t)-1;
1552 if (buf != NULL)
1553 *buf = '<';
1554 break;
1555 }
1556
1557 return result;
1558}
1559
1560/*
1561 * Execute a user defined command.
1562 */
1563 void
1564do_ucmd(exarg_T *eap)
1565{
1566 char_u *buf;
1567 char_u *p;
1568 char_u *q;
1569
1570 char_u *start;
1571 char_u *end = NULL;
1572 char_u *ksp;
1573 size_t len, totlen;
1574
1575 size_t split_len = 0;
1576 char_u *split_buf = NULL;
1577 ucmd_T *cmd;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001578 sctx_T save_current_sctx = current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001579
1580 if (eap->cmdidx == CMD_USER)
1581 cmd = USER_CMD(eap->useridx);
1582 else
1583 cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
1584
1585 /*
1586 * Replace <> in the command by the arguments.
1587 * First round: "buf" is NULL, compute length, allocate "buf".
1588 * Second round: copy result into "buf".
1589 */
1590 buf = NULL;
1591 for (;;)
1592 {
1593 p = cmd->uc_rep; // source
1594 q = buf; // destination
1595 totlen = 0;
1596
1597 for (;;)
1598 {
1599 start = vim_strchr(p, '<');
1600 if (start != NULL)
1601 end = vim_strchr(start + 1, '>');
1602 if (buf != NULL)
1603 {
1604 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1605 ;
1606 if (*ksp == K_SPECIAL
1607 && (start == NULL || ksp < start || end == NULL)
1608 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1609# ifdef FEAT_GUI
1610 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1611# endif
1612 ))
1613 {
1614 // K_SPECIAL has been put in the buffer as K_SPECIAL
1615 // KS_SPECIAL KE_FILLER, like for mappings, but
1616 // do_cmdline() doesn't handle that, so convert it back.
1617 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1618 len = ksp - p;
1619 if (len > 0)
1620 {
1621 mch_memmove(q, p, len);
1622 q += len;
1623 }
1624 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1625 p = ksp + 3;
1626 continue;
1627 }
1628 }
1629
1630 // break if no <item> is found
1631 if (start == NULL || end == NULL)
1632 break;
1633
1634 // Include the '>'
1635 ++end;
1636
1637 // Take everything up to the '<'
1638 len = start - p;
1639 if (buf == NULL)
1640 totlen += len;
1641 else
1642 {
1643 mch_memmove(q, p, len);
1644 q += len;
1645 }
1646
1647 len = uc_check_code(start, end - start, q, cmd, eap,
1648 &split_buf, &split_len);
1649 if (len == (size_t)-1)
1650 {
1651 // no match, continue after '<'
1652 p = start + 1;
1653 len = 1;
1654 }
1655 else
1656 p = end;
1657 if (buf == NULL)
1658 totlen += len;
1659 else
1660 q += len;
1661 }
1662 if (buf != NULL) // second time here, finished
1663 {
1664 STRCPY(q, p);
1665 break;
1666 }
1667
1668 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001669 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001670 if (buf == NULL)
1671 {
1672 vim_free(split_buf);
1673 return;
1674 }
1675 }
1676
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001677 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001678#ifdef FEAT_EVAL
1679 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001680#endif
1681 (void)do_cmdline(buf, eap->getline, eap->cookie,
1682 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001683 current_sctx = save_current_sctx;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001684 vim_free(buf);
1685 vim_free(split_buf);
1686}