blob: 585ced3e77b2bd2ee61f3de9b69ca5dcd5bc7f21 [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
John Marriott9e795852024-08-20 20:57:23 +020019 size_t uc_namelen; // The length of the command name (excluding the NUL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +020020 long_u uc_argt; // The argument type
21 char_u *uc_rep; // The command's replacement string
22 long uc_def; // The default value for a range/count
23 int uc_compl; // completion type
Bram Moolenaarb7316892019-05-01 18:08:42 +020024 cmd_addr_T uc_addr_type; // The command's address type
Bram Moolenaarac9fb182019-04-27 13:04:13 +020025 sctx_T uc_script_ctx; // SCTX where the command was defined
Bram Moolenaar98b7fe72022-03-23 21:36:27 +000026 int uc_flags; // some UC_ flags
Bram Moolenaar9b8d6222020-12-28 18:26:00 +010027# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +020028 char_u *uc_compl_arg; // completion argument if any
Bram Moolenaarac9fb182019-04-27 13:04:13 +020029# endif
30} ucmd_T;
31
32// List of all user commands.
33static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL};
34
Bram Moolenaarcf2594f2022-11-13 23:30:06 +000035// When non-zero it is not allowed to add or remove user commands
36static int ucmd_locked = 0;
37
Bram Moolenaarac9fb182019-04-27 13:04:13 +020038#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i])
39#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i])
40
41/*
42 * List of names for completion for ":command" with the EXPAND_ flag.
John Marriott9e795852024-08-20 20:57:23 +020043 * Must be alphabetical on the 'value' field for completion and because
44 * it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +020045 */
John Marriott9e795852024-08-20 20:57:23 +020046static keyvalue_T command_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +020047{
John Marriott9e795852024-08-20 20:57:23 +020048 KEYVALUE_ENTRY(EXPAND_ARGLIST, "arglist"),
49 KEYVALUE_ENTRY(EXPAND_AUGROUP, "augroup"),
50 KEYVALUE_ENTRY(EXPAND_BEHAVE, "behave"),
51#if defined(FEAT_EVAL)
52 KEYVALUE_ENTRY(EXPAND_BREAKPOINT, "breakpoint"),
53#endif
54 KEYVALUE_ENTRY(EXPAND_BUFFERS, "buffer"),
55 KEYVALUE_ENTRY(EXPAND_COLORS, "color"),
56 KEYVALUE_ENTRY(EXPAND_COMMANDS, "command"),
57 KEYVALUE_ENTRY(EXPAND_COMPILER, "compiler"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020058#if defined(FEAT_CSCOPE)
John Marriott9e795852024-08-20 20:57:23 +020059 KEYVALUE_ENTRY(EXPAND_CSCOPE, "cscope"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020060#endif
Bram Moolenaar0a52df52019-08-18 22:26:31 +020061#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020062 KEYVALUE_ENTRY(EXPAND_USER_DEFINED, "custom"),
63 KEYVALUE_ENTRY(EXPAND_USER_LIST, "customlist"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020064#endif
John Marriott9e795852024-08-20 20:57:23 +020065 KEYVALUE_ENTRY(EXPAND_DIFF_BUFFERS, "diff_buffer"),
66 KEYVALUE_ENTRY(EXPAND_DIRECTORIES, "dir"),
67 KEYVALUE_ENTRY(EXPAND_DIRS_IN_CDPATH, "dir_in_path"),
68 KEYVALUE_ENTRY(EXPAND_ENV_VARS, "environment"),
69 KEYVALUE_ENTRY(EXPAND_EVENTS, "event"),
70 KEYVALUE_ENTRY(EXPAND_EXPRESSION, "expression"),
71 KEYVALUE_ENTRY(EXPAND_FILES, "file"),
72 KEYVALUE_ENTRY(EXPAND_FILES_IN_PATH, "file_in_path"),
73 KEYVALUE_ENTRY(EXPAND_FILETYPE, "filetype"),
74 KEYVALUE_ENTRY(EXPAND_FUNCTIONS, "function"),
75 KEYVALUE_ENTRY(EXPAND_HELP, "help"),
76 KEYVALUE_ENTRY(EXPAND_HIGHLIGHT, "highlight"),
77 KEYVALUE_ENTRY(EXPAND_HISTORY, "history"),
Doug Kearns81642d92024-01-04 22:37:44 +010078#if defined(FEAT_KEYMAP)
John Marriott9e795852024-08-20 20:57:23 +020079 KEYVALUE_ENTRY(EXPAND_KEYMAP, "keymap"),
Doug Kearns81642d92024-01-04 22:37:44 +010080#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +020081#if defined(HAVE_LOCALE_H) || defined(X_LOCALE)
John Marriott9e795852024-08-20 20:57:23 +020082 KEYVALUE_ENTRY(EXPAND_LOCALES, "locale"),
Bram Moolenaarac9fb182019-04-27 13:04:13 +020083#endif
John Marriott9e795852024-08-20 20:57:23 +020084 KEYVALUE_ENTRY(EXPAND_MAPCLEAR, "mapclear"),
85 KEYVALUE_ENTRY(EXPAND_MAPPINGS, "mapping"),
86 KEYVALUE_ENTRY(EXPAND_MENUS, "menu"),
87 KEYVALUE_ENTRY(EXPAND_MESSAGES, "messages"),
88 KEYVALUE_ENTRY(EXPAND_SETTINGS, "option"),
89 KEYVALUE_ENTRY(EXPAND_PACKADD, "packadd"),
90 KEYVALUE_ENTRY(EXPAND_RUNTIME, "runtime"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000091#if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +020092 KEYVALUE_ENTRY(EXPAND_SCRIPTNAMES, "scriptnames"),
Bram Moolenaar6e2e2cc2022-03-14 19:24:46 +000093#endif
John Marriott9e795852024-08-20 20:57:23 +020094 KEYVALUE_ENTRY(EXPAND_SHELLCMD, "shellcmd"),
95#if defined(FEAT_SIGNS)
96 KEYVALUE_ENTRY(EXPAND_SIGN, "sign"),
97#endif
98 KEYVALUE_ENTRY(EXPAND_OWNSYNTAX, "syntax"),
99#if defined(FEAT_PROFILE)
100 KEYVALUE_ENTRY(EXPAND_SYNTIME, "syntime"),
101#endif
102 KEYVALUE_ENTRY(EXPAND_TAGS, "tag"),
103 KEYVALUE_ENTRY(EXPAND_TAGS_LISTFILES, "tag_listfiles"),
104 KEYVALUE_ENTRY(EXPAND_USER, "user"),
105 KEYVALUE_ENTRY(EXPAND_USER_VARS, "var")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200106};
107
John Marriott9e795852024-08-20 20:57:23 +0200108typedef struct
109{
110 cmd_addr_T key;
111 char *fullname;
112 size_t fullnamelen;
113 char *shortname;
114 size_t shortnamelen;
115} addrtype_T;
116
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200117/*
118 * List of names of address types. Must be alphabetical for completion.
John Marriott9e795852024-08-20 20:57:23 +0200119 * Must be sorted by the 'fullname' field because it is used by bsearch()!
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200120 */
John Marriott9e795852024-08-20 20:57:23 +0200121#define ADDRTYPE_ENTRY(k, fn, sn) \
122 {(k), (fn), STRLEN_LITERAL(fn), (sn), STRLEN_LITERAL(sn)}
123static addrtype_T addr_type_complete_tab[] =
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200124{
John Marriott9e795852024-08-20 20:57:23 +0200125 ADDRTYPE_ENTRY(ADDR_ARGUMENTS, "arguments", "arg"),
126 ADDRTYPE_ENTRY(ADDR_BUFFERS, "buffers", "buf"),
127 ADDRTYPE_ENTRY(ADDR_LINES, "lines", "line"),
128 ADDRTYPE_ENTRY(ADDR_LOADED_BUFFERS, "loaded_buffers", "load"),
129 ADDRTYPE_ENTRY(ADDR_OTHER, "other", "?"),
130 ADDRTYPE_ENTRY(ADDR_QUICKFIX, "quickfix", "qf"),
131 ADDRTYPE_ENTRY(ADDR_TABS, "tabs", "tab"),
132 ADDRTYPE_ENTRY(ADDR_WINDOWS, "windows", "win")
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200133};
134
John Marriott9e795852024-08-20 20:57:23 +0200135static int cmp_addr_type(const void *a, const void *b);
136
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200137/*
138 * Search for a user command that matches "eap->cmd".
139 * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx".
140 * Return a pointer to just after the command.
141 * Return NULL if there is no matching command.
142 */
143 char_u *
144find_ucmd(
145 exarg_T *eap,
Bram Moolenaar0023f822022-01-16 21:54:19 +0000146 char_u *p, // end of the command (possibly including count)
147 int *full, // set to TRUE for a full match
148 expand_T *xp, // used for completion, NULL otherwise
149 int *complp) // completion flags or NULL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200150{
151 int len = (int)(p - eap->cmd);
152 int j, k, matchlen = 0;
153 ucmd_T *uc;
154 int found = FALSE;
155 int possible = FALSE;
156 char_u *cp, *np; // Point into typed cmd and test name
157 garray_T *gap;
158 int amb_local = FALSE; // Found ambiguous buffer-local command,
159 // only full match global is accepted.
160
161 /*
162 * Look for buffer-local user commands first, then global ones.
163 */
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000164 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200165 for (;;)
166 {
167 for (j = 0; j < gap->ga_len; ++j)
168 {
169 uc = USER_CMD_GA(gap, j);
170 cp = eap->cmd;
171 np = uc->uc_name;
172 k = 0;
173 while (k < len && *np != NUL && *cp++ == *np++)
174 k++;
175 if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k])))
176 {
177 // If finding a second match, the command is ambiguous. But
178 // not if a buffer-local command wasn't a full match and a
179 // global command is a full match.
180 if (k == len && found && *np != NUL)
181 {
182 if (gap == &ucmds)
183 return NULL;
184 amb_local = TRUE;
185 }
186
187 if (!found || (k == len && *np == NUL))
188 {
189 // If we matched up to a digit, then there could
190 // be another command including the digit that we
191 // should use instead.
192 if (k == len)
193 found = TRUE;
194 else
195 possible = TRUE;
196
197 if (gap == &ucmds)
198 eap->cmdidx = CMD_USER;
199 else
200 eap->cmdidx = CMD_USER_BUF;
201 eap->argt = (long)uc->uc_argt;
202 eap->useridx = j;
203 eap->addr_type = uc->uc_addr_type;
204
Bram Moolenaar52111f82019-04-29 21:30:45 +0200205 if (complp != NULL)
206 *complp = uc->uc_compl;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200207# ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200208 if (xp != NULL)
209 {
210 xp->xp_arg = uc->uc_compl_arg;
211 xp->xp_script_ctx = uc->uc_script_ctx;
Bram Moolenaar1a47ae32019-12-29 23:04:25 +0100212 xp->xp_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200213 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200214# endif
215 // Do not search for further abbreviations
216 // if this is an exact match.
217 matchlen = k;
218 if (k == len && *np == NUL)
219 {
220 if (full != NULL)
221 *full = TRUE;
222 amb_local = FALSE;
223 break;
224 }
225 }
226 }
227 }
228
229 // Stop if we found a full match or searched all.
230 if (j < gap->ga_len || gap == &ucmds)
231 break;
232 gap = &ucmds;
233 }
234
235 // Only found ambiguous matches.
236 if (amb_local)
237 {
238 if (xp != NULL)
239 xp->xp_context = EXPAND_UNSUCCESSFUL;
240 return NULL;
241 }
242
243 // The match we found may be followed immediately by a number. Move "p"
244 // back to point to it.
245 if (found || possible)
246 return p + (matchlen - len);
247 return p;
248}
249
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000250/*
251 * Set completion context for :command
252 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200253 char_u *
254set_context_in_user_cmd(expand_T *xp, char_u *arg_in)
255{
256 char_u *arg = arg_in;
257 char_u *p;
258
259 // Check for attributes
260 while (*arg == '-')
261 {
262 arg++; // Skip "-"
263 p = skiptowhite(arg);
264 if (*p == NUL)
265 {
266 // Cursor is still in the attribute
267 p = vim_strchr(arg, '=');
268 if (p == NULL)
269 {
270 // No "=", so complete attribute names
271 xp->xp_context = EXPAND_USER_CMD_FLAGS;
272 xp->xp_pattern = arg;
273 return NULL;
274 }
275
276 // For the -complete, -nargs and -addr attributes, we complete
277 // their arguments as well.
278 if (STRNICMP(arg, "complete", p - arg) == 0)
279 {
280 xp->xp_context = EXPAND_USER_COMPLETE;
281 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200282 }
283 else if (STRNICMP(arg, "nargs", p - arg) == 0)
284 {
285 xp->xp_context = EXPAND_USER_NARGS;
286 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200287 }
288 else if (STRNICMP(arg, "addr", p - arg) == 0)
289 {
290 xp->xp_context = EXPAND_USER_ADDR_TYPE;
291 xp->xp_pattern = p + 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200292 }
293 return NULL;
294 }
295 arg = skipwhite(p);
296 }
297
298 // After the attributes comes the new command name
299 p = skiptowhite(arg);
300 if (*p == NUL)
301 {
302 xp->xp_context = EXPAND_USER_COMMANDS;
303 xp->xp_pattern = arg;
304 return NULL;
305 }
306
307 // And finally comes a normal command
308 return skipwhite(p);
309}
310
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000311/*
312 * Set the completion context for the argument of a user defined command.
313 */
314 char_u *
315set_context_in_user_cmdarg(
316 char_u *cmd UNUSED,
317 char_u *arg,
318 long argt,
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000319 int context,
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000320 expand_T *xp,
321 int forceit)
322{
323 char_u *p;
324
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000325 if (context == EXPAND_NOTHING)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000326 return NULL;
327
328 if (argt & EX_XFILE)
329 {
330 // EX_XFILE: file names are handled before this call
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000331 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000332 return NULL;
333 }
334
335#ifdef FEAT_MENU
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000336 if (context == EXPAND_MENUS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000337 return set_context_in_menu_cmd(xp, cmd, arg, forceit);
338#endif
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000339 if (context == EXPAND_COMMANDS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000340 return arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000341 if (context == EXPAND_MAPPINGS)
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000342 return set_context_in_map_cmd(xp, (char_u *)"map", arg, forceit, FALSE,
343 FALSE, CMD_map);
344 // Find start of last argument.
345 p = arg;
346 while (*p)
347 {
348 if (*p == ' ')
349 // argument starts after a space
350 arg = p + 1;
351 else if (*p == '\\' && *(p + 1) != NUL)
352 ++p; // skip over escaped character
353 MB_PTR_ADV(p);
354 }
355 xp->xp_pattern = arg;
Bram Moolenaarb8fb5bb2022-02-18 13:56:38 +0000356 xp->xp_context = context;
Yegappan Lakshmananb31aec32022-02-16 12:44:29 +0000357
358 return NULL;
359}
360
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200361 char_u *
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200362expand_user_command_name(int idx)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200363{
364 return get_user_commands(NULL, idx - (int)CMD_SIZE);
365}
366
367/*
368 * Function given to ExpandGeneric() to obtain the list of user command names.
369 */
370 char_u *
371get_user_commands(expand_T *xp UNUSED, int idx)
372{
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200373 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000374 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200375
376 if (idx < buf->b_ucmds.ga_len)
377 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100378
Bram Moolenaarf03e3282019-07-22 21:55:18 +0200379 idx -= buf->b_ucmds.ga_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200380 if (idx < ucmds.ga_len)
Bram Moolenaarc2842ad2022-07-26 17:23:47 +0100381 {
382 int i;
383 char_u *name = USER_CMD(idx)->uc_name;
384
385 for (i = 0; i < buf->b_ucmds.ga_len; ++i)
386 if (STRCMP(name, USER_CMD_GA(&buf->b_ucmds, i)->uc_name) == 0)
387 // global command is overruled by buffer-local one
388 return (char_u *)"";
389 return name;
390 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200391 return NULL;
392}
393
Dominique Pelle748b3082022-01-08 12:41:16 +0000394#ifdef FEAT_EVAL
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200395/*
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200396 * Get the name of user command "idx". "cmdidx" can be CMD_USER or
397 * CMD_USER_BUF.
398 * Returns NULL if the command is not found.
399 */
400 char_u *
401get_user_command_name(int idx, int cmdidx)
402{
403 if (cmdidx == CMD_USER && idx < ucmds.ga_len)
404 return USER_CMD(idx)->uc_name;
405 if (cmdidx == CMD_USER_BUF)
406 {
407 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000408 buf_T *buf = prevwin_curwin()->w_buffer;
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200409
410 if (idx < buf->b_ucmds.ga_len)
411 return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name;
412 }
413 return NULL;
414}
Dominique Pelle748b3082022-01-08 12:41:16 +0000415#endif
Bram Moolenaar80c88ea2021-09-08 14:29:46 +0200416
417/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200418 * Function given to ExpandGeneric() to obtain the list of user address type
419 * names.
420 */
421 char_u *
422get_user_cmd_addr_type(expand_T *xp UNUSED, int idx)
423{
John Marriott9e795852024-08-20 20:57:23 +0200424 if (idx < 0 || idx >= (int)ARRAY_LENGTH(addr_type_complete_tab))
425 return NULL;
426 return (char_u *)addr_type_complete_tab[idx].fullname;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200427}
428
429/*
430 * Function given to ExpandGeneric() to obtain the list of user command
431 * attributes.
432 */
433 char_u *
434get_user_cmd_flags(expand_T *xp UNUSED, int idx)
435{
436 static char *user_cmd_flags[] = {
437 "addr", "bang", "bar", "buffer", "complete",
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000438 "count", "nargs", "range", "register", "keepscript"
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200439 };
440
John Marriott9e795852024-08-20 20:57:23 +0200441 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_flags))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200442 return NULL;
443 return (char_u *)user_cmd_flags[idx];
444}
445
446/*
447 * Function given to ExpandGeneric() to obtain the list of values for -nargs.
448 */
449 char_u *
450get_user_cmd_nargs(expand_T *xp UNUSED, int idx)
451{
452 static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"};
453
John Marriott9e795852024-08-20 20:57:23 +0200454 if (idx < 0 || idx >= (int)ARRAY_LENGTH(user_cmd_nargs))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200455 return NULL;
456 return (char_u *)user_cmd_nargs[idx];
457}
458
459/*
460 * Function given to ExpandGeneric() to obtain the list of values for
461 * -complete.
462 */
463 char_u *
464get_user_cmd_complete(expand_T *xp UNUSED, int idx)
465{
John Marriott9e795852024-08-20 20:57:23 +0200466 if (idx < 0 || idx >= (int)ARRAY_LENGTH(command_complete_tab))
467 return NULL;
468 return (char_u *)command_complete_tab[idx].value;
469}
470
471/*
472 * Return the row in the command_complete_tab table that contains the given key.
473 */
474 static keyvalue_T *
475get_commandtype(int expand)
476{
477 int i;
478
479 for (i = 0; i < (int)ARRAY_LENGTH(command_complete_tab); ++i)
480 if (command_complete_tab[i].key == expand)
481 return &command_complete_tab[i];
482
483 return NULL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200484}
485
Dominique Pelle748b3082022-01-08 12:41:16 +0000486#ifdef FEAT_EVAL
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100487/*
488 * Get the name of completion type "expand" as a string.
489 */
490 char_u *
491cmdcomplete_type_to_str(int expand)
492{
John Marriott9e795852024-08-20 20:57:23 +0200493 keyvalue_T *kv;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100494
John Marriott9e795852024-08-20 20:57:23 +0200495 kv = get_commandtype(expand);
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100496
John Marriott9e795852024-08-20 20:57:23 +0200497 return (kv == NULL) ? NULL : (char_u *)kv->value;
Shougo Matsushita79d599b2022-05-07 12:48:29 +0100498}
499
500/*
501 * Get the index of completion type "complete_str".
502 * Returns EXPAND_NOTHING if no match found.
503 */
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200504 int
505cmdcomplete_str_to_type(char_u *complete_str)
506{
John Marriott9e795852024-08-20 20:57:23 +0200507 keyvalue_T target;
508 keyvalue_T *entry;
509 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200510
Shougo Matsushita92997dd2023-08-20 20:55:55 +0200511 if (STRNCMP(complete_str, "custom,", 7) == 0)
512 return EXPAND_USER_DEFINED;
513 if (STRNCMP(complete_str, "customlist,", 11) == 0)
514 return EXPAND_USER_LIST;
515
John Marriott9e795852024-08-20 20:57:23 +0200516 target.key = 0;
517 target.value = (char *)complete_str;
518 target.length = 0; // not used, see cmp_keyvalue_value()
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200519
John Marriott9e795852024-08-20 20:57:23 +0200520 if (last_entry != NULL && cmp_keyvalue_value(&target, last_entry) == 0)
521 entry = last_entry;
522 else
523 {
524 entry = (keyvalue_T *)bsearch(&target,
525 &command_complete_tab,
526 ARRAY_LENGTH(command_complete_tab),
527 sizeof(command_complete_tab[0]),
528 cmp_keyvalue_value);
529 if (entry == NULL)
530 return EXPAND_NOTHING;
531
532 last_entry = entry;
533 }
534
535 return entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200536}
Dominique Pelle748b3082022-01-08 12:41:16 +0000537#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200538
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200539/*
540 * List user commands starting with "name[name_len]".
541 */
542 static void
543uc_list(char_u *name, size_t name_len)
544{
545 int i, j;
546 int found = FALSE;
547 ucmd_T *cmd;
548 int len;
549 int over;
550 long a;
551 garray_T *gap;
John Marriott9e795852024-08-20 20:57:23 +0200552 keyvalue_T *entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200553
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000554 // don't allow for adding or removing user commands here
555 ++ucmd_locked;
556
Bram Moolenaare38eab22019-12-05 21:50:01 +0100557 // In cmdwin, the alternative buffer should be used.
Bram Moolenaar0f6e28f2022-02-20 20:49:35 +0000558 gap = &prevwin_curwin()->w_buffer->b_ucmds;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200559 for (;;)
560 {
561 for (i = 0; i < gap->ga_len; ++i)
562 {
563 cmd = USER_CMD_GA(gap, i);
564 a = (long)cmd->uc_argt;
565
566 // Skip commands which don't match the requested prefix and
567 // commands filtered out.
568 if (STRNCMP(name, cmd->uc_name, name_len) != 0
569 || message_filtered(cmd->uc_name))
570 continue;
571
572 // Put out the title first time
573 if (!found)
574 msg_puts_title(_("\n Name Args Address Complete Definition"));
575 found = TRUE;
576 msg_putchar('\n');
577 if (got_int)
578 break;
579
580 // Special cases
581 len = 4;
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200582 if (a & EX_BANG)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200583 {
584 msg_putchar('!');
585 --len;
586 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200587 if (a & EX_REGSTR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200588 {
589 msg_putchar('"');
590 --len;
591 }
592 if (gap != &ucmds)
593 {
594 msg_putchar('b');
595 --len;
596 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200597 if (a & EX_TRLBAR)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200598 {
599 msg_putchar('|');
600 --len;
601 }
602 while (len-- > 0)
603 msg_putchar(' ');
604
605 msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
John Marriott9e795852024-08-20 20:57:23 +0200606 len = (int)cmd->uc_namelen + 4;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200607
608 do {
609 msg_putchar(' ');
610 ++len;
611 } while (len < 22);
612
613 // "over" is how much longer the name is than the column width for
614 // the name, we'll try to align what comes after.
615 over = len - 22;
616 len = 0;
617
618 // Arguments
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200619 switch ((int)(a & (EX_EXTRA|EX_NOSPC|EX_NEEDARG)))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200620 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200621 case 0: IObuff[len++] = '0'; break;
622 case (EX_EXTRA): IObuff[len++] = '*'; break;
623 case (EX_EXTRA|EX_NOSPC): IObuff[len++] = '?'; break;
624 case (EX_EXTRA|EX_NEEDARG): IObuff[len++] = '+'; break;
625 case (EX_EXTRA|EX_NOSPC|EX_NEEDARG): IObuff[len++] = '1'; break;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200626 }
627
628 do {
629 IObuff[len++] = ' ';
630 } while (len < 5 - over);
631
632 // Address / Range
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200633 if (a & (EX_RANGE|EX_COUNT))
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200634 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200635 if (a & EX_COUNT)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200636 {
637 // -count=N
John Marriott9e795852024-08-20 20:57:23 +0200638 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ldc", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200639 }
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200640 else if (a & EX_DFLALL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200641 IObuff[len++] = '%';
642 else if (cmd->uc_def >= 0)
643 {
644 // -range=N
John Marriott9e795852024-08-20 20:57:23 +0200645 len += vim_snprintf((char *)IObuff + len, IOSIZE - len, "%ld", cmd->uc_def);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200646 }
647 else
648 IObuff[len++] = '.';
649 }
650
651 do {
652 IObuff[len++] = ' ';
653 } while (len < 8 - over);
654
655 // Address Type
John Marriott9e795852024-08-20 20:57:23 +0200656 for (j = 0; j < (int)ARRAY_LENGTH(addr_type_complete_tab); ++j)
657 if (addr_type_complete_tab[j].key != ADDR_LINES
658 && addr_type_complete_tab[j].key == cmd->uc_addr_type)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200659 {
John Marriott9e795852024-08-20 20:57:23 +0200660 STRCPY(IObuff + len, addr_type_complete_tab[j].shortname);
661 len += (int)addr_type_complete_tab[j].shortnamelen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200662 break;
663 }
664
665 do {
666 IObuff[len++] = ' ';
667 } while (len < 13 - over);
668
669 // Completion
John Marriott9e795852024-08-20 20:57:23 +0200670 entry = get_commandtype(cmd->uc_compl);
671 if (entry != NULL)
672 {
673 STRCPY(IObuff + len, entry->value);
674 len += entry->length;
Bram Moolenaar78f60322022-01-17 22:16:33 +0000675#ifdef FEAT_EVAL
John Marriott9e795852024-08-20 20:57:23 +0200676 if (p_verbose > 0 && cmd->uc_compl_arg != NULL)
677 {
678 size_t uc_compl_arglen = STRLEN(cmd->uc_compl_arg);
679
680 if (uc_compl_arglen < 200)
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000681 {
John Marriott9e795852024-08-20 20:57:23 +0200682 IObuff[len++] = ',';
683 STRCPY(IObuff + len, cmd->uc_compl_arg);
684 len += uc_compl_arglen;
Bram Moolenaar3f3597b2022-01-17 19:06:56 +0000685 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200686 }
John Marriott9e795852024-08-20 20:57:23 +0200687#endif
688 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200689
690 do {
691 IObuff[len++] = ' ';
692 } while (len < 25 - over);
693
John Marriott9e795852024-08-20 20:57:23 +0200694 IObuff[len] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200695 msg_outtrans(IObuff);
696
697 msg_outtrans_special(cmd->uc_rep, FALSE,
698 name_len == 0 ? Columns - 47 : 0);
699#ifdef FEAT_EVAL
700 if (p_verbose > 0)
701 last_set_msg(cmd->uc_script_ctx);
702#endif
703 out_flush();
704 ui_breakcheck();
705 if (got_int)
706 break;
707 }
708 if (gap == &ucmds || i < gap->ga_len)
709 break;
710 gap = &ucmds;
711 }
712
713 if (!found)
714 msg(_("No user-defined commands found"));
Bram Moolenaarcf2594f2022-11-13 23:30:06 +0000715
716 --ucmd_locked;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200717}
718
719 char *
720uc_fun_cmd(void)
721{
722 static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
723 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
724 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
725 0xb9, 0x7f, 0};
726 int i;
727
728 for (i = 0; fcmd[i]; ++i)
729 IObuff[i] = fcmd[i] - 0x40;
John Marriott9e795852024-08-20 20:57:23 +0200730 IObuff[i] = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200731 return (char *)IObuff;
732}
733
734/*
735 * Parse address type argument
736 */
737 static int
738parse_addr_type_arg(
739 char_u *value,
740 int vallen,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200741 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200742{
John Marriott9e795852024-08-20 20:57:23 +0200743 addrtype_T target;
744 addrtype_T *entry;
745 static addrtype_T *last_entry; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200746
John Marriott9e795852024-08-20 20:57:23 +0200747 target.key = 0;
748 target.fullname = (char *)value;
749 target.fullnamelen = vallen;
750
751 if (last_entry != NULL && cmp_addr_type(&target, last_entry) == 0)
752 entry = last_entry;
753 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200754 {
John Marriott9e795852024-08-20 20:57:23 +0200755 entry = (addrtype_T *)bsearch(&target,
756 &addr_type_complete_tab,
757 ARRAY_LENGTH(addr_type_complete_tab),
758 sizeof(addr_type_complete_tab[0]),
759 cmp_addr_type);
760 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200761 {
John Marriott9e795852024-08-20 20:57:23 +0200762 int i;
763 char_u *err = value;
764
765 for (i = 0; err[i] != NUL && !VIM_ISWHITE(err[i]); i++)
766 ;
767 err[i] = NUL;
768 semsg(_(e_invalid_address_type_value_str), err);
769 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200770 }
John Marriott9e795852024-08-20 20:57:23 +0200771
772 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200773 }
774
John Marriott9e795852024-08-20 20:57:23 +0200775 *addr_type_arg = entry->key;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200776
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200777 return OK;
778}
779
John Marriott9e795852024-08-20 20:57:23 +0200780 static int
781cmp_addr_type(const void *a, const void *b)
782{
783 addrtype_T *at1 = (addrtype_T *)a;
784 addrtype_T *at2 = (addrtype_T *)b;
785
786 return STRNCMP(at1->fullname, at2->fullname, MAX(at1->fullnamelen, at2->fullnamelen));
787}
788
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200789/*
790 * Parse a completion argument "value[vallen]".
791 * The detected completion goes in "*complp", argument type in "*argt".
792 * When there is an argument, for function and user defined completion, it's
793 * copied to allocated memory and stored in "*compl_arg".
794 * Returns FAIL if something is wrong.
795 */
796 int
797parse_compl_arg(
798 char_u *value,
799 int vallen,
800 int *complp,
801 long *argt,
802 char_u **compl_arg UNUSED)
803{
804 char_u *arg = NULL;
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200805# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200806 size_t arglen = 0;
807# endif
808 int i;
809 int valend = vallen;
John Marriott9e795852024-08-20 20:57:23 +0200810 keyvalue_T target;
811 keyvalue_T *entry;
812 static keyvalue_T *last_entry = NULL; // cached result
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200813
814 // Look for any argument part - which is the part after any ','
815 for (i = 0; i < vallen; ++i)
816 {
817 if (value[i] == ',')
818 {
819 arg = &value[i + 1];
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200820# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200821 arglen = vallen - i - 1;
822# endif
823 valend = i;
824 break;
825 }
826 }
827
John Marriott9e795852024-08-20 20:57:23 +0200828 target.key = 0;
829 target.value = (char *)value;
830 target.length = valend;
831
832 if (last_entry != NULL && cmp_keyvalue_value_n(&target, last_entry) == 0)
833 entry = last_entry;
834 else
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200835 {
John Marriott9e795852024-08-20 20:57:23 +0200836 entry = (keyvalue_T *)bsearch(&target,
837 &command_complete_tab,
838 ARRAY_LENGTH(command_complete_tab),
839 sizeof(command_complete_tab[0]),
840 cmp_keyvalue_value_n);
841 if (entry == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200842 {
John Marriott9e795852024-08-20 20:57:23 +0200843 semsg(_(e_invalid_complete_value_str), value);
844 return FAIL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200845 }
John Marriott9e795852024-08-20 20:57:23 +0200846
847 last_entry = entry;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200848 }
849
John Marriott9e795852024-08-20 20:57:23 +0200850 *complp = entry->key;
851 if (*complp == EXPAND_BUFFERS)
852 *argt |= EX_BUFNAME;
853 else if (*complp == EXPAND_DIRECTORIES || *complp == EXPAND_FILES)
854 *argt |= EX_XFILE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200855
John Marriott9e795852024-08-20 20:57:23 +0200856 if (
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200857# if defined(FEAT_EVAL)
John Marriott9e795852024-08-20 20:57:23 +0200858 *complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST
859 &&
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200860# endif
John Marriott9e795852024-08-20 20:57:23 +0200861 arg != NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200862 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000863 emsg(_(e_completion_argument_only_allowed_for_custom_completion));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200864 return FAIL;
865 }
866
Bram Moolenaar0a52df52019-08-18 22:26:31 +0200867# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200868 if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST)
869 && arg == NULL)
870 {
Bram Moolenaarb09feaa2022-01-02 20:20:45 +0000871 emsg(_(e_custom_completion_requires_function_argument));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200872 return FAIL;
873 }
874
875 if (arg != NULL)
Bram Moolenaar71ccd032020-06-12 22:59:11 +0200876 *compl_arg = vim_strnsave(arg, arglen);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200877# endif
John Marriott9e795852024-08-20 20:57:23 +0200878
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200879 return OK;
880}
881
882/*
883 * Scan attributes in the ":command" command.
884 * Return FAIL when something is wrong.
885 */
886 static int
887uc_scan_attr(
888 char_u *attr,
889 size_t len,
890 long *argt,
891 long *def,
892 int *flags,
Bram Moolenaar52111f82019-04-29 21:30:45 +0200893 int *complp,
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200894 char_u **compl_arg,
Bram Moolenaarb7316892019-05-01 18:08:42 +0200895 cmd_addr_T *addr_type_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200896{
897 char_u *p;
898
899 if (len == 0)
900 {
Bram Moolenaar1a992222021-12-31 17:25:48 +0000901 emsg(_(e_no_attribute_specified));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200902 return FAIL;
903 }
904
905 // First, try the simple attributes (no arguments)
906 if (STRNICMP(attr, "bang", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200907 *argt |= EX_BANG;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200908 else if (STRNICMP(attr, "buffer", len) == 0)
909 *flags |= UC_BUFFER;
910 else if (STRNICMP(attr, "register", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200911 *argt |= EX_REGSTR;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +0000912 else if (STRNICMP(attr, "keepscript", len) == 0)
913 *argt |= EX_KEEPSCRIPT;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200914 else if (STRNICMP(attr, "bar", len) == 0)
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200915 *argt |= EX_TRLBAR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200916 else
917 {
918 int i;
919 char_u *val = NULL;
920 size_t vallen = 0;
921 size_t attrlen = len;
922
923 // Look for the attribute name - which is the part before any '='
924 for (i = 0; i < (int)len; ++i)
925 {
926 if (attr[i] == '=')
927 {
928 val = &attr[i + 1];
929 vallen = len - i - 1;
930 attrlen = i;
931 break;
932 }
933 }
934
935 if (STRNICMP(attr, "nargs", attrlen) == 0)
936 {
937 if (vallen == 1)
938 {
939 if (*val == '0')
940 // Do nothing - this is the default
941 ;
942 else if (*val == '1')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200943 *argt |= (EX_EXTRA | EX_NOSPC | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200944 else if (*val == '*')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200945 *argt |= EX_EXTRA;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200946 else if (*val == '?')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200947 *argt |= (EX_EXTRA | EX_NOSPC);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200948 else if (*val == '+')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200949 *argt |= (EX_EXTRA | EX_NEEDARG);
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200950 else
951 goto wrong_nargs;
952 }
953 else
954 {
955wrong_nargs:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000956 emsg(_(e_invalid_number_of_arguments));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200957 return FAIL;
958 }
959 }
960 else if (STRNICMP(attr, "range", attrlen) == 0)
961 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200962 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200963 if (vallen == 1 && *val == '%')
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200964 *argt |= EX_DFLALL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200965 else if (val != NULL)
966 {
967 p = val;
968 if (*def >= 0)
969 {
970two_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000971 emsg(_(e_count_cannot_be_specified_twice));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200972 return FAIL;
973 }
974
975 *def = getdigits(&p);
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200976 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200977
978 if (p != val + vallen || vallen == 0)
979 {
980invalid_count:
Bram Moolenaar1a992222021-12-31 17:25:48 +0000981 emsg(_(e_invalid_default_value_for_count));
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200982 return FAIL;
983 }
984 }
Bram Moolenaarb7316892019-05-01 18:08:42 +0200985 // default for -range is using buffer lines
986 if (*addr_type_arg == ADDR_NONE)
987 *addr_type_arg = ADDR_LINES;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200988 }
989 else if (STRNICMP(attr, "count", attrlen) == 0)
990 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +0200991 *argt |= (EX_COUNT | EX_ZEROR | EX_RANGE);
Bram Moolenaarb7316892019-05-01 18:08:42 +0200992 // default for -count is using any number
993 if (*addr_type_arg == ADDR_NONE)
994 *addr_type_arg = ADDR_OTHER;
Bram Moolenaarac9fb182019-04-27 13:04:13 +0200995
996 if (val != NULL)
997 {
998 p = val;
999 if (*def >= 0)
1000 goto two_count;
1001
1002 *def = getdigits(&p);
1003
1004 if (p != val + vallen)
1005 goto invalid_count;
1006 }
1007
1008 if (*def < 0)
1009 *def = 0;
1010 }
1011 else if (STRNICMP(attr, "complete", attrlen) == 0)
1012 {
1013 if (val == NULL)
1014 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001015 semsg(_(e_argument_required_for_str), "-complete");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001016 return FAIL;
1017 }
1018
Bram Moolenaar52111f82019-04-29 21:30:45 +02001019 if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001020 == FAIL)
1021 return FAIL;
1022 }
1023 else if (STRNICMP(attr, "addr", attrlen) == 0)
1024 {
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001025 *argt |= EX_RANGE;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001026 if (val == NULL)
1027 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001028 semsg(_(e_argument_required_for_str), "-addr");
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001029 return FAIL;
1030 }
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001031 if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001032 return FAIL;
Bram Moolenaare4f5f3a2019-05-04 14:05:08 +02001033 if (*addr_type_arg != ADDR_LINES)
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001034 *argt |= EX_ZEROR;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001035 }
1036 else
1037 {
1038 char_u ch = attr[len];
1039 attr[len] = '\0';
Bram Moolenaar1a992222021-12-31 17:25:48 +00001040 semsg(_(e_invalid_attribute_str), attr);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001041 attr[len] = ch;
1042 return FAIL;
1043 }
1044 }
1045
1046 return OK;
1047}
1048
1049/*
1050 * Add a user command to the list or replace an existing one.
1051 */
1052 static int
1053uc_add_command(
1054 char_u *name,
1055 size_t name_len,
1056 char_u *rep,
1057 long argt,
1058 long def,
1059 int flags,
1060 int compl,
1061 char_u *compl_arg UNUSED,
Bram Moolenaarb7316892019-05-01 18:08:42 +02001062 cmd_addr_T addr_type,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001063 int force)
1064{
1065 ucmd_T *cmd = NULL;
1066 char_u *p;
1067 int i;
1068 int cmp = 1;
1069 char_u *rep_buf = NULL;
1070 garray_T *gap;
1071
zeertzjq7e0bae02023-08-11 23:15:38 +02001072 replace_termcodes(rep, &rep_buf, 0, 0, NULL);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001073 if (rep_buf == NULL)
1074 {
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001075 // can't replace termcodes - try using the string as is
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001076 rep_buf = vim_strsave(rep);
1077
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001078 // give up if out of memory
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001079 if (rep_buf == NULL)
1080 return FAIL;
1081 }
1082
1083 // get address of growarray: global or in curbuf
1084 if (flags & UC_BUFFER)
1085 {
1086 gap = &curbuf->b_ucmds;
1087 if (gap->ga_itemsize == 0)
Bram Moolenaar04935fb2022-01-08 16:19:22 +00001088 ga_init2(gap, sizeof(ucmd_T), 4);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001089 }
1090 else
1091 gap = &ucmds;
1092
1093 // Search for the command in the already defined commands.
1094 for (i = 0; i < gap->ga_len; ++i)
1095 {
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001096 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001097 cmp = STRNCMP(name, cmd->uc_name, name_len);
1098 if (cmp == 0)
1099 {
John Marriott9e795852024-08-20 20:57:23 +02001100 if (name_len < cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001101 cmp = -1;
John Marriott9e795852024-08-20 20:57:23 +02001102 else if (name_len > cmd->uc_namelen)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001103 cmp = 1;
1104 }
1105
1106 if (cmp == 0)
1107 {
1108 // Command can be replaced with "command!" and when sourcing the
1109 // same script again, but only once.
1110 if (!force
1111#ifdef FEAT_EVAL
1112 && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
1113 || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)
1114#endif
1115 )
1116 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001117 semsg(_(e_command_already_exists_add_bang_to_replace_it_str),
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001118 name);
1119 goto fail;
1120 }
1121
1122 VIM_CLEAR(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001123#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001124 VIM_CLEAR(cmd->uc_compl_arg);
1125#endif
1126 break;
1127 }
1128
1129 // Stop as soon as we pass the name to add
1130 if (cmp < 0)
1131 break;
1132 }
1133
1134 // Extend the array unless we're replacing an existing command
1135 if (cmp != 0)
1136 {
Yegappan Lakshmananfadc02a2023-01-27 21:03:12 +00001137 if (ga_grow(gap, 1) == FAIL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001138 goto fail;
Bram Moolenaar71ccd032020-06-12 22:59:11 +02001139 if ((p = vim_strnsave(name, name_len)) == NULL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001140 goto fail;
1141
1142 cmd = USER_CMD_GA(gap, i);
1143 mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T));
1144
1145 ++gap->ga_len;
1146
1147 cmd->uc_name = p;
John Marriott9e795852024-08-20 20:57:23 +02001148 cmd->uc_namelen = name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001149 }
1150
1151 cmd->uc_rep = rep_buf;
1152 cmd->uc_argt = argt;
1153 cmd->uc_def = def;
1154 cmd->uc_compl = compl;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001155 cmd->uc_script_ctx = current_sctx;
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001156 if (flags & UC_VIM9)
1157 cmd->uc_script_ctx.sc_version = SCRIPT_VERSION_VIM9;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001158 cmd->uc_flags = flags & UC_VIM9;
Bram Moolenaar9b8d6222020-12-28 18:26:00 +01001159#ifdef FEAT_EVAL
Bram Moolenaar1a47ae32019-12-29 23:04:25 +01001160 cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001161 cmd->uc_compl_arg = compl_arg;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001162#endif
1163 cmd->uc_addr_type = addr_type;
1164
1165 return OK;
1166
1167fail:
1168 vim_free(rep_buf);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001169#if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001170 vim_free(compl_arg);
1171#endif
1172 return FAIL;
1173}
1174
1175/*
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001176 * If "p" starts with "{" then read a block of commands until "}".
1177 * Used for ":command" and ":autocmd".
1178 */
1179 char_u *
1180may_get_cmd_block(exarg_T *eap, char_u *p, char_u **tofree, int *flags)
1181{
1182 char_u *retp = p;
1183
1184 if (*p == '{' && ends_excmd2(eap->arg, skipwhite(p + 1))
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001185 && eap->ea_getline != NULL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001186 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001187 garray_T ga;
1188 char_u *line = NULL;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001189
1190 ga_init2(&ga, sizeof(char_u *), 10);
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001191 if (ga_copy_string(&ga, p) == FAIL)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001192 return retp;
1193
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001194 // If the argument ends in "}" it must have been concatenated already
1195 // for ISN_EXEC.
1196 if (p[STRLEN(p) - 1] != '}')
1197 // Read lines between '{' and '}'. Does not support nesting or
1198 // here-doc constructs.
1199 for (;;)
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001200 {
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001201 vim_free(line);
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01001202 if ((line = eap->ea_getline(':', eap->cookie,
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001203 0, GETLINE_CONCAT_CONTBAR)) == NULL)
1204 {
1205 emsg(_(e_missing_rcurly));
1206 break;
1207 }
Bram Moolenaar9f1a39a2022-01-08 15:39:39 +00001208 if (ga_copy_string(&ga, line) == FAIL)
Bram Moolenaare4db17f2021-08-01 21:19:43 +02001209 break;
1210 if (*skipwhite(line) == '}')
1211 break;
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001212 }
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001213 vim_free(line);
1214 retp = *tofree = ga_concat_strings(&ga, "\n");
1215 ga_clear_strings(&ga);
1216 *flags |= UC_VIM9;
1217 }
1218 return retp;
1219}
1220
1221/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001222 * ":command ..." implementation
1223 */
1224 void
1225ex_command(exarg_T *eap)
1226{
Bram Moolenaarb7316892019-05-01 18:08:42 +02001227 char_u *name;
1228 char_u *end;
1229 char_u *p;
1230 long argt = 0;
1231 long def = -1;
1232 int flags = 0;
1233 int compl = EXPAND_NOTHING;
1234 char_u *compl_arg = NULL;
1235 cmd_addr_T addr_type_arg = ADDR_NONE;
1236 int has_attr = (eap->arg[0] == '-');
1237 int name_len;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001238
1239 p = eap->arg;
1240
1241 // Check for attributes
1242 while (*p == '-')
1243 {
1244 ++p;
1245 end = skiptowhite(p);
1246 if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl,
1247 &compl_arg, &addr_type_arg) == FAIL)
zeertzjq33e54302022-12-19 16:49:27 +00001248 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001249 p = skipwhite(end);
1250 }
1251
1252 // Get the name (if any) and skip to the following argument
1253 name = p;
1254 if (ASCII_ISALPHA(*p))
1255 while (ASCII_ISALNUM(*p))
1256 ++p;
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001257 if (!ends_excmd2(eap->arg, p) && !VIM_ISWHITE(*p))
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001258 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001259 emsg(_(e_invalid_command_name));
zeertzjq33e54302022-12-19 16:49:27 +00001260 goto theend;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001261 }
1262 end = p;
1263 name_len = (int)(end - name);
1264
1265 // If there is nothing after the name, and no attributes were specified,
1266 // we are listing commands
1267 p = skipwhite(end);
Bram Moolenaara72cfb82020-04-23 17:07:30 +02001268 if (!has_attr && ends_excmd2(eap->arg, p))
zeertzjq33e54302022-12-19 16:49:27 +00001269 {
John Marriott9e795852024-08-20 20:57:23 +02001270 uc_list(name, name_len);
zeertzjq33e54302022-12-19 16:49:27 +00001271 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001272 else if (!ASCII_ISUPPER(*name))
zeertzjq33e54302022-12-19 16:49:27 +00001273 {
Bram Moolenaar1a992222021-12-31 17:25:48 +00001274 emsg(_(e_user_defined_commands_must_start_with_an_uppercase_letter));
zeertzjq33e54302022-12-19 16:49:27 +00001275 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001276 else if ((name_len == 1 && *name == 'X')
1277 || (name_len <= 4
1278 && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0))
zeertzjq33e54302022-12-19 16:49:27 +00001279 {
Bram Moolenaar9d00e4a2022-01-05 17:49:15 +00001280 emsg(_(e_reserved_name_cannot_be_used_for_user_defined_command));
zeertzjq33e54302022-12-19 16:49:27 +00001281 }
Martin Tournoijde69a732021-07-11 14:28:25 +02001282 else if (compl > 0 && (argt & EX_EXTRA) == 0)
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001283 {
1284 // Some plugins rely on silently ignoring the mistake, only make this
1285 // an error in Vim9 script.
1286 if (in_vim9script())
Bram Moolenaar41a34852021-08-04 16:09:24 +02001287 emsg(_(e_complete_used_without_allowing_arguments));
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001288 else
1289 give_warning_with_source(
Bram Moolenaar41a34852021-08-04 16:09:24 +02001290 (char_u *)_(e_complete_used_without_allowing_arguments),
1291 TRUE, TRUE);
Bram Moolenaarcc7eb2a2021-07-11 19:12:04 +02001292 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001293 else
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001294 {
1295 char_u *tofree = NULL;
1296
Bram Moolenaar73b8b0a2021-08-01 14:52:32 +02001297 p = may_get_cmd_block(eap, p, &tofree, &flags);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001298
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001299 uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg,
1300 addr_type_arg, eap->forceit);
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001301 vim_free(tofree);
zeertzjq33e54302022-12-19 16:49:27 +00001302
1303 return; // success
Bram Moolenaar5d7c2df2021-07-27 21:17:32 +02001304 }
zeertzjq33e54302022-12-19 16:49:27 +00001305
1306theend:
1307 vim_free(compl_arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001308}
1309
1310/*
1311 * ":comclear" implementation
1312 * Clear all user commands, global and for current buffer.
1313 */
1314 void
1315ex_comclear(exarg_T *eap UNUSED)
1316{
1317 uc_clear(&ucmds);
Bram Moolenaare5c83282019-05-03 23:15:37 +02001318 if (curbuf != NULL)
1319 uc_clear(&curbuf->b_ucmds);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001320}
1321
1322/*
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001323 * If ucmd_locked is set give an error and return TRUE.
1324 * Otherwise return FALSE.
1325 */
1326 static int
1327is_ucmd_locked(void)
1328{
1329 if (ucmd_locked > 0)
1330 {
1331 emsg(_(e_cannot_change_user_commands_while_listing));
1332 return TRUE;
1333 }
1334 return FALSE;
1335}
1336
1337/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001338 * Clear all user commands for "gap".
1339 */
1340 void
1341uc_clear(garray_T *gap)
1342{
1343 int i;
1344 ucmd_T *cmd;
1345
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001346 if (is_ucmd_locked())
1347 return;
1348
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001349 for (i = 0; i < gap->ga_len; ++i)
1350 {
1351 cmd = USER_CMD_GA(gap, i);
1352 vim_free(cmd->uc_name);
John Marriott9e795852024-08-20 20:57:23 +02001353 cmd->uc_namelen = 0;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001354 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001355# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001356 vim_free(cmd->uc_compl_arg);
1357# endif
1358 }
1359 ga_clear(gap);
1360}
1361
1362/*
1363 * ":delcommand" implementation
1364 */
1365 void
1366ex_delcommand(exarg_T *eap)
1367{
1368 int i = 0;
1369 ucmd_T *cmd = NULL;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001370 int res = -1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001371 garray_T *gap;
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001372 char_u *arg = eap->arg;
1373 int buffer_only = FALSE;
1374
1375 if (STRNCMP(arg, "-buffer", 7) == 0 && VIM_ISWHITE(arg[7]))
1376 {
1377 buffer_only = TRUE;
1378 arg = skipwhite(arg + 7);
1379 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001380
1381 gap = &curbuf->b_ucmds;
1382 for (;;)
1383 {
1384 for (i = 0; i < gap->ga_len; ++i)
1385 {
1386 cmd = USER_CMD_GA(gap, i);
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001387 res = STRCMP(arg, cmd->uc_name);
1388 if (res <= 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001389 break;
1390 }
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001391 if (gap == &ucmds || res == 0 || buffer_only)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001392 break;
1393 gap = &ucmds;
1394 }
1395
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001396 if (res != 0)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001397 {
Bram Moolenaarbdcba242021-09-12 20:58:02 +02001398 semsg(_(buffer_only
1399 ? e_no_such_user_defined_command_in_current_buffer_str
1400 : e_no_such_user_defined_command_str), arg);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001401 return;
1402 }
1403
Bram Moolenaarcf2594f2022-11-13 23:30:06 +00001404 if (is_ucmd_locked())
1405 return;
1406
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001407 vim_free(cmd->uc_name);
1408 vim_free(cmd->uc_rep);
Bram Moolenaar0a52df52019-08-18 22:26:31 +02001409# if defined(FEAT_EVAL)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001410 vim_free(cmd->uc_compl_arg);
1411# endif
1412
1413 --gap->ga_len;
1414
1415 if (i < gap->ga_len)
1416 mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T));
1417}
1418
1419/*
1420 * Split and quote args for <f-args>.
1421 */
1422 static char_u *
1423uc_split_args(char_u *arg, size_t *lenp)
1424{
1425 char_u *buf;
1426 char_u *p;
1427 char_u *q;
1428 int len;
1429
1430 // Precalculate length
1431 p = arg;
1432 len = 2; // Initial and final quotes
1433
1434 while (*p)
1435 {
1436 if (p[0] == '\\' && p[1] == '\\')
1437 {
1438 len += 2;
1439 p += 2;
1440 }
1441 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1442 {
1443 len += 1;
1444 p += 2;
1445 }
1446 else if (*p == '\\' || *p == '"')
1447 {
1448 len += 2;
1449 p += 1;
1450 }
1451 else if (VIM_ISWHITE(*p))
1452 {
1453 p = skipwhite(p);
1454 if (*p == NUL)
1455 break;
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001456 len += 4; // ", "
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001457 }
1458 else
1459 {
1460 int charlen = (*mb_ptr2len)(p);
1461
1462 len += charlen;
1463 p += charlen;
1464 }
1465 }
1466
1467 buf = alloc(len + 1);
1468 if (buf == NULL)
1469 {
1470 *lenp = 0;
1471 return buf;
1472 }
1473
1474 p = arg;
1475 q = buf;
1476 *q++ = '"';
1477 while (*p)
1478 {
1479 if (p[0] == '\\' && p[1] == '\\')
1480 {
1481 *q++ = '\\';
1482 *q++ = '\\';
1483 p += 2;
1484 }
1485 else if (p[0] == '\\' && VIM_ISWHITE(p[1]))
1486 {
1487 *q++ = p[1];
1488 p += 2;
1489 }
1490 else if (*p == '\\' || *p == '"')
1491 {
1492 *q++ = '\\';
1493 *q++ = *p++;
1494 }
1495 else if (VIM_ISWHITE(*p))
1496 {
1497 p = skipwhite(p);
1498 if (*p == NUL)
1499 break;
1500 *q++ = '"';
1501 *q++ = ',';
Bram Moolenaar20d89e02020-10-20 23:11:33 +02001502 *q++ = ' ';
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001503 *q++ = '"';
1504 }
1505 else
1506 {
1507 MB_COPY_CHAR(p, q);
1508 }
1509 }
1510 *q++ = '"';
John Marriott9e795852024-08-20 20:57:23 +02001511 *q = NUL;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001512
1513 *lenp = len;
1514 return buf;
1515}
1516
1517 static size_t
John Marriott9e795852024-08-20 20:57:23 +02001518add_cmd_modifier(char_u *buf, size_t buflen, char *mod_str, size_t mod_strlen, int *multi_mods)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001519{
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001520 if (buf != NULL)
1521 {
1522 if (*multi_mods)
John Marriott9e795852024-08-20 20:57:23 +02001523 {
1524 STRCPY(buf + buflen, " "); // the separating space
1525 ++buflen;
1526 }
1527 STRCPY(buf + buflen, mod_str);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001528 }
1529
John Marriott9e795852024-08-20 20:57:23 +02001530 if (*multi_mods)
1531 ++mod_strlen; // +1 for the separating space
1532 else
1533 *multi_mods = 1;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001534
John Marriott9e795852024-08-20 20:57:23 +02001535 return mod_strlen;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001536}
1537
1538/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001539 * Add modifiers from "cmod->cmod_split" to "buf". Set "multi_mods" when one
Bram Moolenaare1004402020-10-24 20:49:43 +02001540 * was added. Return the number of bytes added.
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001541 */
1542 size_t
dundargocc57b5bc2022-11-02 13:30:51 +00001543add_win_cmd_modifiers(char_u *buf, cmdmod_T *cmod, int *multi_mods)
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001544{
John Marriott9e795852024-08-20 20:57:23 +02001545 size_t buflen = 0;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001546
1547 // :aboveleft and :leftabove
Bram Moolenaar02194d22020-10-24 23:08:38 +02001548 if (cmod->cmod_split & WSP_ABOVE)
John Marriott9e795852024-08-20 20:57:23 +02001549 buflen += add_cmd_modifier(buf, buflen, "aboveleft", STRLEN_LITERAL("aboveleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001550 // :belowright and :rightbelow
Bram Moolenaar02194d22020-10-24 23:08:38 +02001551 if (cmod->cmod_split & WSP_BELOW)
John Marriott9e795852024-08-20 20:57:23 +02001552 buflen += add_cmd_modifier(buf, buflen, "belowright", STRLEN_LITERAL("belowright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001553 // :botright
Bram Moolenaar02194d22020-10-24 23:08:38 +02001554 if (cmod->cmod_split & WSP_BOT)
John Marriott9e795852024-08-20 20:57:23 +02001555 buflen += add_cmd_modifier(buf, buflen, "botright", STRLEN_LITERAL("botright"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001556
1557 // :tab
Bram Moolenaar02194d22020-10-24 23:08:38 +02001558 if (cmod->cmod_tab > 0)
zeertzjq208567e2022-10-18 13:11:21 +01001559 {
1560 int tabnr = cmod->cmod_tab - 1;
1561
1562 if (tabnr == tabpage_index(curtab))
1563 {
1564 // For compatibility, don't add a tabpage number if it is the same
1565 // as the default number for :tab.
John Marriott9e795852024-08-20 20:57:23 +02001566 buflen += add_cmd_modifier(buf, buflen, "tab", STRLEN_LITERAL("tab"), multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001567 }
1568 else
1569 {
1570 char tab_buf[NUMBUFLEN + 3];
John Marriott9e795852024-08-20 20:57:23 +02001571 size_t tab_buflen;
zeertzjq208567e2022-10-18 13:11:21 +01001572
John Marriott9e795852024-08-20 20:57:23 +02001573 tab_buflen = vim_snprintf(tab_buf, sizeof(tab_buf), "%dtab", tabnr);
1574 buflen += add_cmd_modifier(buf, buflen, tab_buf, tab_buflen, multi_mods);
zeertzjq208567e2022-10-18 13:11:21 +01001575 }
1576 }
1577
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001578 // :topleft
Bram Moolenaar02194d22020-10-24 23:08:38 +02001579 if (cmod->cmod_split & WSP_TOP)
John Marriott9e795852024-08-20 20:57:23 +02001580 buflen += add_cmd_modifier(buf, buflen, "topleft", STRLEN_LITERAL("topleft"), multi_mods);
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001581 // :vertical
Bram Moolenaar02194d22020-10-24 23:08:38 +02001582 if (cmod->cmod_split & WSP_VERT)
John Marriott9e795852024-08-20 20:57:23 +02001583 buflen += add_cmd_modifier(buf, buflen, "vertical", STRLEN_LITERAL("vertical"), multi_mods);
zeertzjqd3de1782022-09-01 12:58:52 +01001584 // :horizontal
1585 if (cmod->cmod_split & WSP_HOR)
John Marriott9e795852024-08-20 20:57:23 +02001586 buflen += add_cmd_modifier(buf, buflen, "horizontal", STRLEN_LITERAL("horizontal"), multi_mods);
1587
1588 return buflen;
Bram Moolenaar7a1637f2020-04-13 21:16:21 +02001589}
1590
1591/*
Bram Moolenaar02194d22020-10-24 23:08:38 +02001592 * Generate text for the "cmod" command modifiers.
1593 * If "buf" is NULL just return the length.
1594 */
Bram Moolenaara360dbe2020-10-26 18:46:53 +01001595 size_t
Bram Moolenaar02194d22020-10-24 23:08:38 +02001596produce_cmdmods(char_u *buf, cmdmod_T *cmod, int quote)
1597{
John Marriott9e795852024-08-20 20:57:23 +02001598 size_t buflen = 0;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001599 int multi_mods = 0;
1600 int i;
John Marriott9e795852024-08-20 20:57:23 +02001601 static keyvalue_T mod_entry_tab[] =
1602 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001603#ifdef FEAT_BROWSE_CMD
John Marriott9e795852024-08-20 20:57:23 +02001604 KEYVALUE_ENTRY(CMOD_BROWSE, "browse"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001605#endif
1606#if defined(FEAT_GUI_DIALOG) || defined(FEAT_CON_DIALOG)
John Marriott9e795852024-08-20 20:57:23 +02001607 KEYVALUE_ENTRY(CMOD_CONFIRM, "confirm"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001608#endif
John Marriott9e795852024-08-20 20:57:23 +02001609 KEYVALUE_ENTRY(CMOD_HIDE, "hide"),
1610 KEYVALUE_ENTRY(CMOD_KEEPALT, "keepalt"),
1611 KEYVALUE_ENTRY(CMOD_KEEPJUMPS, "keepjumps"),
1612 KEYVALUE_ENTRY(CMOD_KEEPMARKS, "keepmarks"),
1613 KEYVALUE_ENTRY(CMOD_KEEPPATTERNS, "keeppatterns"),
1614 KEYVALUE_ENTRY(CMOD_LOCKMARKS, "lockmarks"),
1615 KEYVALUE_ENTRY(CMOD_NOSWAPFILE, "noswapfile"),
1616 KEYVALUE_ENTRY(CMOD_UNSILENT, "unsilent"),
1617 KEYVALUE_ENTRY(CMOD_NOAUTOCMD, "noautocmd"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001618#ifdef HAVE_SANDBOX
John Marriott9e795852024-08-20 20:57:23 +02001619 KEYVALUE_ENTRY(CMOD_SANDBOX, "sandbox"),
Bram Moolenaar02194d22020-10-24 23:08:38 +02001620#endif
John Marriott9e795852024-08-20 20:57:23 +02001621 KEYVALUE_ENTRY(CMOD_LEGACY, "legacy")
Bram Moolenaar02194d22020-10-24 23:08:38 +02001622 };
1623
John Marriott9e795852024-08-20 20:57:23 +02001624 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001625 {
John Marriott9e795852024-08-20 20:57:23 +02001626 ++buflen;
1627 if (buf != NULL)
1628 {
1629 *buf = '"';
1630 *(buf + buflen) = NUL;
1631 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001632 }
John Marriott9e795852024-08-20 20:57:23 +02001633 else
1634 if (buf != NULL)
1635 *buf = NUL;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001636
1637 // the modifiers that are simple flags
John Marriott9e795852024-08-20 20:57:23 +02001638 for (i = 0; i < (int)ARRAY_LENGTH(mod_entry_tab); ++i)
1639 if (cmod->cmod_flags & mod_entry_tab[i].key)
1640 buflen += add_cmd_modifier(buf, buflen, mod_entry_tab[i].value, mod_entry_tab[i].length, &multi_mods);
Bram Moolenaar02194d22020-10-24 23:08:38 +02001641
1642 // :silent
1643 if (cmod->cmod_flags & CMOD_SILENT)
John Marriott9e795852024-08-20 20:57:23 +02001644 {
1645 if (cmod->cmod_flags & CMOD_ERRSILENT)
1646 buflen += add_cmd_modifier(buf, buflen, "silent!", STRLEN_LITERAL("silent!"), &multi_mods);
1647 else
1648 buflen += add_cmd_modifier(buf, buflen, "silent", STRLEN_LITERAL("silent"), &multi_mods);
1649 }
1650
Bram Moolenaar02194d22020-10-24 23:08:38 +02001651 // :verbose
zeertzjq9359e8a2022-07-03 13:16:09 +01001652 if (cmod->cmod_verbose > 0)
1653 {
1654 int verbose_value = cmod->cmod_verbose - 1;
1655
1656 if (verbose_value == 1)
John Marriott9e795852024-08-20 20:57:23 +02001657 buflen += add_cmd_modifier(buf, buflen, "verbose", STRLEN_LITERAL("verbose"), &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001658 else
1659 {
1660 char verbose_buf[NUMBUFLEN];
John Marriott9e795852024-08-20 20:57:23 +02001661 size_t verbose_buflen;
zeertzjq9359e8a2022-07-03 13:16:09 +01001662
John Marriott9e795852024-08-20 20:57:23 +02001663 verbose_buflen = vim_snprintf(verbose_buf, sizeof(verbose_buf), "%dverbose", verbose_value);
1664 buflen += add_cmd_modifier(buf, buflen, verbose_buf, verbose_buflen, &multi_mods);
zeertzjq9359e8a2022-07-03 13:16:09 +01001665 }
1666 }
zeertzjq9359e8a2022-07-03 13:16:09 +01001667
John Marriott9e795852024-08-20 20:57:23 +02001668 // flags from cmod->cmod_split
1669 buflen += add_win_cmd_modifiers((buf == NULL) ? NULL : buf + buflen, cmod, &multi_mods);
1670
1671 if (quote)
Bram Moolenaar02194d22020-10-24 23:08:38 +02001672 {
John Marriott9e795852024-08-20 20:57:23 +02001673 if (buf == NULL)
1674 ++buflen;
1675 else
1676 {
1677 *(buf + buflen) = '"';
1678 ++buflen;
1679 *(buf + buflen) = NUL;
1680 }
Bram Moolenaar02194d22020-10-24 23:08:38 +02001681 }
John Marriott9e795852024-08-20 20:57:23 +02001682
1683 return buflen;
Bram Moolenaar02194d22020-10-24 23:08:38 +02001684}
1685
1686/*
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001687 * Check for a <> code in a user command.
1688 * "code" points to the '<'. "len" the length of the <> (inclusive).
1689 * "buf" is where the result is to be added.
1690 * "split_buf" points to a buffer used for splitting, caller should free it.
1691 * "split_len" is the length of what "split_buf" contains.
1692 * Returns the length of the replacement, which has been added to "buf".
1693 * Returns -1 if there was no match, and only the "<" has been copied.
1694 */
1695 static size_t
1696uc_check_code(
1697 char_u *code,
1698 size_t len,
1699 char_u *buf,
1700 ucmd_T *cmd, // the user command we're expanding
1701 exarg_T *eap, // ex arguments
1702 char_u **split_buf,
1703 size_t *split_len)
1704{
1705 size_t result = 0;
1706 char_u *p = code + 1;
1707 size_t l = len - 2;
1708 int quote = 0;
1709 enum {
1710 ct_ARGS,
1711 ct_BANG,
1712 ct_COUNT,
1713 ct_LINE1,
1714 ct_LINE2,
1715 ct_RANGE,
1716 ct_MODS,
1717 ct_REGISTER,
1718 ct_LT,
1719 ct_NONE
1720 } type = ct_NONE;
1721
1722 if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-')
1723 {
1724 quote = (*p == 'q' || *p == 'Q') ? 1 : 2;
1725 p += 2;
1726 l -= 2;
1727 }
1728
1729 ++l;
1730 if (l <= 1)
1731 type = ct_NONE;
1732 else if (STRNICMP(p, "args>", l) == 0)
1733 type = ct_ARGS;
1734 else if (STRNICMP(p, "bang>", l) == 0)
1735 type = ct_BANG;
1736 else if (STRNICMP(p, "count>", l) == 0)
1737 type = ct_COUNT;
1738 else if (STRNICMP(p, "line1>", l) == 0)
1739 type = ct_LINE1;
1740 else if (STRNICMP(p, "line2>", l) == 0)
1741 type = ct_LINE2;
1742 else if (STRNICMP(p, "range>", l) == 0)
1743 type = ct_RANGE;
1744 else if (STRNICMP(p, "lt>", l) == 0)
1745 type = ct_LT;
1746 else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0)
1747 type = ct_REGISTER;
1748 else if (STRNICMP(p, "mods>", l) == 0)
1749 type = ct_MODS;
1750
1751 switch (type)
1752 {
1753 case ct_ARGS:
1754 // Simple case first
1755 if (*eap->arg == NUL)
1756 {
1757 if (quote == 1)
1758 {
1759 result = 2;
1760 if (buf != NULL)
1761 STRCPY(buf, "''");
1762 }
1763 else
1764 result = 0;
1765 break;
1766 }
1767
1768 // When specified there is a single argument don't split it.
1769 // Works for ":Cmd %" when % is "a b c".
Bram Moolenaar8071cb22019-07-12 17:58:01 +02001770 if ((eap->argt & EX_NOSPC) && quote == 2)
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001771 quote = 1;
1772
1773 switch (quote)
1774 {
1775 case 0: // No quoting, no splitting
1776 result = STRLEN(eap->arg);
1777 if (buf != NULL)
1778 STRCPY(buf, eap->arg);
1779 break;
1780 case 1: // Quote, but don't split
1781 result = STRLEN(eap->arg) + 2;
1782 for (p = eap->arg; *p; ++p)
1783 {
1784 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1785 // DBCS can contain \ in a trail byte, skip the
1786 // double-byte character.
1787 ++p;
1788 else
1789 if (*p == '\\' || *p == '"')
1790 ++result;
1791 }
1792
1793 if (buf != NULL)
1794 {
1795 *buf++ = '"';
1796 for (p = eap->arg; *p; ++p)
1797 {
1798 if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2)
1799 // DBCS can contain \ in a trail byte, copy the
1800 // double-byte character to avoid escaping.
1801 *buf++ = *p++;
1802 else
1803 if (*p == '\\' || *p == '"')
1804 *buf++ = '\\';
1805 *buf++ = *p;
1806 }
1807 *buf = '"';
1808 }
1809
1810 break;
1811 case 2: // Quote and split (<f-args>)
1812 // This is hard, so only do it once, and cache the result
1813 if (*split_buf == NULL)
1814 *split_buf = uc_split_args(eap->arg, split_len);
1815
1816 result = *split_len;
1817 if (buf != NULL && result != 0)
1818 STRCPY(buf, *split_buf);
1819
1820 break;
1821 }
1822 break;
1823
1824 case ct_BANG:
1825 result = eap->forceit ? 1 : 0;
1826 if (quote)
1827 result += 2;
1828 if (buf != NULL)
1829 {
1830 if (quote)
1831 *buf++ = '"';
1832 if (eap->forceit)
1833 *buf++ = '!';
1834 if (quote)
1835 *buf = '"';
1836 }
1837 break;
1838
1839 case ct_LINE1:
1840 case ct_LINE2:
1841 case ct_RANGE:
1842 case ct_COUNT:
1843 {
John Marriott9e795852024-08-20 20:57:23 +02001844 char num_buf[NUMBUFLEN];
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001845 long num = (type == ct_LINE1) ? eap->line1 :
1846 (type == ct_LINE2) ? eap->line2 :
1847 (type == ct_RANGE) ? eap->addr_count :
1848 (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
1849 size_t num_len;
1850
John Marriott9e795852024-08-20 20:57:23 +02001851 num_len = vim_snprintf(num_buf, sizeof(num_buf), "%ld", num);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001852 result = num_len;
1853
1854 if (quote)
1855 result += 2;
1856
1857 if (buf != NULL)
1858 {
1859 if (quote)
1860 *buf++ = '"';
1861 STRCPY(buf, num_buf);
1862 buf += num_len;
1863 if (quote)
1864 *buf = '"';
1865 }
1866
1867 break;
1868 }
1869
1870 case ct_MODS:
1871 {
Bram Moolenaar02194d22020-10-24 23:08:38 +02001872 result = produce_cmdmods(buf, &cmdmod, quote);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001873 break;
1874 }
1875
1876 case ct_REGISTER:
1877 result = eap->regname ? 1 : 0;
1878 if (quote)
1879 result += 2;
1880 if (buf != NULL)
1881 {
1882 if (quote)
1883 *buf++ = '\'';
1884 if (eap->regname)
1885 *buf++ = eap->regname;
1886 if (quote)
1887 *buf = '\'';
1888 }
1889 break;
1890
1891 case ct_LT:
1892 result = 1;
1893 if (buf != NULL)
1894 *buf = '<';
1895 break;
1896
1897 default:
1898 // Not recognized: just copy the '<' and return -1.
1899 result = (size_t)-1;
1900 if (buf != NULL)
1901 *buf = '<';
1902 break;
1903 }
1904
1905 return result;
1906}
1907
1908/*
1909 * Execute a user defined command.
1910 */
1911 void
1912do_ucmd(exarg_T *eap)
1913{
1914 char_u *buf;
1915 char_u *p;
1916 char_u *q;
1917
1918 char_u *start;
1919 char_u *end = NULL;
1920 char_u *ksp;
1921 size_t len, totlen;
1922
1923 size_t split_len = 0;
1924 char_u *split_buf = NULL;
1925 ucmd_T *cmd;
Bram Moolenaar205f29c2021-12-10 21:46:09 +00001926 sctx_T save_current_sctx;
1927 int restore_current_sctx = FALSE;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00001928#ifdef FEAT_EVAL
1929 int restore_script_version = 0;
1930#endif
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001931
1932 if (eap->cmdidx == CMD_USER)
1933 cmd = USER_CMD(eap->useridx);
1934 else
zeertzjqb444ee72023-02-20 15:25:13 +00001935 cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02001936
1937 /*
1938 * Replace <> in the command by the arguments.
1939 * First round: "buf" is NULL, compute length, allocate "buf".
1940 * Second round: copy result into "buf".
1941 */
1942 buf = NULL;
1943 for (;;)
1944 {
1945 p = cmd->uc_rep; // source
1946 q = buf; // destination
1947 totlen = 0;
1948
1949 for (;;)
1950 {
1951 start = vim_strchr(p, '<');
1952 if (start != NULL)
1953 end = vim_strchr(start + 1, '>');
1954 if (buf != NULL)
1955 {
1956 for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp)
1957 ;
1958 if (*ksp == K_SPECIAL
1959 && (start == NULL || ksp < start || end == NULL)
1960 && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER)
1961# ifdef FEAT_GUI
1962 || (ksp[1] == KS_EXTRA && ksp[2] == (int)KE_CSI)
1963# endif
1964 ))
1965 {
1966 // K_SPECIAL has been put in the buffer as K_SPECIAL
1967 // KS_SPECIAL KE_FILLER, like for mappings, but
1968 // do_cmdline() doesn't handle that, so convert it back.
1969 // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI.
1970 len = ksp - p;
1971 if (len > 0)
1972 {
1973 mch_memmove(q, p, len);
1974 q += len;
1975 }
1976 *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI;
1977 p = ksp + 3;
1978 continue;
1979 }
1980 }
1981
1982 // break if no <item> is found
1983 if (start == NULL || end == NULL)
1984 break;
1985
1986 // Include the '>'
1987 ++end;
1988
1989 // Take everything up to the '<'
1990 len = start - p;
1991 if (buf == NULL)
1992 totlen += len;
1993 else
1994 {
1995 mch_memmove(q, p, len);
1996 q += len;
1997 }
1998
1999 len = uc_check_code(start, end - start, q, cmd, eap,
2000 &split_buf, &split_len);
2001 if (len == (size_t)-1)
2002 {
2003 // no match, continue after '<'
2004 p = start + 1;
2005 len = 1;
2006 }
2007 else
2008 p = end;
2009 if (buf == NULL)
2010 totlen += len;
2011 else
2012 q += len;
2013 }
2014 if (buf != NULL) // second time here, finished
2015 {
2016 STRCPY(q, p);
2017 break;
2018 }
2019
2020 totlen += STRLEN(p); // Add on the trailing characters
Bram Moolenaar964b3742019-05-24 18:54:09 +02002021 buf = alloc(totlen + 1);
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002022 if (buf == NULL)
2023 {
2024 vim_free(split_buf);
2025 return;
2026 }
2027 }
2028
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002029 if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0)
2030 {
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002031 restore_current_sctx = TRUE;
2032 save_current_sctx = current_sctx;
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002033 current_sctx.sc_version = cmd->uc_script_ctx.sc_version;
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002034#ifdef FEAT_EVAL
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002035 current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002036 if (cmd->uc_flags & UC_VIM9)
2037 {
2038 // In a {} block variables use Vim9 script rules, even in a legacy
2039 // script.
2040 restore_script_version =
2041 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version;
2042 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version = SCRIPT_VERSION_VIM9;
2043 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002044#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002045 }
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002046
Zoltan Arpadffy6fdb6282023-12-19 20:53:07 +01002047 (void)do_cmdline(buf, eap->ea_getline, eap->cookie,
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002048 DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
Bram Moolenaar205f29c2021-12-10 21:46:09 +00002049
2050 // Careful: Do not use "cmd" here, it may have become invalid if a user
2051 // command was added.
2052 if (restore_current_sctx)
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002053 {
2054#ifdef FEAT_EVAL
2055 if (restore_script_version != 0)
2056 SCRIPT_ITEM(current_sctx.sc_sid)->sn_version =
2057 restore_script_version;
2058#endif
Bram Moolenaar58ef8a32021-11-12 11:25:11 +00002059 current_sctx = save_current_sctx;
Bram Moolenaar98b7fe72022-03-23 21:36:27 +00002060 }
Bram Moolenaarac9fb182019-04-27 13:04:13 +02002061 vim_free(buf);
2062 vim_free(split_buf);
2063}