blob: e2c0114ca3ca8ce585f39b90e311e65a64dd4baa [file] [log] [blame]
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * usercmd.c: User defined command support
12 */
13
14#include "vim.h"
15
16typedef struct ucmd
17{
18 char_u *uc_name; // The command name
19 long_u uc_argt; // The argument type
20 char_u *uc_rep; // The command's replacement string
21 long uc_def; // The default value for a range/count
22 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020023 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020024 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000025 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010026# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020027 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028# endif
29} ucmd_T;
30
31// List of all user commands.
32static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
33
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000034// When non-zero it is not allowed to add or remove user commands
35static int ucmd_locked = 0;
36
Bram Moolenaarac9fb182019-04-27 13:04:13 +020037#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
38#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
39
40/*
41 * List of names for completion for ":command" with the EXPAND_ flag.
42 * Must be alphabetical for completion.
43 */
44static struct
45{
46 int expand;
47 char *name;
48} command_complete[] =
49{
50 {EXPAND_ARGLIST, "arglist"},
51 {EXPAND_AUGROUP, "augroup"},
52 {EXPAND_BEHAVE, "behave"},
53 {EXPAND_BUFFERS, "buffer"},
54 {EXPAND_COLORS, "color"},
55 {EXPAND_COMMANDS, "command"},
56 {EXPAND_COMPILER, "compiler"},
57#if defined(FEAT_CSCOPE)
58 {EXPAND_CSCOPE, "cscope"},
59#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020060#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020061 {EXPAND_USER_DEFINED, "custom"},
62 {EXPAND_USER_LIST, "customlist"},
63#endif
Bram Moolenaarae7dba82019-12-29 13:56:33 +010064 {EXPAND_DIFF_BUFFERS, "diff_buffer"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020065 {EXPAND_DIRECTORIES, "dir"},
66 {EXPAND_ENV_VARS, "environment"},
67 {EXPAND_EVENTS, "event"},
68 {EXPAND_EXPRESSION, "expression"},
69 {EXPAND_FILES, "file"},
70 {EXPAND_FILES_IN_PATH, "file_in_path"},
71 {EXPAND_FILETYPE, "filetype"},
72 {EXPAND_FUNCTIONS, "function"},
73 {EXPAND_HELP, "help"},
74 {EXPAND_HIGHLIGHT, "highlight"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020075 {EXPAND_HISTORY, "history"},
Doug Kearns81642d92024-01-04 22:37:44 +010076#if defined(FEAT_KEYMAP)
77 {EXPAND_KEYMAP, "keymap"},
78#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020079#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
80 {EXPAND_LOCALES, "locale"},
81#endif
82 {EXPAND_MAPCLEAR, "mapclear"},
83 {EXPAND_MAPPINGS, "mapping"},
84 {EXPAND_MENUS, "menu"},
85 {EXPAND_MESSAGES, "messages"},
86 {EXPAND_OWNSYNTAX, "syntax"},
87#if defined(FEAT_PROFILE)
88 {EXPAND_SYNTIME, "syntime"},
89#endif
90 {EXPAND_SETTINGS, "option"},
91 {EXPAND_PACKADD, "packadd"},
roota6759382023-01-21 21:56:06 +000092 {EXPAND_RUNTIME, "runtime"},
Bram Moolenaarac9fb182019-04-27 13:04:13 +020093 {EXPAND_SHELLCMD, "shellcmd"},
94#if defined(FEAT_SIGNS)
95 {EXPAND_SIGN, "sign"},
96#endif
97 {EXPAND_TAGS, "tag"},
98 {EXPAND_TAGS_LISTFILES, "tag_listfiles"},
99 {EXPAND_USER, "user"},
100 {EXPAND_USER_VARS, "var"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000101#if defined(FEAT_EVAL)
102 {EXPAND_BREAKPOINT, "breakpoint"},
Yegappan Lakshmanan454ce672022-03-24 11:22:13 +0000103 {EXPAND_SCRIPTNAMES, "scriptnames"},
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +0000104#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200105 {0, NULL}
106};
107
108/*
109 * List of names of address types. Must be alphabetical for completion.
110 */
111static struct
112{
Bram Moolenaarb7316892019-05-01 18:08:42 +0200113 cmd_addr_T expand;
114 char *name;
115 char *shortname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200116} addr_type_complete[] =
117{
118 {ADDR_ARGUMENTS, "arguments", "arg"},
119 {ADDR_LINES, "lines", "line"},
120 {ADDR_LOADED_BUFFERS, "loaded_buffers", "load"},
121 {ADDR_TABS, "tabs", "tab"},
122 {ADDR_BUFFERS, "buffers", "buf"},
123 {ADDR_WINDOWS, "windows", "win"},
124 {ADDR_QUICKFIX, "quickfix", "qf"},
125 {ADDR_OTHER, "other", "?"},
Bram Moolenaarb7316892019-05-01 18:08:42 +0200126 {ADDR_NONE, NULL, NULL}
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200127};
128
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200129/*
130 * Search for a user command that matches "eap->cmd".
131 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
132 * Return a pointer to just after the command.
133 * Return NULL if there is no matching command.
134 */
135 char_u *
136find_ucmd(
137 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000138 char_u *p, // end of the command (possibly including count)
139 int *full, // set to TRUE for a full match
140 expand_T *xp, // used for completion, NULL otherwise
141 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200142{
143 int len = (int)(p - eap->cmd);
144 int j, k, matchlen = 0;
145 ucmd_T *uc;
146 int found = FALSE;
147 int possible = FALSE;
148 char_u *cp, *np; // Point into typed cmd and test name
149 garray_T *gap;
150 int amb_local = FALSE; // Found ambiguous buffer-local command,
151 // only full match global is accepted.
152
153 /*
154 * Look for buffer-local user commands first, then global ones.
155 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000156 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200157 for (;;)
158 {
159 for (j = 0; j < gap->ga_len; ++j)
160 {
161 uc = USER_CMD_GA(gap, j);
162 cp = eap->cmd;
163 np = uc->uc_name;
164 k = 0;
165 while (k < len && *np != NUL && *cp++ == *np++)
166 k++;
167 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
168 {
169 // If finding a second match, the command is ambiguous. But
170 // not if a buffer-local command wasn't a full match and a
171 // global command is a full match.
172 if (k == len && found && *np != NUL)
173 {
174 if (gap == &ucmds)
175 return NULL;
176 amb_local = TRUE;
177 }
178
179 if (!found || (k == len && *np == NUL))
180 {
181 // If we matched up to a digit, then there could
182 // be another command including the digit that we
183 // should use instead.
184 if (k == len)
185 found = TRUE;
186 else
187 possible = TRUE;
188
189 if (gap == &ucmds)
190 eap->cmdidx = CMD_USER;
191 else
192 eap->cmdidx = CMD_USER_BUF;
193 eap->argt = (long)uc->uc_argt;
194 eap->useridx = j;
195 eap->addr_type = uc->uc_addr_type;
196
Bram Moolenaar52111f82019-04-29 21:30:45 +0200197 if (complp != NULL)
198 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200199# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200200 if (xp != NULL)
201 {
202 xp->xp_arg = uc->uc_compl_arg;
203 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100204 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200205 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200206# endif
207 // Do not search for further abbreviations
208 // if this is an exact match.
209 matchlen = k;
210 if (k == len && *np == NUL)
211 {
212 if (full != NULL)
213 *full = TRUE;
214 amb_local = FALSE;
215 break;
216 }
217 }
218 }
219 }
220
221 // Stop if we found a full match or searched all.
222 if (j < gap->ga_len || gap == &ucmds)
223 break;
224 gap = &ucmds;
225 }
226
227 // Only found ambiguous matches.
228 if (amb_local)
229 {
230 if (xp != NULL)
231 xp->xp_context = EXPAND_UNSUCCESSFUL;
232 return NULL;
233 }
234
235 // The match we found may be followed immediately by a number. Move "p"
236 // back to point to it.
237 if (found || possible)
238 return p + (matchlen - len);
239 return p;
240}
241
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000242/*
243 * Set completion context for :command
244 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200245 char_u *
246set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
247{
248 char_u *arg = arg_in;
249 char_u *p;
250
251 // Check for attributes
252 while (*arg == '-')
253 {
254 arg++; // Skip "-"
255 p = skiptowhite(arg);
256 if (*p == NUL)
257 {
258 // Cursor is still in the attribute
259 p = vim_strchr(arg, '=');
260 if (p == NULL)
261 {
262 // No "=", so complete attribute names
263 xp->xp_context = EXPAND_USER_CMD_FLAGS;
264 xp->xp_pattern = arg;
265 return NULL;
266 }
267
268 // For the -complete, -nargs and -addr attributes, we complete
269 // their arguments as well.
270 if (STRNICMP(arg, "complete", p - arg) == 0)
271 {
272 xp->xp_context = EXPAND_USER_COMPLETE;
273 xp->xp_pattern = p + 1;
274 return NULL;
275 }
276 else if (STRNICMP(arg, "nargs", p - arg) == 0)
277 {
278 xp->xp_context = EXPAND_USER_NARGS;
279 xp->xp_pattern = p + 1;
280 return NULL;
281 }
282 else if (STRNICMP(arg, "addr", p - arg) == 0)
283 {
284 xp->xp_context = EXPAND_USER_ADDR_TYPE;
285 xp->xp_pattern = p + 1;
286 return NULL;
287 }
288 return NULL;
289 }
290 arg = skipwhite(p);
291 }
292
293 // After the attributes comes the new command name
294 p = skiptowhite(arg);
295 if (*p == NUL)
296 {
297 xp->xp_context = EXPAND_USER_COMMANDS;
298 xp->xp_pattern = arg;
299 return NULL;
300 }
301
302 // And finally comes a normal command
303 return skipwhite(p);
304}
305
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000306/*
307 * Set the completion context for the argument of a user defined command.
308 */
309 char_u *
310set_context_in_user_cmdarg(
311 char_u *cmd UNUSED,
312 char_u *arg,
313 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000314 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000315 expand_T *xp,
316 int forceit)
317{
318 char_u *p;
319
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000320 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000321 return NULL;
322
323 if (argt & EX_XFILE)
324 {
325 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000326 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000327 return NULL;
328 }
329
330#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000331 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000332 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
333#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000334 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000335 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000336 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000337 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
338 FALSE, CMD_map);
339 // Find start of last argument.
340 p = arg;
341 while (*p)
342 {
343 if (*p == ' ')
344 // argument starts after a space
345 arg = p + 1;
346 else if (*p == '\\' && *(p + 1) != NUL)
347 ++p; // skip over escaped character
348 MB_PTR_ADV(p);
349 }
350 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000351 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000352
353 return NULL;
354}
355
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200356 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200357expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200358{
359 return get_user_commands(NULL, idx - (int)CMD_SIZE);
360}
361
362/*
363 * Function given to ExpandGeneric() to obtain the list of user command names.
364 */
365 char_u *
366get_user_commands(expand_T *xp UNUSED, int idx)
367{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200368 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000369 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200370
371 if (idx < buf->b_ucmds.ga_len)
372 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100373
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200374 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200375 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100376 {
377 int i;
378 char_u *name = USER_CMD(idx)->uc_name;
379
380 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
381 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
382 // global command is overruled by buffer-local one
383 return (char_u *)"";
384 return name;
385 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200386 return NULL;
387}
388
Dominique Pelle748b3082022-01-08 12:41:16 +0000389#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200390/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200391 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
392 * CMD_USER_BUF.
393 * Returns NULL if the command is not found.
394 */
395 char_u *
396get_user_command_name(int idx, int cmdidx)
397{
398 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
399 return USER_CMD(idx)->uc_name;
400 if (cmdidx == CMD_USER_BUF)
401 {
402 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000403 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200404
405 if (idx < buf->b_ucmds.ga_len)
406 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
407 }
408 return NULL;
409}
Dominique Pelle748b3082022-01-08 12:41:16 +0000410#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200411
412/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200413 * Function given to ExpandGeneric() to obtain the list of user address type
414 * names.
415 */
416 char_u *
417get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
418{
419 return (char_u *)addr_type_complete[idx].name;
420}
421
422/*
423 * Function given to ExpandGeneric() to obtain the list of user command
424 * attributes.
425 */
426 char_u *
427get_user_cmd_flags(expand_T *xp UNUSED, int idx)
428{
429 static char *user_cmd_flags[] = {
430 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000431 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200432 };
433
K.Takataeeec2542021-06-02 13:28:16 +0200434 if (idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200435 return NULL;
436 return (char_u *)user_cmd_flags[idx];
437}
438
439/*
440 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
441 */
442 char_u *
443get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
444{
445 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
446
K.Takataeeec2542021-06-02 13:28:16 +0200447 if (idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200448 return NULL;
449 return (char_u *)user_cmd_nargs[idx];
450}
451
452/*
453 * Function given to ExpandGeneric() to obtain the list of values for
454 * -complete.
455 */
456 char_u *
457get_user_cmd_complete(expand_T *xp UNUSED, int idx)
458{
459 return (char_u *)command_complete[idx].name;
460}
461
Dominique Pelle748b3082022-01-08 12:41:16 +0000462#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100463/*
464 * Get the name of completion type "expand" as a string.
465 */
466 char_u *
467cmdcomplete_type_to_str(int expand)
468{
469 int i;
470
471 for (i = 0; command_complete[i].expand != 0; i++)
472 if (command_complete[i].expand == expand)
473 return (char_u *)command_complete[i].name;
474
475 return NULL;
476}
477
478/*
479 * Get the index of completion type "complete_str".
480 * Returns EXPAND_NOTHING if no match found.
481 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200482 int
483cmdcomplete_str_to_type(char_u *complete_str)
484{
485 int i;
486
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200487 if (STRNCMP(complete_str, "custom,", 7) == 0)
488 return EXPAND_USER_DEFINED;
489 if (STRNCMP(complete_str, "customlist,", 11) == 0)
490 return EXPAND_USER_LIST;
491
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200492 for (i = 0; command_complete[i].expand != 0; ++i)
493 if (STRCMP(complete_str, command_complete[i].name) == 0)
494 return command_complete[i].expand;
495
496 return EXPAND_NOTHING;
497}
Dominique Pelle748b3082022-01-08 12:41:16 +0000498#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200499
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200500/*
501 * List user commands starting with "name[name_len]".
502 */
503 static void
504uc_list(char_u *name, size_t name_len)
505{
506 int i, j;
507 int found = FALSE;
508 ucmd_T *cmd;
509 int len;
510 int over;
511 long a;
512 garray_T *gap;
513
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000514 // don't allow for adding or removing user commands here
515 ++ucmd_locked;
516
Bram Moolenaare38eab22019-12-05 21:50:01 +0100517 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000518 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200519 for (;;)
520 {
521 for (i = 0; i < gap->ga_len; ++i)
522 {
523 cmd = USER_CMD_GA(gap, i);
524 a = (long)cmd->uc_argt;
525
526 // Skip commands which don't match the requested prefix and
527 // commands filtered out.
528 if (STRNCMP(name, cmd->uc_name, name_len) != 0
529 || message_filtered(cmd->uc_name))
530 continue;
531
532 // Put out the title first time
533 if (!found)
534 msg_puts_title(_("\n Name Args Address Complete Definition"));
535 found = TRUE;
536 msg_putchar('\n');
537 if (got_int)
538 break;
539
540 // Special cases
541 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200542 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200543 {
544 msg_putchar('!');
545 --len;
546 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200547 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200548 {
549 msg_putchar('"');
550 --len;
551 }
552 if (gap != &ucmds)
553 {
554 msg_putchar('b');
555 --len;
556 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200557 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200558 {
559 msg_putchar('|');
560 --len;
561 }
562 while (len-- > 0)
563 msg_putchar(' ');
564
565 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
566 len = (int)STRLEN(cmd->uc_name) + 4;
567
568 do {
569 msg_putchar(' ');
570 ++len;
571 } while (len < 22);
572
573 // "over" is how much longer the name is than the column width for
574 // the name, we'll try to align what comes after.
575 over = len - 22;
576 len = 0;
577
578 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200579 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200580 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200581 case 0: IObuff[len++] = '0'; break;
582 case (EX_EXTRA): IObuff[len++] = '*'; break;
583 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
584 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
585 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200586 }
587
588 do {
589 IObuff[len++] = ' ';
590 } while (len < 5 - over);
591
592 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200593 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200594 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200595 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200596 {
597 // -count=N
598 sprintf((char *)IObuff + len, "%ldc", cmd->uc_def);
599 len += (int)STRLEN(IObuff + len);
600 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200601 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200602 IObuff[len++] = '%';
603 else if (cmd->uc_def >= 0)
604 {
605 // -range=N
606 sprintf((char *)IObuff + len, "%ld", cmd->uc_def);
607 len += (int)STRLEN(IObuff + len);
608 }
609 else
610 IObuff[len++] = '.';
611 }
612
613 do {
614 IObuff[len++] = ' ';
615 } while (len < 8 - over);
616
617 // Address Type
Bram Moolenaarb7316892019-05-01 18:08:42 +0200618 for (j = 0; addr_type_complete[j].expand != ADDR_NONE; ++j)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200619 if (addr_type_complete[j].expand != ADDR_LINES
620 && addr_type_complete[j].expand == cmd->uc_addr_type)
621 {
622 STRCPY(IObuff + len, addr_type_complete[j].shortname);
623 len += (int)STRLEN(IObuff + len);
624 break;
625 }
626
627 do {
628 IObuff[len++] = ' ';
629 } while (len < 13 - over);
630
631 // Completion
632 for (j = 0; command_complete[j].expand != 0; ++j)
633 if (command_complete[j].expand == cmd->uc_compl)
634 {
635 STRCPY(IObuff + len, command_complete[j].name);
636 len += (int)STRLEN(IObuff + len);
Bram Moolenaar78f60322022-01-17 22:16:33 +0000637#ifdef FEAT_EVAL
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000638 if (p_verbose > 0 && cmd->uc_compl_arg != NULL
639 && STRLEN(cmd->uc_compl_arg) < 200)
640 {
641 IObuff[len] = ',';
642 STRCPY(IObuff + len + 1, cmd->uc_compl_arg);
643 len += (int)STRLEN(IObuff + len);
644 }
Bram Moolenaar78f60322022-01-17 22:16:33 +0000645#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200646 break;
647 }
648
649 do {
650 IObuff[len++] = ' ';
651 } while (len < 25 - over);
652
653 IObuff[len] = '\0';
654 msg_outtrans(IObuff);
655
656 msg_outtrans_special(cmd->uc_rep, FALSE,
657 name_len == 0 ? Columns - 47 : 0);
658#ifdef FEAT_EVAL
659 if (p_verbose > 0)
660 last_set_msg(cmd->uc_script_ctx);
661#endif
662 out_flush();
663 ui_breakcheck();
664 if (got_int)
665 break;
666 }
667 if (gap == &ucmds || i < gap->ga_len)
668 break;
669 gap = &ucmds;
670 }
671
672 if (!found)
673 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000674
675 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200676}
677
678 char *
679uc_fun_cmd(void)
680{
681 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
682 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
683 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
684 0xb9, 0x7f, 0};
685 int i;
686
687 for (i = 0; fcmd[i]; ++i)
688 IObuff[i] = fcmd[i] - 0x40;
689 IObuff[i] = 0;
690 return (char *)IObuff;
691}
692
693/*
694 * Parse address type argument
695 */
696 static int
697parse_addr_type_arg(
698 char_u *value,
699 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200700 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200701{
702 int i, a, b;
703
Bram Moolenaarb7316892019-05-01 18:08:42 +0200704 for (i = 0; addr_type_complete[i].expand != ADDR_NONE; ++i)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200705 {
706 a = (int)STRLEN(addr_type_complete[i].name) == vallen;
707 b = STRNCMP(value, addr_type_complete[i].name, vallen) == 0;
708 if (a && b)
709 {
710 *addr_type_arg = addr_type_complete[i].expand;
711 break;
712 }
713 }
714
Bram Moolenaarb7316892019-05-01 18:08:42 +0200715 if (addr_type_complete[i].expand == ADDR_NONE)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200716 {
717 char_u *err = value;
718
719 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
720 ;
721 err[i] = NUL;
Bram Moolenaar11de43d2022-01-06 21:41:11 +0000722 semsg(_(e_invalid_address_type_value_str), err);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200723 return FAIL;
724 }
725
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200726 return OK;
727}
728
729/*
730 * Parse a completion argument "value[vallen]".
731 * The detected completion goes in "*complp", argument type in "*argt".
732 * When there is an argument, for function and user defined completion, it's
733 * copied to allocated memory and stored in "*compl_arg".
734 * Returns FAIL if something is wrong.
735 */
736 int
737parse_compl_arg(
738 char_u *value,
739 int vallen,
740 int *complp,
741 long *argt,
742 char_u **compl_arg UNUSED)
743{
744 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200745# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746 size_t arglen = 0;
747# endif
748 int i;
749 int valend = vallen;
750
751 // Look for any argument part - which is the part after any ','
752 for (i = 0; i < vallen; ++i)
753 {
754 if (value[i] == ',')
755 {
756 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200757# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200758 arglen = vallen - i - 1;
759# endif
760 valend = i;
761 break;
762 }
763 }
764
765 for (i = 0; command_complete[i].expand != 0; ++i)
766 {
767 if ((int)STRLEN(command_complete[i].name) == valend
768 && STRNCMP(value, command_complete[i].name, valend) == 0)
769 {
770 *complp = command_complete[i].expand;
771 if (command_complete[i].expand == EXPAND_BUFFERS)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200772 *argt |= EX_BUFNAME;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200773 else if (command_complete[i].expand == EXPAND_DIRECTORIES
774 || command_complete[i].expand == EXPAND_FILES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200775 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776 break;
777 }
778 }
779
780 if (command_complete[i].expand == 0)
781 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000782 semsg(_(e_invalid_complete_value_str), value);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200783 return FAIL;
784 }
785
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200786# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200787 if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
788 && arg != NULL)
789# else
790 if (arg != NULL)
791# endif
792 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000793 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200794 return FAIL;
795 }
796
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200797# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200798 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
799 && arg == NULL)
800 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000801 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200802 return FAIL;
803 }
804
805 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200806 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200807# endif
808 return OK;
809}
810
811/*
812 * Scan attributes in the ":command" command.
813 * Return FAIL when something is wrong.
814 */
815 static int
816uc_scan_attr(
817 char_u *attr,
818 size_t len,
819 long *argt,
820 long *def,
821 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200822 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200823 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200824 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200825{
826 char_u *p;
827
828 if (len == 0)
829 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000830 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200831 return FAIL;
832 }
833
834 // First, try the simple attributes (no arguments)
835 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200836 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200837 else if (STRNICMP(attr, "buffer", len) == 0)
838 *flags |= UC_BUFFER;
839 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200840 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000841 else if (STRNICMP(attr, "keepscript", len) == 0)
842 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200843 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200844 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200845 else
846 {
847 int i;
848 char_u *val = NULL;
849 size_t vallen = 0;
850 size_t attrlen = len;
851
852 // Look for the attribute name - which is the part before any '='
853 for (i = 0; i < (int)len; ++i)
854 {
855 if (attr[i] == '=')
856 {
857 val = &attr[i + 1];
858 vallen = len - i - 1;
859 attrlen = i;
860 break;
861 }
862 }
863
864 if (STRNICMP(attr, "nargs", attrlen) == 0)
865 {
866 if (vallen == 1)
867 {
868 if (*val == '0')
869 // Do nothing - this is the default
870 ;
871 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200872 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200873 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200874 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200875 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200876 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200878 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200879 else
880 goto wrong_nargs;
881 }
882 else
883 {
884wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000885 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200886 return FAIL;
887 }
888 }
889 else if (STRNICMP(attr, "range", attrlen) == 0)
890 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200891 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200892 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200893 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200894 else if (val != NULL)
895 {
896 p = val;
897 if (*def >= 0)
898 {
899two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000900 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200901 return FAIL;
902 }
903
904 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200905 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200906
907 if (p != val + vallen || vallen == 0)
908 {
909invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000910 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200911 return FAIL;
912 }
913 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200914 // default for -range is using buffer lines
915 if (*addr_type_arg == ADDR_NONE)
916 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200917 }
918 else if (STRNICMP(attr, "count", attrlen) == 0)
919 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200920 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200921 // default for -count is using any number
922 if (*addr_type_arg == ADDR_NONE)
923 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200924
925 if (val != NULL)
926 {
927 p = val;
928 if (*def >= 0)
929 goto two_count;
930
931 *def = getdigits(&p);
932
933 if (p != val + vallen)
934 goto invalid_count;
935 }
936
937 if (*def < 0)
938 *def = 0;
939 }
940 else if (STRNICMP(attr, "complete", attrlen) == 0)
941 {
942 if (val == NULL)
943 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000944 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200945 return FAIL;
946 }
947
Bram Moolenaar52111f82019-04-29 21:30:45 +0200948 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200949 == FAIL)
950 return FAIL;
951 }
952 else if (STRNICMP(attr, "addr", attrlen) == 0)
953 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200954 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200955 if (val == NULL)
956 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000957 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200958 return FAIL;
959 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200960 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200961 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +0200962 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200963 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200964 }
965 else
966 {
967 char_u ch = attr[len];
968 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +0000969 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200970 attr[len] = ch;
971 return FAIL;
972 }
973 }
974
975 return OK;
976}
977
978/*
979 * Add a user command to the list or replace an existing one.
980 */
981 static int
982uc_add_command(
983 char_u *name,
984 size_t name_len,
985 char_u *rep,
986 long argt,
987 long def,
988 int flags,
989 int compl,
990 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200991 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200992 int force)
993{
994 ucmd_T *cmd = NULL;
995 char_u *p;
996 int i;
997 int cmp = 1;
998 char_u *rep_buf = NULL;
999 garray_T *gap;
1000
zeertzjq7e0bae02023-08-11 23:15:38 +02001001 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001002 if (rep_buf == NULL)
1003 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001004 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001005 rep_buf = vim_strsave(rep);
1006
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001007 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001008 if (rep_buf == NULL)
1009 return FAIL;
1010 }
1011
1012 // get address of growarray: global or in curbuf
1013 if (flags & UC_BUFFER)
1014 {
1015 gap = &curbuf->b_ucmds;
1016 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001017 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001018 }
1019 else
1020 gap = &ucmds;
1021
1022 // Search for the command in the already defined commands.
1023 for (i = 0; i < gap->ga_len; ++i)
1024 {
1025 size_t len;
1026
1027 cmd = USER_CMD_GA(gap, i);
1028 len = STRLEN(cmd->uc_name);
1029 cmp = STRNCMP(name, cmd->uc_name, name_len);
1030 if (cmp == 0)
1031 {
1032 if (name_len < len)
1033 cmp = -1;
1034 else if (name_len > len)
1035 cmp = 1;
1036 }
1037
1038 if (cmp == 0)
1039 {
1040 // Command can be replaced with "command!" and when sourcing the
1041 // same script again, but only once.
1042 if (!force
1043#ifdef FEAT_EVAL
1044 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1045 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1046#endif
1047 )
1048 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001049 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001050 name);
1051 goto fail;
1052 }
1053
1054 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001055#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001056 VIM_CLEAR(cmd->uc_compl_arg);
1057#endif
1058 break;
1059 }
1060
1061 // Stop as soon as we pass the name to add
1062 if (cmp < 0)
1063 break;
1064 }
1065
1066 // Extend the array unless we're replacing an existing command
1067 if (cmp != 0)
1068 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001069 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001070 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001071 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001072 goto fail;
1073
1074 cmd = USER_CMD_GA(gap, i);
1075 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1076
1077 ++gap->ga_len;
1078
1079 cmd->uc_name = p;
1080 }
1081
1082 cmd->uc_rep = rep_buf;
1083 cmd->uc_argt = argt;
1084 cmd->uc_def = def;
1085 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001086 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001087 if (flags & UC_VIM9)
1088 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001089 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001090#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001091 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001092 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001093#endif
1094 cmd->uc_addr_type = addr_type;
1095
1096 return OK;
1097
1098fail:
1099 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001100#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001101 vim_free(compl_arg);
1102#endif
1103 return FAIL;
1104}
1105
1106/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001107 * If "p" starts with "{" then read a block of commands until "}".
1108 * Used for ":command" and ":autocmd".
1109 */
1110 char_u *
1111may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1112{
1113 char_u *retp = p;
1114
1115 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001116 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001117 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001118 garray_T ga;
1119 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001120
1121 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001122 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001123 return retp;
1124
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001125 // If the argument ends in "}" it must have been concatenated already
1126 // for ISN_EXEC.
1127 if (p[STRLEN(p) - 1] != '}')
1128 // Read lines between '{' and '}'. Does not support nesting or
1129 // here-doc constructs.
1130 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001131 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001132 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001133 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001134 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1135 {
1136 emsg(_(e_missing_rcurly));
1137 break;
1138 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001139 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001140 break;
1141 if (*skipwhite(line) == '}')
1142 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001143 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001144 vim_free(line);
1145 retp = *tofree = ga_concat_strings(&ga, "\n");
1146 ga_clear_strings(&ga);
1147 *flags |= UC_VIM9;
1148 }
1149 return retp;
1150}
1151
1152/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001153 * ":command ..." implementation
1154 */
1155 void
1156ex_command(exarg_T *eap)
1157{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001158 char_u *name;
1159 char_u *end;
1160 char_u *p;
1161 long argt = 0;
1162 long def = -1;
1163 int flags = 0;
1164 int compl = EXPAND_NOTHING;
1165 char_u *compl_arg = NULL;
1166 cmd_addr_T addr_type_arg = ADDR_NONE;
1167 int has_attr = (eap->arg[0] == '-');
1168 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001169
1170 p = eap->arg;
1171
1172 // Check for attributes
1173 while (*p == '-')
1174 {
1175 ++p;
1176 end = skiptowhite(p);
1177 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1178 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001179 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001180 p = skipwhite(end);
1181 }
1182
1183 // Get the name (if any) and skip to the following argument
1184 name = p;
1185 if (ASCII_ISALPHA(*p))
1186 while (ASCII_ISALNUM(*p))
1187 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001188 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001189 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001190 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001191 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001192 }
1193 end = p;
1194 name_len = (int)(end - name);
1195
1196 // If there is nothing after the name, and no attributes were specified,
1197 // we are listing commands
1198 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001199 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001200 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001201 uc_list(name, end - name);
zeertzjq33e54302022-12-19 16:49:27 +00001202 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001203 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001204 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001205 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001206 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001207 else if ((name_len == 1 && *name == 'X')
1208 || (name_len <= 4
1209 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001210 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001211 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001212 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001213 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001214 {
1215 // Some plugins rely on silently ignoring the mistake, only make this
1216 // an error in Vim9 script.
1217 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001218 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001219 else
1220 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001221 (char_u *)_(e_complete_used_without_allowing_arguments),
1222 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001223 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001224 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001225 {
1226 char_u *tofree = NULL;
1227
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001228 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001229
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001230 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1231 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001232 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001233
1234 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001235 }
zeertzjq33e54302022-12-19 16:49:27 +00001236
1237theend:
1238 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001239}
1240
1241/*
1242 * ":comclear" implementation
1243 * Clear all user commands, global and for current buffer.
1244 */
1245 void
1246ex_comclear(exarg_T *eap UNUSED)
1247{
1248 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001249 if (curbuf != NULL)
1250 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001251}
1252
1253/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001254 * If ucmd_locked is set give an error and return TRUE.
1255 * Otherwise return FALSE.
1256 */
1257 static int
1258is_ucmd_locked(void)
1259{
1260 if (ucmd_locked > 0)
1261 {
1262 emsg(_(e_cannot_change_user_commands_while_listing));
1263 return TRUE;
1264 }
1265 return FALSE;
1266}
1267
1268/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001269 * Clear all user commands for "gap".
1270 */
1271 void
1272uc_clear(garray_T *gap)
1273{
1274 int i;
1275 ucmd_T *cmd;
1276
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001277 if (is_ucmd_locked())
1278 return;
1279
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001280 for (i = 0; i < gap->ga_len; ++i)
1281 {
1282 cmd = USER_CMD_GA(gap, i);
1283 vim_free(cmd->uc_name);
1284 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001285# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001286 vim_free(cmd->uc_compl_arg);
1287# endif
1288 }
1289 ga_clear(gap);
1290}
1291
1292/*
1293 * ":delcommand" implementation
1294 */
1295 void
1296ex_delcommand(exarg_T *eap)
1297{
1298 int i = 0;
1299 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001300 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001301 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001302 char_u *arg = eap->arg;
1303 int buffer_only = FALSE;
1304
1305 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1306 {
1307 buffer_only = TRUE;
1308 arg = skipwhite(arg + 7);
1309 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001310
1311 gap = &curbuf->b_ucmds;
1312 for (;;)
1313 {
1314 for (i = 0; i < gap->ga_len; ++i)
1315 {
1316 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001317 res = STRCMP(arg, cmd->uc_name);
1318 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001319 break;
1320 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001321 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001322 break;
1323 gap = &ucmds;
1324 }
1325
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001326 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001327 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001328 semsg(_(buffer_only
1329 ? e_no_such_user_defined_command_in_current_buffer_str
1330 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001331 return;
1332 }
1333
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001334 if (is_ucmd_locked())
1335 return;
1336
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001337 vim_free(cmd->uc_name);
1338 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001339# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001340 vim_free(cmd->uc_compl_arg);
1341# endif
1342
1343 --gap->ga_len;
1344
1345 if (i < gap->ga_len)
1346 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1347}
1348
1349/*
1350 * Split and quote args for <f-args>.
1351 */
1352 static char_u *
1353uc_split_args(char_u *arg, size_t *lenp)
1354{
1355 char_u *buf;
1356 char_u *p;
1357 char_u *q;
1358 int len;
1359
1360 // Precalculate length
1361 p = arg;
1362 len = 2; // Initial and final quotes
1363
1364 while (*p)
1365 {
1366 if (p[0] == '\\' && p[1] == '\\')
1367 {
1368 len += 2;
1369 p += 2;
1370 }
1371 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1372 {
1373 len += 1;
1374 p += 2;
1375 }
1376 else if (*p == '\\' || *p == '"')
1377 {
1378 len += 2;
1379 p += 1;
1380 }
1381 else if (VIM_ISWHITE(*p))
1382 {
1383 p = skipwhite(p);
1384 if (*p == NUL)
1385 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001386 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001387 }
1388 else
1389 {
1390 int charlen = (*mb_ptr2len)(p);
1391
1392 len += charlen;
1393 p += charlen;
1394 }
1395 }
1396
1397 buf = alloc(len + 1);
1398 if (buf == NULL)
1399 {
1400 *lenp = 0;
1401 return buf;
1402 }
1403
1404 p = arg;
1405 q = buf;
1406 *q++ = '"';
1407 while (*p)
1408 {
1409 if (p[0] == '\\' && p[1] == '\\')
1410 {
1411 *q++ = '\\';
1412 *q++ = '\\';
1413 p += 2;
1414 }
1415 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1416 {
1417 *q++ = p[1];
1418 p += 2;
1419 }
1420 else if (*p == '\\' || *p == '"')
1421 {
1422 *q++ = '\\';
1423 *q++ = *p++;
1424 }
1425 else if (VIM_ISWHITE(*p))
1426 {
1427 p = skipwhite(p);
1428 if (*p == NUL)
1429 break;
1430 *q++ = '"';
1431 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001432 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001433 *q++ = '"';
1434 }
1435 else
1436 {
1437 MB_COPY_CHAR(p, q);
1438 }
1439 }
1440 *q++ = '"';
1441 *q = 0;
1442
1443 *lenp = len;
1444 return buf;
1445}
1446
1447 static size_t
1448add_cmd_modifier(char_u *buf, char *mod_str, int *multi_mods)
1449{
1450 size_t result;
1451
1452 result = STRLEN(mod_str);
1453 if (*multi_mods)
1454 result += 1;
1455 if (buf != NULL)
1456 {
1457 if (*multi_mods)
1458 STRCAT(buf, " ");
1459 STRCAT(buf, mod_str);
1460 }
1461
1462 *multi_mods = 1;
1463
1464 return result;
1465}
1466
1467/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001468 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001469 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001470 */
1471 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001472add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001473{
1474 size_t result = 0;
1475
1476 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001477 if (cmod->cmod_split & WSP_ABOVE)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001478 result += add_cmd_modifier(buf, "aboveleft", multi_mods);
1479 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001480 if (cmod->cmod_split & WSP_BELOW)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001481 result += add_cmd_modifier(buf, "belowright", multi_mods);
1482 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001483 if (cmod->cmod_split & WSP_BOT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001484 result += add_cmd_modifier(buf, "botright", multi_mods);
1485
1486 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001487 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001488 {
1489 int tabnr = cmod->cmod_tab - 1;
1490
1491 if (tabnr == tabpage_index(curtab))
1492 {
1493 // For compatibility, don't add a tabpage number if it is the same
1494 // as the default number for :tab.
1495 result += add_cmd_modifier(buf, "tab", multi_mods);
1496 }
1497 else
1498 {
1499 char tab_buf[NUMBUFLEN + 3];
1500
1501 sprintf(tab_buf, "%dtab", tabnr);
1502 result += add_cmd_modifier(buf, tab_buf, multi_mods);
1503 }
1504 }
1505
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001506 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001507 if (cmod->cmod_split & WSP_TOP)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001508 result += add_cmd_modifier(buf, "topleft", multi_mods);
1509 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001510 if (cmod->cmod_split & WSP_VERT)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001511 result += add_cmd_modifier(buf, "vertical", multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001512 // :horizontal
1513 if (cmod->cmod_split & WSP_HOR)
1514 result += add_cmd_modifier(buf, "horizontal", multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001515 return result;
1516}
1517
1518/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001519 * Generate text for the "cmod" command modifiers.
1520 * If "buf" is NULL just return the length.
1521 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001522 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001523produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1524{
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001525 size_t result = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001526 int multi_mods = 0;
1527 int i;
1528 typedef struct {
1529 int flag;
1530 char *name;
1531 } mod_entry_T;
1532 static mod_entry_T mod_entries[] = {
1533#ifdef FEAT_BROWSE_CMD
1534 {CMOD_BROWSE, "browse"},
1535#endif
1536#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
1537 {CMOD_CONFIRM, "confirm"},
1538#endif
1539 {CMOD_HIDE, "hide"},
1540 {CMOD_KEEPALT, "keepalt"},
1541 {CMOD_KEEPJUMPS, "keepjumps"},
1542 {CMOD_KEEPMARKS, "keepmarks"},
1543 {CMOD_KEEPPATTERNS, "keeppatterns"},
1544 {CMOD_LOCKMARKS, "lockmarks"},
1545 {CMOD_NOSWAPFILE, "noswapfile"},
1546 {CMOD_UNSILENT, "unsilent"},
1547 {CMOD_NOAUTOCMD, "noautocmd"},
1548#ifdef HAVE_SANDBOX
1549 {CMOD_SANDBOX, "sandbox"},
1550#endif
Bram Moolenaarb579f6e2021-12-04 11:57:00 +00001551 {CMOD_LEGACY, "legacy"},
Bram Moolenaar02194d22020-10-24 23:08:38 +02001552 {0, NULL}
1553 };
1554
1555 result = quote ? 2 : 0;
1556 if (buf != NULL)
1557 {
1558 if (quote)
1559 *buf++ = '"';
1560 *buf = '\0';
1561 }
1562
1563 // the modifiers that are simple flags
1564 for (i = 0; mod_entries[i].name != NULL; ++i)
1565 if (cmod->cmod_flags & mod_entries[i].flag)
1566 result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods);
1567
1568 // :silent
1569 if (cmod->cmod_flags & CMOD_SILENT)
1570 result += add_cmd_modifier(buf,
1571 (cmod->cmod_flags & CMOD_ERRSILENT) ? "silent!"
1572 : "silent", &multi_mods);
1573 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001574 if (cmod->cmod_verbose > 0)
1575 {
1576 int verbose_value = cmod->cmod_verbose - 1;
1577
1578 if (verbose_value == 1)
1579 result += add_cmd_modifier(buf, "verbose", &multi_mods);
1580 else
1581 {
1582 char verbose_buf[NUMBUFLEN];
1583
1584 sprintf(verbose_buf, "%dverbose", verbose_value);
1585 result += add_cmd_modifier(buf, verbose_buf, &multi_mods);
1586 }
1587 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001588 // flags from cmod->cmod_split
dundargocc57b5bc2022-11-02 13:30:51 +00001589 result += add_win_cmd_modifiers(buf, cmod, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001590
Bram Moolenaar02194d22020-10-24 23:08:38 +02001591 if (quote && buf != NULL)
1592 {
1593 buf += result - 2;
1594 *buf = '"';
1595 }
1596 return result;
1597}
1598
1599/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001600 * Check for a <> code in a user command.
1601 * "code" points to the '<'. "len" the length of the <> (inclusive).
1602 * "buf" is where the result is to be added.
1603 * "split_buf" points to a buffer used for splitting, caller should free it.
1604 * "split_len" is the length of what "split_buf" contains.
1605 * Returns the length of the replacement, which has been added to "buf".
1606 * Returns -1 if there was no match, and only the "<" has been copied.
1607 */
1608 static size_t
1609uc_check_code(
1610 char_u *code,
1611 size_t len,
1612 char_u *buf,
1613 ucmd_T *cmd, // the user command we're expanding
1614 exarg_T *eap, // ex arguments
1615 char_u **split_buf,
1616 size_t *split_len)
1617{
1618 size_t result = 0;
1619 char_u *p = code + 1;
1620 size_t l = len - 2;
1621 int quote = 0;
1622 enum {
1623 ct_ARGS,
1624 ct_BANG,
1625 ct_COUNT,
1626 ct_LINE1,
1627 ct_LINE2,
1628 ct_RANGE,
1629 ct_MODS,
1630 ct_REGISTER,
1631 ct_LT,
1632 ct_NONE
1633 } type = ct_NONE;
1634
1635 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1636 {
1637 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1638 p += 2;
1639 l -= 2;
1640 }
1641
1642 ++l;
1643 if (l <= 1)
1644 type = ct_NONE;
1645 else if (STRNICMP(p, "args>", l) == 0)
1646 type = ct_ARGS;
1647 else if (STRNICMP(p, "bang>", l) == 0)
1648 type = ct_BANG;
1649 else if (STRNICMP(p, "count>", l) == 0)
1650 type = ct_COUNT;
1651 else if (STRNICMP(p, "line1>", l) == 0)
1652 type = ct_LINE1;
1653 else if (STRNICMP(p, "line2>", l) == 0)
1654 type = ct_LINE2;
1655 else if (STRNICMP(p, "range>", l) == 0)
1656 type = ct_RANGE;
1657 else if (STRNICMP(p, "lt>", l) == 0)
1658 type = ct_LT;
1659 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1660 type = ct_REGISTER;
1661 else if (STRNICMP(p, "mods>", l) == 0)
1662 type = ct_MODS;
1663
1664 switch (type)
1665 {
1666 case ct_ARGS:
1667 // Simple case first
1668 if (*eap->arg == NUL)
1669 {
1670 if (quote == 1)
1671 {
1672 result = 2;
1673 if (buf != NULL)
1674 STRCPY(buf, "''");
1675 }
1676 else
1677 result = 0;
1678 break;
1679 }
1680
1681 // When specified there is a single argument don't split it.
1682 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001683 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001684 quote = 1;
1685
1686 switch (quote)
1687 {
1688 case 0: // No quoting, no splitting
1689 result = STRLEN(eap->arg);
1690 if (buf != NULL)
1691 STRCPY(buf, eap->arg);
1692 break;
1693 case 1: // Quote, but don't split
1694 result = STRLEN(eap->arg) + 2;
1695 for (p = eap->arg; *p; ++p)
1696 {
1697 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1698 // DBCS can contain \ in a trail byte, skip the
1699 // double-byte character.
1700 ++p;
1701 else
1702 if (*p == '\\' || *p == '"')
1703 ++result;
1704 }
1705
1706 if (buf != NULL)
1707 {
1708 *buf++ = '"';
1709 for (p = eap->arg; *p; ++p)
1710 {
1711 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1712 // DBCS can contain \ in a trail byte, copy the
1713 // double-byte character to avoid escaping.
1714 *buf++ = *p++;
1715 else
1716 if (*p == '\\' || *p == '"')
1717 *buf++ = '\\';
1718 *buf++ = *p;
1719 }
1720 *buf = '"';
1721 }
1722
1723 break;
1724 case 2: // Quote and split (<f-args>)
1725 // This is hard, so only do it once, and cache the result
1726 if (*split_buf == NULL)
1727 *split_buf = uc_split_args(eap->arg, split_len);
1728
1729 result = *split_len;
1730 if (buf != NULL && result != 0)
1731 STRCPY(buf, *split_buf);
1732
1733 break;
1734 }
1735 break;
1736
1737 case ct_BANG:
1738 result = eap->forceit ? 1 : 0;
1739 if (quote)
1740 result += 2;
1741 if (buf != NULL)
1742 {
1743 if (quote)
1744 *buf++ = '"';
1745 if (eap->forceit)
1746 *buf++ = '!';
1747 if (quote)
1748 *buf = '"';
1749 }
1750 break;
1751
1752 case ct_LINE1:
1753 case ct_LINE2:
1754 case ct_RANGE:
1755 case ct_COUNT:
1756 {
1757 char num_buf[20];
1758 long num = (type == ct_LINE1) ? eap->line1 :
1759 (type == ct_LINE2) ? eap->line2 :
1760 (type == ct_RANGE) ? eap->addr_count :
1761 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1762 size_t num_len;
1763
1764 sprintf(num_buf, "%ld", num);
1765 num_len = STRLEN(num_buf);
1766 result = num_len;
1767
1768 if (quote)
1769 result += 2;
1770
1771 if (buf != NULL)
1772 {
1773 if (quote)
1774 *buf++ = '"';
1775 STRCPY(buf, num_buf);
1776 buf += num_len;
1777 if (quote)
1778 *buf = '"';
1779 }
1780
1781 break;
1782 }
1783
1784 case ct_MODS:
1785 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001786 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001787 break;
1788 }
1789
1790 case ct_REGISTER:
1791 result = eap->regname ? 1 : 0;
1792 if (quote)
1793 result += 2;
1794 if (buf != NULL)
1795 {
1796 if (quote)
1797 *buf++ = '\'';
1798 if (eap->regname)
1799 *buf++ = eap->regname;
1800 if (quote)
1801 *buf = '\'';
1802 }
1803 break;
1804
1805 case ct_LT:
1806 result = 1;
1807 if (buf != NULL)
1808 *buf = '<';
1809 break;
1810
1811 default:
1812 // Not recognized: just copy the '<' and return -1.
1813 result = (size_t)-1;
1814 if (buf != NULL)
1815 *buf = '<';
1816 break;
1817 }
1818
1819 return result;
1820}
1821
1822/*
1823 * Execute a user defined command.
1824 */
1825 void
1826do_ucmd(exarg_T *eap)
1827{
1828 char_u *buf;
1829 char_u *p;
1830 char_u *q;
1831
1832 char_u *start;
1833 char_u *end = NULL;
1834 char_u *ksp;
1835 size_t len, totlen;
1836
1837 size_t split_len = 0;
1838 char_u *split_buf = NULL;
1839 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001840 sctx_T save_current_sctx;
1841 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001842#ifdef FEAT_EVAL
1843 int restore_script_version = 0;
1844#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001845
1846 if (eap->cmdidx == CMD_USER)
1847 cmd = USER_CMD(eap->useridx);
1848 else
zeertzjqb444ee72023-02-20 15:25:13 +00001849 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001850
1851 /*
1852 * Replace <> in the command by the arguments.
1853 * First round: "buf" is NULL, compute length, allocate "buf".
1854 * Second round: copy result into "buf".
1855 */
1856 buf = NULL;
1857 for (;;)
1858 {
1859 p = cmd->uc_rep; // source
1860 q = buf; // destination
1861 totlen = 0;
1862
1863 for (;;)
1864 {
1865 start = vim_strchr(p, '<');
1866 if (start != NULL)
1867 end = vim_strchr(start + 1, '>');
1868 if (buf != NULL)
1869 {
1870 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1871 ;
1872 if (*ksp == K_SPECIAL
1873 && (start == NULL || ksp < start || end == NULL)
1874 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1875# ifdef FEAT_GUI
1876 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1877# endif
1878 ))
1879 {
1880 // K_SPECIAL has been put in the buffer as K_SPECIAL
1881 // KS_SPECIAL KE_FILLER, like for mappings, but
1882 // do_cmdline() doesn't handle that, so convert it back.
1883 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1884 len = ksp - p;
1885 if (len > 0)
1886 {
1887 mch_memmove(q, p, len);
1888 q += len;
1889 }
1890 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1891 p = ksp + 3;
1892 continue;
1893 }
1894 }
1895
1896 // break if no <item> is found
1897 if (start == NULL || end == NULL)
1898 break;
1899
1900 // Include the '>'
1901 ++end;
1902
1903 // Take everything up to the '<'
1904 len = start - p;
1905 if (buf == NULL)
1906 totlen += len;
1907 else
1908 {
1909 mch_memmove(q, p, len);
1910 q += len;
1911 }
1912
1913 len = uc_check_code(start, end - start, q, cmd, eap,
1914 &split_buf, &split_len);
1915 if (len == (size_t)-1)
1916 {
1917 // no match, continue after '<'
1918 p = start + 1;
1919 len = 1;
1920 }
1921 else
1922 p = end;
1923 if (buf == NULL)
1924 totlen += len;
1925 else
1926 q += len;
1927 }
1928 if (buf != NULL) // second time here, finished
1929 {
1930 STRCPY(q, p);
1931 break;
1932 }
1933
1934 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02001935 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001936 if (buf == NULL)
1937 {
1938 vim_free(split_buf);
1939 return;
1940 }
1941 }
1942
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001943 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
1944 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001945 restore_current_sctx = TRUE;
1946 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001947 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001948#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001949 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001950 if (cmd->uc_flags & UC_VIM9)
1951 {
1952 // In a {} block variables use Vim9 script rules, even in a legacy
1953 // script.
1954 restore_script_version =
1955 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
1956 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
1957 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001958#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001959 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001960
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001961 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001962 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001963
1964 // Careful: Do not use "cmd" here, it may have become invalid if a user
1965 // command was added.
1966 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001967 {
1968#ifdef FEAT_EVAL
1969 if (restore_script_version != 0)
1970 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
1971 restore_script_version;
1972#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00001973 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001974 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001975 vim_free(buf);
1976 vim_free(split_buf);
1977}