command.c (44625B)
1 #include <inttypes.h> 2 #include <lauxlib.h> 3 #include <stdbool.h> 4 #include <stdio.h> 5 #include <string.h> 6 7 #include "klib/kvec.h" 8 #include "nvim/api/command.h" 9 #include "nvim/api/keysets_defs.h" 10 #include "nvim/api/private/defs.h" 11 #include "nvim/api/private/dispatch.h" 12 #include "nvim/api/private/helpers.h" 13 #include "nvim/api/private/validate.h" 14 #include "nvim/ascii_defs.h" 15 #include "nvim/autocmd.h" 16 #include "nvim/autocmd_defs.h" 17 #include "nvim/buffer_defs.h" 18 #include "nvim/charset.h" 19 #include "nvim/cmdexpand_defs.h" 20 #include "nvim/ex_cmds_defs.h" 21 #include "nvim/ex_docmd.h" 22 #include "nvim/ex_eval.h" 23 #include "nvim/garray.h" 24 #include "nvim/garray_defs.h" 25 #include "nvim/globals.h" 26 #include "nvim/lua/executor.h" 27 #include "nvim/macros_defs.h" 28 #include "nvim/mbyte.h" 29 #include "nvim/memory.h" 30 #include "nvim/memory_defs.h" 31 #include "nvim/pos_defs.h" 32 #include "nvim/regexp.h" 33 #include "nvim/register.h" 34 #include "nvim/strings.h" 35 #include "nvim/types_defs.h" 36 #include "nvim/usercmd.h" 37 #include "nvim/vim_defs.h" 38 #include "nvim/window.h" 39 40 #include "api/command.c.generated.h" 41 42 /// Parse arguments for :map/:abbrev commands, preserving whitespace in RHS. 43 /// @param arg_str The argument string to parse 44 /// @param arena Arena allocator 45 /// @return Array with at most 2 elements: [lhs, rhs] 46 static Array parse_map_cmd(const char *arg_str, Arena *arena) 47 { 48 Array args = arena_array(arena, 2); 49 50 char *lhs_start = (char *)arg_str; 51 char *lhs_end = skiptowhite(lhs_start); 52 size_t lhs_len = (size_t)(lhs_end - lhs_start); 53 54 // Add the LHS (first argument) 55 ADD_C(args, STRING_OBJ(cstrn_as_string(lhs_start, lhs_len))); 56 57 // Add the RHS (second argument) if it exists, preserving all whitespace 58 char *rhs_start = skipwhite(lhs_end); 59 if (*rhs_start != NUL) { 60 size_t rhs_len = strlen(rhs_start); 61 ADD_C(args, STRING_OBJ(cstrn_as_string(rhs_start, rhs_len))); 62 } 63 64 return args; 65 } 66 67 /// Parse command line. 68 /// 69 /// Doesn't check the validity of command arguments. 70 /// 71 /// @param str Command line string to parse. Cannot contain "\n". 72 /// @param opts Optional parameters. Reserved for future use. 73 /// @param[out] err Error details, if any. 74 /// @return Dict containing command information, with these keys: 75 /// - cmd: (string) Command name. 76 /// - range: (array) (optional) Command range ([<line1>] [<line2>]). 77 /// Omitted if command doesn't accept a range. 78 /// Otherwise, has no elements if no range was specified, one element if 79 /// only a single range item was specified, or two elements if both range 80 /// items were specified. 81 /// - count: (number) (optional) Command [<count>]. 82 /// Omitted if command cannot take a count. 83 /// - reg: (string) (optional) Command [<register>]. 84 /// Omitted if command cannot take a register. 85 /// - bang: (boolean) Whether command contains a [<bang>] (!) modifier. 86 /// - args: (array) Command arguments. 87 /// - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines. 88 /// - nargs: (string) Value of |:command-nargs|. 89 /// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. 90 /// Empty if there isn't a next command. 91 /// - magic: (dict) Which characters have special meaning in the command arguments. 92 /// - file: (boolean) The command expands filenames. Which means characters such as "%", 93 /// "#" and wildcards are expanded. 94 /// - bar: (boolean) The "|" character is treated as a command separator and the double 95 /// quote character (") is treated as the start of a comment. 96 /// - mods: (dict) |:command-modifiers|. 97 /// - filter: (dict) |:filter|. 98 /// - pattern: (string) Filter pattern. Empty string if there is no filter. 99 /// - force: (boolean) Whether filter is inverted or not. 100 /// - silent: (boolean) |:silent|. 101 /// - emsg_silent: (boolean) |:silent!|. 102 /// - unsilent: (boolean) |:unsilent|. 103 /// - sandbox: (boolean) |:sandbox|. 104 /// - noautocmd: (boolean) |:noautocmd|. 105 /// - browse: (boolean) |:browse|. 106 /// - confirm: (boolean) |:confirm|. 107 /// - hide: (boolean) |:hide|. 108 /// - horizontal: (boolean) |:horizontal|. 109 /// - keepalt: (boolean) |:keepalt|. 110 /// - keepjumps: (boolean) |:keepjumps|. 111 /// - keepmarks: (boolean) |:keepmarks|. 112 /// - keeppatterns: (boolean) |:keeppatterns|. 113 /// - lockmarks: (boolean) |:lockmarks|. 114 /// - noswapfile: (boolean) |:noswapfile|. 115 /// - tab: (integer) |:tab|. -1 when omitted. 116 /// - verbose: (integer) |:verbose|. -1 when omitted. 117 /// - vertical: (boolean) |:vertical|. 118 /// - split: (string) Split modifier string, is an empty string when there's no split 119 /// modifier. If there is a split modifier it can be one of: 120 /// - "aboveleft": |:aboveleft|. 121 /// - "belowright": |:belowright|. 122 /// - "topleft": |:topleft|. 123 /// - "botright": |:botright|. 124 Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err) 125 FUNC_API_SINCE(10) FUNC_API_FAST 126 { 127 Dict(cmd) result = KEYDICT_INIT; 128 129 // Parse command line 130 exarg_T ea; 131 CmdParseInfo cmdinfo; 132 char *cmdline = arena_memdupz(arena, str.data, str.size); 133 const char *errormsg = NULL; 134 135 if (!parse_cmdline(&cmdline, &ea, &cmdinfo, &errormsg)) { 136 if (errormsg != NULL) { 137 api_set_error(err, kErrorTypeException, "Parsing command-line: %s", errormsg); 138 } else { 139 api_set_error(err, kErrorTypeException, "Parsing command-line"); 140 } 141 goto end; 142 } 143 144 // Parse arguments 145 Array args = ARRAY_DICT_INIT; 146 size_t length = strlen(ea.arg); 147 148 // Check if this is a mapping command that needs special handling 149 // like mapping commands need special argument parsing to preserve whitespace in RHS: 150 // "map a b c" => { args=["a", "b c"], ... } 151 if (is_map_cmd(ea.cmdidx) && *ea.arg != NUL) { 152 // For mapping commands, split differently to preserve whitespace 153 args = parse_map_cmd(ea.arg, arena); 154 } else if (ea.argt & EX_NOSPC) { 155 // For nargs = 1 or '?', pass the entire argument list as a single argument, 156 // otherwise split arguments by whitespace. 157 if (*ea.arg != NUL) { 158 args = arena_array(arena, 1); 159 ADD_C(args, STRING_OBJ(cstrn_as_string(ea.arg, length))); 160 } 161 } else { 162 size_t end = 0; 163 size_t len = 0; 164 char *buf = arena_alloc(arena, length + 1, false); 165 bool done = false; 166 args = arena_array(arena, uc_nargs_upper_bound(ea.arg, length)); 167 168 while (!done) { 169 done = uc_split_args_iter(ea.arg, length, &end, buf, &len); 170 if (len > 0) { 171 ADD_C(args, STRING_OBJ(cstrn_as_string(buf, len))); 172 buf += len + 1; 173 } 174 } 175 } 176 177 ucmd_T *cmd = NULL; 178 if (ea.cmdidx == CMD_USER) { 179 cmd = USER_CMD(ea.useridx); 180 } else if (ea.cmdidx == CMD_USER_BUF) { 181 cmd = USER_CMD_GA(&curbuf->b_ucmds, ea.useridx); 182 } 183 184 char *name = (cmd != NULL ? cmd->uc_name : get_command_name(NULL, ea.cmdidx)); 185 PUT_KEY(result, cmd, cmd, cstr_as_string(name)); 186 187 if ((ea.argt & EX_RANGE) && ea.addr_count > 0) { 188 Array range = arena_array(arena, 2); 189 if (ea.addr_count > 1) { 190 ADD_C(range, INTEGER_OBJ(ea.line1)); 191 } 192 ADD_C(range, INTEGER_OBJ(ea.line2)); 193 PUT_KEY(result, cmd, range, range); 194 } 195 196 if (ea.argt & EX_COUNT) { 197 Integer count = ea.addr_count > 0 ? ea.line2 : (cmd != NULL ? cmd->uc_def : 0); 198 // For built-in commands, if count is not explicitly provided and the default value is 0, 199 // do not include the count field in the result, so the command uses its built-in default 200 // behavior. 201 if (ea.addr_count > 0 || (cmd != NULL && cmd->uc_def != 0) || count != 0) { 202 PUT_KEY(result, cmd, count, count); 203 } 204 } 205 206 if (ea.argt & EX_REGSTR) { 207 char reg[2] = { (char)ea.regname, NUL }; 208 PUT_KEY(result, cmd, reg, CSTR_TO_ARENA_STR(arena, reg)); 209 } 210 211 PUT_KEY(result, cmd, bang, ea.forceit); 212 PUT_KEY(result, cmd, args, args); 213 214 char nargs[2]; 215 if (ea.argt & EX_EXTRA) { 216 if (ea.argt & EX_NOSPC) { 217 if (ea.argt & EX_NEEDARG) { 218 nargs[0] = '1'; 219 } else { 220 nargs[0] = '?'; 221 } 222 } else if (ea.argt & EX_NEEDARG) { 223 nargs[0] = '+'; 224 } else { 225 nargs[0] = '*'; 226 } 227 } else { 228 nargs[0] = '0'; 229 } 230 nargs[1] = NUL; 231 PUT_KEY(result, cmd, nargs, CSTR_TO_ARENA_OBJ(arena, nargs)); 232 233 char *addr; 234 switch (ea.addr_type) { 235 case ADDR_LINES: 236 addr = "line"; 237 break; 238 case ADDR_ARGUMENTS: 239 addr = "arg"; 240 break; 241 case ADDR_BUFFERS: 242 addr = "buf"; 243 break; 244 case ADDR_LOADED_BUFFERS: 245 addr = "load"; 246 break; 247 case ADDR_WINDOWS: 248 addr = "win"; 249 break; 250 case ADDR_TABS: 251 addr = "tab"; 252 break; 253 case ADDR_QUICKFIX: 254 addr = "qf"; 255 break; 256 case ADDR_NONE: 257 addr = "none"; 258 break; 259 default: 260 addr = "?"; 261 break; 262 } 263 PUT_KEY(result, cmd, addr, cstr_as_string(addr)); 264 PUT_KEY(result, cmd, nextcmd, cstr_as_string(ea.nextcmd)); 265 266 // TODO(bfredl): nested keydict would be nice.. 267 Dict mods = arena_dict(arena, 20); 268 269 Dict filter = arena_dict(arena, 2); 270 PUT_C(filter, "pattern", CSTR_TO_ARENA_OBJ(arena, cmdinfo.cmdmod.cmod_filter_pat)); 271 PUT_C(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force)); 272 PUT_C(mods, "filter", DICT_OBJ(filter)); 273 274 PUT_C(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT)); 275 PUT_C(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT)); 276 PUT_C(mods, "unsilent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_UNSILENT)); 277 PUT_C(mods, "sandbox", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX)); 278 PUT_C(mods, "noautocmd", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOAUTOCMD)); 279 PUT_C(mods, "tab", INTEGER_OBJ(cmdinfo.cmdmod.cmod_tab - 1)); 280 PUT_C(mods, "verbose", INTEGER_OBJ(cmdinfo.cmdmod.cmod_verbose - 1)); 281 PUT_C(mods, "browse", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_BROWSE)); 282 PUT_C(mods, "confirm", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_CONFIRM)); 283 PUT_C(mods, "hide", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_HIDE)); 284 PUT_C(mods, "keepalt", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPALT)); 285 PUT_C(mods, "keepjumps", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPJUMPS)); 286 PUT_C(mods, "keepmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPMARKS)); 287 PUT_C(mods, "keeppatterns", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_KEEPPATTERNS)); 288 PUT_C(mods, "lockmarks", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_LOCKMARKS)); 289 PUT_C(mods, "noswapfile", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_NOSWAPFILE)); 290 PUT_C(mods, "vertical", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_VERT)); 291 PUT_C(mods, "horizontal", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_split & WSP_HOR)); 292 293 char *split; 294 if (cmdinfo.cmdmod.cmod_split & WSP_BOT) { 295 split = "botright"; 296 } else if (cmdinfo.cmdmod.cmod_split & WSP_TOP) { 297 split = "topleft"; 298 } else if (cmdinfo.cmdmod.cmod_split & WSP_BELOW) { 299 split = "belowright"; 300 } else if (cmdinfo.cmdmod.cmod_split & WSP_ABOVE) { 301 split = "aboveleft"; 302 } else { 303 split = ""; 304 } 305 PUT_C(mods, "split", CSTR_AS_OBJ(split)); 306 307 PUT_KEY(result, cmd, mods, mods); 308 309 Dict magic = arena_dict(arena, 2); 310 PUT_C(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file)); 311 PUT_C(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar)); 312 PUT_KEY(result, cmd, magic, magic); 313 314 undo_cmdmod(&cmdinfo.cmdmod); 315 end: 316 return result; 317 } 318 319 /// Executes an Ex command. 320 /// 321 /// Unlike |nvim_command()| this command takes a structured Dict instead of a String. This 322 /// allows for easier construction and manipulation of an Ex command. This also allows for things 323 /// such as having spaces inside a command argument, expanding filenames in a command that otherwise 324 /// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String. 325 /// 326 /// The first argument may also be used instead of count for commands that support it in order to 327 /// make their usage simpler with |vim.cmd()|. For example, instead of 328 /// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`. 329 /// 330 /// On execution error: fails with Vimscript error, updates v:errmsg. 331 /// 332 /// @see |nvim_exec2()| 333 /// @see |nvim_command()| 334 /// 335 /// @param cmd Command to execute. Must be a Dict that can contain the same values as 336 /// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd" 337 /// which are ignored if provided. All values except for "cmd" are optional. 338 /// @param opts Optional parameters. 339 /// - output: (boolean, default false) Whether to return command output. 340 /// @param[out] err Error details, if any. 341 /// @return Command output (non-error, non-shell |:!|) if `output` is true, else empty string. 342 String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena *arena, Error *err) 343 FUNC_API_SINCE(10) 344 { 345 exarg_T ea; 346 CLEAR_FIELD(ea); 347 348 CmdParseInfo cmdinfo; 349 CLEAR_FIELD(cmdinfo); 350 351 char *cmdline = NULL; 352 char *cmdname = NULL; 353 ArrayOf(String) args = ARRAY_DICT_INIT; 354 355 String retv = (String)STRING_INIT; 356 357 #define OBJ_TO_BOOL(var, value, default, varname) \ 358 do { \ 359 (var) = api_object_to_bool(value, varname, default, err); \ 360 if (ERROR_SET(err)) { \ 361 goto end; \ 362 } \ 363 } while (0) 364 365 #define VALIDATE_MOD(cond, mod_, name_) \ 366 do { \ 367 if (!(cond)) { \ 368 api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \ 369 goto end; \ 370 } \ 371 } while (0) 372 373 VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", { 374 goto end; 375 }); 376 VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, { 377 goto end; 378 }); 379 380 cmdname = arena_string(arena, cmd->cmd).data; 381 ea.cmd = cmdname; 382 383 char *p = find_ex_command(&ea, NULL); 384 385 // If this looks like an undefined user command and there are CmdUndefined 386 // autocommands defined, trigger the matching autocommands. 387 if (p != NULL && ea.cmdidx == CMD_SIZE && ASCII_ISUPPER(*ea.cmd) 388 && has_event(EVENT_CMDUNDEFINED)) { 389 p = arena_string(arena, cmd->cmd).data; 390 int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL); 391 // If the autocommands did something and didn't cause an error, try 392 // finding the command again. 393 p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd; 394 } 395 396 VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, { 397 goto end; 398 }); 399 VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, { 400 goto end; 401 }); 402 const char *fullname = IS_USER_CMDIDX(ea.cmdidx) 403 ? get_user_command_name(ea.useridx, ea.cmdidx) 404 : get_command_name(NULL, ea.cmdidx); 405 VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, { 406 goto end; 407 }); 408 409 // Get the command flags so that we can know what type of arguments the command uses. 410 // Not required for a user command since `find_ex_command` already deals with it in that case. 411 if (!IS_USER_CMDIDX(ea.cmdidx)) { 412 ea.argt = get_cmd_argt(ea.cmdidx); 413 } 414 415 // Track whether the first argument was interpreted as count to avoid conflicts 416 bool count_from_first_arg = false; 417 // Parse command arguments since it's needed to get the command address type. 418 if (HAS_KEY(cmd, cmd, args)) { 419 // Special handling: for commands that support count but not regular arguments, 420 // if a single numeric argument is provided, interpret it as count 421 if (cmd->args.size == 1 && (ea.argt & EX_COUNT) && !(ea.argt & EX_EXTRA)) { 422 Object first_arg = cmd->args.items[0]; 423 bool is_numeric = false; 424 int64_t count_value = 0; 425 426 if (first_arg.type == kObjectTypeInteger) { 427 is_numeric = true; 428 count_value = first_arg.data.integer; 429 } else if (first_arg.type == kObjectTypeString) { 430 // Try to parse string as a number Example: vim.api.nvim_cmd({cmd = 'copen', args = {'10'}}, {}) 431 char *endptr; 432 long val = strtol(first_arg.data.string.data, &endptr, 10); 433 // Check if entire string was consumed (valid number) and string is not empty 434 if (*endptr == '\0' && first_arg.data.string.size > 0) { 435 is_numeric = true; 436 count_value = val; 437 } 438 } 439 440 if (is_numeric && count_value >= 0) { 441 // Interpret the argument as count 442 count_from_first_arg = true; 443 ea.addr_count = 1; 444 ea.line1 = ea.line2 = (linenr_T)count_value; 445 args = arena_array(arena, 0); 446 } 447 } 448 449 if (!count_from_first_arg) { 450 // Process all arguments. Convert non-String arguments to String and check if String arguments 451 // have non-whitespace characters. 452 args = arena_array(arena, cmd->args.size); 453 for (size_t i = 0; i < cmd->args.size; i++) { 454 Object elem = cmd->args.items[i]; 455 char *data_str; 456 457 switch (elem.type) { 458 case kObjectTypeBoolean: 459 data_str = arena_alloc(arena, 2, false); 460 data_str[0] = elem.data.boolean ? '1' : '0'; 461 data_str[1] = NUL; 462 ADD_C(args, CSTR_AS_OBJ(data_str)); 463 break; 464 case kObjectTypeBuffer: 465 case kObjectTypeWindow: 466 case kObjectTypeTabpage: 467 case kObjectTypeInteger: 468 data_str = arena_alloc(arena, NUMBUFLEN, false); 469 snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer); 470 ADD_C(args, CSTR_AS_OBJ(data_str)); 471 break; 472 case kObjectTypeString: 473 VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, { 474 goto end; 475 }); 476 ADD_C(args, elem); 477 break; 478 default: 479 VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), { 480 goto end; 481 }); 482 break; 483 } 484 } 485 486 bool argc_valid; 487 488 // Check if correct number of arguments is used. 489 switch (ea.argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { 490 case EX_EXTRA | EX_NOSPC | EX_NEEDARG: 491 argc_valid = args.size == 1; 492 break; 493 case EX_EXTRA | EX_NOSPC: 494 argc_valid = args.size <= 1; 495 break; 496 case EX_EXTRA | EX_NEEDARG: 497 argc_valid = args.size >= 1; 498 break; 499 case EX_EXTRA: 500 argc_valid = true; 501 break; 502 default: 503 argc_valid = args.size == 0; 504 break; 505 } 506 507 VALIDATE(argc_valid, "%s", "Wrong number of arguments", { 508 goto end; 509 }); 510 } 511 } 512 513 // Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()` 514 // since it only ever checks the first argument. 515 set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL); 516 517 if (HAS_KEY(cmd, cmd, range)) { 518 VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data); 519 VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, { 520 goto end; 521 }); 522 523 Array range = cmd->range; 524 ea.addr_count = (int)range.size; 525 526 for (size_t i = 0; i < range.size; i++) { 527 Object elem = range.items[i]; 528 VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0), 529 "range element", "non-negative Integer", NULL, { 530 goto end; 531 }); 532 } 533 534 if (range.size > 0) { 535 ea.line1 = (linenr_T)range.items[0].data.integer; 536 ea.line2 = (linenr_T)range.items[range.size - 1].data.integer; 537 } 538 539 VALIDATE_S((invalid_range(&ea) == NULL), "range", "", { 540 goto end; 541 }); 542 } 543 if (ea.addr_count == 0) { 544 if (ea.argt & EX_DFLALL) { 545 set_cmd_dflall_range(&ea); // Default range for range=% 546 } else { 547 ea.line1 = ea.line2 = get_cmd_default_range(&ea); // Default range. 548 549 if (ea.addr_type == ADDR_OTHER) { 550 // Default is 1, not cursor. 551 ea.line2 = 1; 552 } 553 } 554 } 555 556 if (HAS_KEY(cmd, cmd, count)) { 557 VALIDATE(!count_from_first_arg, "%s", "Cannot specify both 'count' and numeric argument", { 558 goto end; 559 }); 560 VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data); 561 VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, { 562 goto end; 563 }); 564 set_cmd_count(&ea, (linenr_T)cmd->count, true); 565 } 566 567 if (HAS_KEY(cmd, cmd, reg)) { 568 VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data); 569 VALIDATE_EXP((cmd->reg.size == 1), 570 "reg", "single character", cmd->reg.data, { 571 goto end; 572 }); 573 char regname = cmd->reg.data[0]; 574 VALIDATE((regname != '='), "%s", "Cannot use register \"=", { 575 goto end; 576 }); 577 VALIDATE(valid_yank_reg(regname, 578 (!IS_USER_CMDIDX(ea.cmdidx) 579 && ea.cmdidx != CMD_put && ea.cmdidx != CMD_iput)), 580 "Invalid register: \"%c", regname, { 581 goto end; 582 }); 583 ea.regname = (uint8_t)regname; 584 } 585 586 ea.forceit = cmd->bang; 587 VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data); 588 589 if (HAS_KEY(cmd, cmd, magic)) { 590 Dict(cmd_magic) magic[1] = KEYDICT_INIT; 591 if (!api_dict_to_keydict(magic, DictHash(cmd_magic), cmd->magic, err)) { 592 goto end; 593 } 594 595 cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE); 596 cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR); 597 if (cmdinfo.magic.file) { 598 ea.argt |= EX_XFILE; 599 } else { 600 ea.argt &= ~EX_XFILE; 601 } 602 } else { 603 cmdinfo.magic.file = ea.argt & EX_XFILE; 604 cmdinfo.magic.bar = ea.argt & EX_TRLBAR; 605 } 606 607 if (HAS_KEY(cmd, cmd, mods)) { 608 Dict(cmd_mods) mods[1] = KEYDICT_INIT; 609 if (!api_dict_to_keydict(mods, DictHash(cmd_mods), cmd->mods, err)) { 610 goto end; 611 } 612 613 if (HAS_KEY(mods, cmd_mods, filter)) { 614 Dict(cmd_mods_filter) filter[1] = KEYDICT_INIT; 615 616 if (!api_dict_to_keydict(&filter, DictHash(cmd_mods_filter), 617 mods->filter, err)) { 618 goto end; 619 } 620 621 if (HAS_KEY(filter, cmd_mods_filter, pattern)) { 622 cmdinfo.cmdmod.cmod_filter_force = filter->force; 623 624 // "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter 625 // is inverted. 626 if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) { 627 cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern); 628 cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat, 629 RE_MAGIC); 630 } 631 } 632 } 633 634 if (HAS_KEY(mods, cmd_mods, tab)) { 635 if ((int)mods->tab >= 0) { 636 // Silently ignore negative integers to allow mods.tab to be set to -1. 637 cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1; 638 } 639 } 640 641 if (HAS_KEY(mods, cmd_mods, verbose)) { 642 if ((int)mods->verbose >= 0) { 643 // Silently ignore negative integers to allow mods.verbose to be set to -1. 644 cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1; 645 } 646 } 647 648 cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0); 649 650 cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0); 651 652 if (HAS_KEY(mods, cmd_mods, split)) { 653 if (*mods->split.data == NUL) { 654 // Empty string, do nothing. 655 } else if (strcmp(mods->split.data, "aboveleft") == 0 656 || strcmp(mods->split.data, "leftabove") == 0) { 657 cmdinfo.cmdmod.cmod_split |= WSP_ABOVE; 658 } else if (strcmp(mods->split.data, "belowright") == 0 659 || strcmp(mods->split.data, "rightbelow") == 0) { 660 cmdinfo.cmdmod.cmod_split |= WSP_BELOW; 661 } else if (strcmp(mods->split.data, "topleft") == 0) { 662 cmdinfo.cmdmod.cmod_split |= WSP_TOP; 663 } else if (strcmp(mods->split.data, "botright") == 0) { 664 cmdinfo.cmdmod.cmod_split |= WSP_BOT; 665 } else { 666 VALIDATE_S(false, "mods.split", "", { 667 goto end; 668 }); 669 } 670 } 671 672 #define OBJ_TO_CMOD_FLAG(flag, value) \ 673 if (value) { \ 674 cmdinfo.cmdmod.cmod_flags |= (flag); \ 675 } 676 677 OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent); 678 OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent); 679 OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent); 680 OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox); 681 OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd); 682 OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse); 683 OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm); 684 OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide); 685 OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt); 686 OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps); 687 OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks); 688 OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns); 689 OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks); 690 OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile); 691 692 if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) { 693 // CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't 694 // work properly. 695 cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT; 696 } 697 698 VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)), 699 "%s", "Command cannot be run in sandbox", { 700 goto end; 701 }); 702 } 703 704 // Finally, build the command line string that will be stored inside ea.cmdlinep. 705 // This also sets the values of ea.cmd, ea.arg, ea.args and ea.arglens. 706 build_cmdline_str(&cmdline, &ea, &cmdinfo, args); 707 ea.cmdlinep = &cmdline; 708 709 // Check for "++opt=val" argument. 710 if (ea.argt & EX_ARGOPT) { 711 while (ea.arg[0] == '+' && ea.arg[1] == '+') { 712 char *orig_arg = ea.arg; 713 int result = getargopt(&ea); 714 VALIDATE_S(result != FAIL || is_cmd_ni(ea.cmdidx), "argument ", orig_arg, { 715 goto end; 716 }); 717 } 718 } 719 720 // Check for "+command" argument. 721 if ((ea.argt & EX_CMDARG) && !ea.usefilter) { 722 ea.do_ecmd_cmd = getargcmd(&ea.arg); 723 } 724 725 garray_T capture_local; 726 const int save_msg_silent = msg_silent; 727 const bool save_redir_off = redir_off; 728 garray_T * const save_capture_ga = capture_ga; 729 const int save_msg_col = msg_col; 730 731 if (opts->output) { 732 ga_init(&capture_local, 1, 80); 733 capture_ga = &capture_local; 734 } 735 736 TRY_WRAP(err, { 737 if (opts->output) { 738 msg_silent++; 739 redir_off = false; 740 msg_col = 0; // prevent leading spaces 741 } 742 743 WITH_SCRIPT_CONTEXT(channel_id, { 744 execute_cmd(&ea, &cmdinfo, false); 745 }); 746 747 if (opts->output) { 748 capture_ga = save_capture_ga; 749 msg_silent = save_msg_silent; 750 redir_off = save_redir_off; 751 // Put msg_col back where it was, since nothing should have been written. 752 msg_col = save_msg_col; 753 } 754 }); 755 756 if (ERROR_SET(err)) { 757 goto clear_ga; 758 } 759 760 if (opts->output && capture_local.ga_len > 1) { 761 // TODO(bfredl): if there are more cases like this we might want custom xfree-list in arena 762 retv = CBUF_TO_ARENA_STR(arena, capture_local.ga_data, (size_t)capture_local.ga_len); 763 // redir usually (except :echon) prepends a newline. 764 if (retv.data[0] == '\n') { 765 retv.data++; 766 retv.size--; 767 } 768 } 769 clear_ga: 770 if (opts->output) { 771 ga_clear(&capture_local); 772 } 773 end: 774 xfree(cmdline); 775 xfree(ea.args); 776 xfree(ea.arglens); 777 778 return retv; 779 780 #undef OBJ_TO_BOOL 781 #undef OBJ_TO_CMOD_FLAG 782 #undef VALIDATE_MOD 783 } 784 785 /// Check if a string contains only whitespace characters. 786 static bool string_iswhite(String str) 787 { 788 for (size_t i = 0; i < str.size; i++) { 789 if (!ascii_iswhite(str.data[i])) { 790 // Found a non-whitespace character 791 return false; 792 } else if (str.data[i] == NUL) { 793 // Terminate at first occurrence of a NUL character 794 break; 795 } 796 } 797 return true; 798 } 799 800 /// Build cmdline string for command, used by `nvim_cmd()`. 801 static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdinfo, 802 ArrayOf(String) args) 803 { 804 size_t argc = args.size; 805 StringBuilder cmdline = KV_INITIAL_VALUE; 806 kv_resize(cmdline, 32); // Make it big enough to handle most typical commands 807 808 // Add command modifiers 809 if (cmdinfo->cmdmod.cmod_tab != 0) { 810 kv_printf(cmdline, "%dtab ", cmdinfo->cmdmod.cmod_tab - 1); 811 } 812 if (cmdinfo->cmdmod.cmod_verbose > 0) { 813 kv_printf(cmdline, "%dverbose ", cmdinfo->cmdmod.cmod_verbose - 1); 814 } 815 816 if (cmdinfo->cmdmod.cmod_flags & CMOD_ERRSILENT) { 817 kv_concat(cmdline, "silent! "); 818 } else if (cmdinfo->cmdmod.cmod_flags & CMOD_SILENT) { 819 kv_concat(cmdline, "silent "); 820 } 821 822 if (cmdinfo->cmdmod.cmod_flags & CMOD_UNSILENT) { 823 kv_concat(cmdline, "unsilent "); 824 } 825 826 switch (cmdinfo->cmdmod.cmod_split & (WSP_ABOVE | WSP_BELOW | WSP_TOP | WSP_BOT)) { 827 case WSP_ABOVE: 828 kv_concat(cmdline, "aboveleft "); 829 break; 830 case WSP_BELOW: 831 kv_concat(cmdline, "belowright "); 832 break; 833 case WSP_TOP: 834 kv_concat(cmdline, "topleft "); 835 break; 836 case WSP_BOT: 837 kv_concat(cmdline, "botright "); 838 break; 839 default: 840 break; 841 } 842 843 #define CMDLINE_APPEND_IF(cond, str) \ 844 do { \ 845 if (cond) { \ 846 kv_concat(cmdline, str); \ 847 } \ 848 } while (0) 849 850 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_VERT, "vertical "); 851 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_split & WSP_HOR, "horizontal "); 852 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_SANDBOX, "sandbox "); 853 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOAUTOCMD, "noautocmd "); 854 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_BROWSE, "browse "); 855 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_CONFIRM, "confirm "); 856 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_HIDE, "hide "); 857 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPALT, "keepalt "); 858 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPJUMPS, "keepjumps "); 859 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPMARKS, "keepmarks "); 860 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_KEEPPATTERNS, "keeppatterns "); 861 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_LOCKMARKS, "lockmarks "); 862 CMDLINE_APPEND_IF(cmdinfo->cmdmod.cmod_flags & CMOD_NOSWAPFILE, "noswapfile "); 863 #undef CMDLINE_APPEND_IF 864 865 // Command range / count. 866 if (eap->argt & EX_RANGE) { 867 if (eap->addr_count == 1) { 868 kv_printf(cmdline, "%" PRIdLINENR, eap->line2); 869 } else if (eap->addr_count > 1) { 870 kv_printf(cmdline, "%" PRIdLINENR ",%" PRIdLINENR, eap->line1, eap->line2); 871 eap->addr_count = 2; // Make sure address count is not greater than 2 872 } 873 } 874 875 // Keep the index of the position where command name starts, so eap->cmd can point to it. 876 size_t cmdname_idx = cmdline.size; 877 kv_concat(cmdline, eap->cmd); 878 879 // Command bang. 880 if (eap->argt & EX_BANG && eap->forceit) { 881 kv_concat(cmdline, "!"); 882 } 883 884 // Command register. 885 if (eap->argt & EX_REGSTR && eap->regname) { 886 kv_printf(cmdline, " %c", eap->regname); 887 } 888 889 eap->argc = argc; 890 eap->arglens = eap->argc > 0 ? xcalloc(argc, sizeof(size_t)) : NULL; 891 size_t argstart_idx = cmdline.size; 892 for (size_t i = 0; i < argc; i++) { 893 String s = args.items[i].data.string; 894 eap->arglens[i] = s.size; 895 kv_concat(cmdline, " "); 896 kv_concat_len(cmdline, s.data, s.size); 897 } 898 899 // Done appending to cmdline, ensure it is NUL terminated 900 kv_push(cmdline, NUL); 901 902 // Now that all the arguments are appended, use the command index and argument indices to set the 903 // values of eap->cmd, eap->arg and eap->args. 904 eap->cmd = cmdline.items + cmdname_idx; 905 eap->args = eap->argc > 0 ? xcalloc(argc, sizeof(char *)) : NULL; 906 size_t offset = argstart_idx; 907 for (size_t i = 0; i < argc; i++) { 908 offset++; // Account for space 909 eap->args[i] = cmdline.items + offset; 910 offset += eap->arglens[i]; 911 } 912 // If there isn't an argument, make eap->arg point to end of cmdline. 913 eap->arg = argc > 0 ? eap->args[0] 914 : cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL 915 916 // Finally, make cmdlinep point to the cmdline string. 917 *cmdlinep = cmdline.items; 918 919 // Replace, :make and :grep with 'makeprg' and 'grepprg'. 920 char *p = replace_makeprg(eap, eap->arg, cmdlinep); 921 if (p != eap->arg) { 922 // If replace_makeprg() modified the cmdline string, correct the eap->arg pointer. 923 eap->arg = p; 924 // This cannot be a user command, so eap->args will not be used. 925 XFREE_CLEAR(eap->args); 926 XFREE_CLEAR(eap->arglens); 927 eap->argc = 0; 928 } 929 } 930 931 // uncrustify:off 932 933 /// Creates a global |user-commands| command. 934 /// 935 /// For Lua usage see |lua-guide-commands-create|. 936 /// 937 /// Example: 938 /// 939 /// ```vim 940 /// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) 941 /// :SayHello 942 /// Hello world! 943 /// ``` 944 /// 945 /// @param name Name of the new user command. Must begin with an uppercase letter. 946 /// @param command Replacement command to execute when this user command is executed. When called 947 /// from Lua, the command can also be a Lua function. The function is called with a 948 /// single table argument that contains the following keys: 949 /// - name: (string) Command name 950 /// - args: (string) The args passed to the command, if any [<args>] 951 /// - fargs: (table) The args split by unescaped whitespace (when more than one 952 /// argument is allowed), if any [<f-args>] 953 /// - nargs: (string) Number of arguments |:command-nargs| 954 /// - bang: (boolean) "true" if the command was executed with a ! modifier [<bang>] 955 /// - line1: (number) The starting line of the command range [<line1>] 956 /// - line2: (number) The final line of the command range [<line2>] 957 /// - range: (number) The number of items in the command range: 0, 1, or 2 [<range>] 958 /// - count: (number) Any count supplied [<count>] 959 /// - reg: (string) The optional register, if specified [<reg>] 960 /// - mods: (string) Command modifiers, if any [<mods>] 961 /// - smods: (table) Command modifiers in a structured format. Has the same 962 /// structure as the "mods" key of |nvim_parse_cmd()|. 963 /// @param opts Optional flags 964 /// - `desc` (string) Command description. 965 /// - `force` (boolean, default true) Override any previous definition. 966 /// - `complete` |:command-complete| command or function like |:command-completion-customlist|. 967 /// - `preview` (function) Preview handler for 'inccommand' |:command-preview| 968 /// - Set boolean |command-attributes| such as |:command-bang| or |:command-bar| to 969 /// true (but not |:command-buffer|, use |nvim_buf_create_user_command()| instead). 970 /// @param[out] err Error details, if any. 971 void nvim_create_user_command(uint64_t channel_id, 972 String name, 973 Union(String, LuaRefOf((DictAs(create_user_command__command_args) args))) command, 974 Dict(user_command) *opts, 975 Error *err) 976 FUNC_API_SINCE(9) 977 { 978 create_user_command(channel_id, name, command, opts, 0, err); 979 } 980 981 // uncrustify:on 982 983 /// Delete a user-defined command. 984 /// 985 /// @param name Name of the command to delete. 986 /// @param[out] err Error details, if any. 987 void nvim_del_user_command(String name, Error *err) 988 FUNC_API_SINCE(9) 989 { 990 nvim_buf_del_user_command(-1, name, err); 991 } 992 993 /// Creates a buffer-local command |user-commands|. 994 /// 995 /// @param buffer Buffer id, or 0 for current buffer. 996 /// @param[out] err Error details, if any. 997 /// @see nvim_create_user_command 998 void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command, 999 Dict(user_command) *opts, Error *err) 1000 FUNC_API_SINCE(9) 1001 { 1002 buf_T *target_buf = find_buffer_by_handle(buffer, err); 1003 if (ERROR_SET(err)) { 1004 return; 1005 } 1006 1007 buf_T *save_curbuf = curbuf; 1008 curbuf = target_buf; 1009 create_user_command(channel_id, name, command, opts, UC_BUFFER, err); 1010 curbuf = save_curbuf; 1011 } 1012 1013 /// Delete a buffer-local user-defined command. 1014 /// 1015 /// Only commands created with |:command-buffer| or 1016 /// |nvim_buf_create_user_command()| can be deleted with this function. 1017 /// 1018 /// @param buffer Buffer id, or 0 for current buffer. 1019 /// @param name Name of the command to delete. 1020 /// @param[out] err Error details, if any. 1021 void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) 1022 FUNC_API_SINCE(9) 1023 { 1024 garray_T *gap; 1025 if (buffer == -1) { 1026 gap = &ucmds; 1027 } else { 1028 buf_T *buf = find_buffer_by_handle(buffer, err); 1029 if (ERROR_SET(err)) { 1030 return; 1031 } 1032 gap = &buf->b_ucmds; 1033 } 1034 1035 for (int i = 0; i < gap->ga_len; i++) { 1036 ucmd_T *cmd = USER_CMD_GA(gap, i); 1037 if (!strcmp(name.data, cmd->uc_name)) { 1038 free_ucmd(cmd); 1039 1040 gap->ga_len -= 1; 1041 1042 if (i < gap->ga_len) { 1043 memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); 1044 } 1045 1046 return; 1047 } 1048 } 1049 1050 api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data); 1051 } 1052 1053 void create_user_command(uint64_t channel_id, String name, Union(String, LuaRef) command, 1054 Dict(user_command) *opts, int flags, Error *err) 1055 { 1056 uint32_t argt = 0; 1057 int64_t def = -1; 1058 cmd_addr_T addr_type_arg = ADDR_NONE; 1059 int context = EXPAND_NOTHING; 1060 char *compl_arg = NULL; 1061 const char *rep = NULL; 1062 LuaRef luaref = LUA_NOREF; 1063 LuaRef compl_luaref = LUA_NOREF; 1064 LuaRef preview_luaref = LUA_NOREF; 1065 1066 VALIDATE_S(uc_validate_name(name.data), "command name", name.data, { 1067 goto err; 1068 }); 1069 VALIDATE_S(!mb_islower(name.data[0]), "command name (must start with uppercase)", 1070 name.data, { 1071 goto err; 1072 }); 1073 VALIDATE((!HAS_KEY(opts, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s", 1074 "Cannot use both 'range' and 'count'", { 1075 goto err; 1076 }); 1077 1078 if (opts->nargs.type == kObjectTypeInteger) { 1079 switch (opts->nargs.data.integer) { 1080 case 0: 1081 // Default value, nothing to do 1082 break; 1083 case 1: 1084 argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; 1085 break; 1086 default: 1087 VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, { 1088 goto err; 1089 }); 1090 } 1091 } else if (opts->nargs.type == kObjectTypeString) { 1092 VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, { 1093 goto err; 1094 }); 1095 1096 switch (opts->nargs.data.string.data[0]) { 1097 case '*': 1098 argt |= EX_EXTRA; 1099 break; 1100 case '?': 1101 argt |= EX_EXTRA | EX_NOSPC; 1102 break; 1103 case '+': 1104 argt |= EX_EXTRA | EX_NEEDARG; 1105 break; 1106 default: 1107 VALIDATE_S(false, "nargs", opts->nargs.data.string.data, { 1108 goto err; 1109 }); 1110 } 1111 } else if (HAS_KEY(opts, user_command, nargs)) { 1112 VALIDATE_S(false, "nargs", "", { 1113 goto err; 1114 }); 1115 } 1116 1117 VALIDATE((!HAS_KEY(opts, user_command, complete) || argt), 1118 "%s", "'complete' used without 'nargs'", { 1119 goto err; 1120 }); 1121 1122 if (opts->range.type == kObjectTypeBoolean) { 1123 if (opts->range.data.boolean) { 1124 argt |= EX_RANGE; 1125 addr_type_arg = ADDR_LINES; 1126 } 1127 } else if (opts->range.type == kObjectTypeString) { 1128 VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1), 1129 "range", "", { 1130 goto err; 1131 }); 1132 argt |= EX_RANGE | EX_DFLALL; 1133 addr_type_arg = ADDR_LINES; 1134 } else if (opts->range.type == kObjectTypeInteger) { 1135 argt |= EX_RANGE | EX_ZEROR; 1136 def = opts->range.data.integer; 1137 addr_type_arg = ADDR_LINES; 1138 } else if (HAS_KEY(opts, user_command, range)) { 1139 VALIDATE_S(false, "range", "", { 1140 goto err; 1141 }); 1142 } 1143 1144 if (opts->count.type == kObjectTypeBoolean) { 1145 if (opts->count.data.boolean) { 1146 argt |= EX_COUNT | EX_ZEROR | EX_RANGE; 1147 addr_type_arg = ADDR_OTHER; 1148 def = 0; 1149 } 1150 } else if (opts->count.type == kObjectTypeInteger) { 1151 argt |= EX_COUNT | EX_ZEROR | EX_RANGE; 1152 addr_type_arg = ADDR_OTHER; 1153 def = opts->count.data.integer; 1154 } else if (HAS_KEY(opts, user_command, count)) { 1155 VALIDATE_S(false, "count", "", { 1156 goto err; 1157 }); 1158 } 1159 1160 if (HAS_KEY(opts, user_command, addr)) { 1161 VALIDATE_T("addr", kObjectTypeString, opts->addr.type, { 1162 goto err; 1163 }); 1164 1165 VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data, 1166 (int)opts->addr.data.string.size, &addr_type_arg), "addr", 1167 opts->addr.data.string.data, { 1168 goto err; 1169 }); 1170 1171 argt |= EX_RANGE; 1172 if (addr_type_arg != ADDR_LINES) { 1173 argt |= EX_ZEROR; 1174 } 1175 } 1176 1177 if (opts->bang) { 1178 argt |= EX_BANG; 1179 } 1180 1181 if (opts->bar) { 1182 argt |= EX_TRLBAR; 1183 } 1184 1185 if (opts->register_) { 1186 argt |= EX_REGSTR; 1187 } 1188 1189 if (opts->keepscript) { 1190 argt |= EX_KEEPSCRIPT; 1191 } 1192 1193 bool force = GET_BOOL_OR_TRUE(opts, user_command, force); 1194 if (ERROR_SET(err)) { 1195 goto err; 1196 } 1197 1198 if (opts->complete.type == kObjectTypeLuaRef) { 1199 context = EXPAND_USER_LUA; 1200 compl_luaref = opts->complete.data.luaref; 1201 opts->complete.data.luaref = LUA_NOREF; 1202 } else if (opts->complete.type == kObjectTypeString) { 1203 VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data, 1204 (int)opts->complete.data.string.size, &context, &argt, 1205 &compl_arg), 1206 "complete", opts->complete.data.string.data, { 1207 goto err; 1208 }); 1209 } else if (HAS_KEY(opts, user_command, complete)) { 1210 VALIDATE_EXP(false, "complete", "Function or String", NULL, { 1211 goto err; 1212 }); 1213 } 1214 1215 if (HAS_KEY(opts, user_command, preview)) { 1216 VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, { 1217 goto err; 1218 }); 1219 1220 argt |= EX_PREVIEW; 1221 preview_luaref = opts->preview.data.luaref; 1222 opts->preview.data.luaref = LUA_NOREF; 1223 } 1224 1225 switch (command.type) { 1226 case kObjectTypeLuaRef: 1227 luaref = api_new_luaref(command.data.luaref); 1228 if (opts->desc.type == kObjectTypeString) { 1229 rep = opts->desc.data.string.data; 1230 } else { 1231 rep = ""; 1232 } 1233 break; 1234 case kObjectTypeString: 1235 rep = command.data.string.data; 1236 break; 1237 default: 1238 VALIDATE_EXP(false, "command", "Function or String", NULL, { 1239 goto err; 1240 }); 1241 } 1242 1243 WITH_SCRIPT_CONTEXT(channel_id, { 1244 if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg, 1245 compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) { 1246 api_set_error(err, kErrorTypeException, "Failed to create user command"); 1247 // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg 1248 } 1249 }); 1250 1251 return; 1252 1253 err: 1254 NLUA_CLEAR_REF(luaref); 1255 NLUA_CLEAR_REF(compl_luaref); 1256 xfree(compl_arg); 1257 } 1258 /// Gets a map of global (non-buffer-local) Ex commands. 1259 /// 1260 /// Currently only |user-commands| are supported, not builtin Ex commands. 1261 /// 1262 /// @see |nvim_get_all_options_info()| 1263 /// 1264 /// @param opts Optional parameters. Currently only supports 1265 /// {"builtin":false} 1266 /// @param[out] err Error details, if any. 1267 /// 1268 /// @returns Map of maps describing commands. 1269 DictOf(DictAs(command_info)) nvim_get_commands(Dict(get_commands) *opts, Arena *arena, Error *err) 1270 FUNC_API_SINCE(4) 1271 { 1272 return nvim_buf_get_commands(-1, opts, arena, err); 1273 } 1274 1275 /// Gets a map of buffer-local |user-commands|. 1276 /// 1277 /// @param buffer Buffer id, or 0 for current buffer 1278 /// @param opts Optional parameters. Currently not used. 1279 /// @param[out] err Error details, if any. 1280 /// 1281 /// @returns Map of maps describing commands. 1282 DictAs(command_info) nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Arena *arena, 1283 Error *err) 1284 FUNC_API_SINCE(4) 1285 { 1286 bool global = (buffer == -1); 1287 if (ERROR_SET(err)) { 1288 return (Dict)ARRAY_DICT_INIT; 1289 } 1290 1291 if (global) { 1292 if (opts->builtin) { 1293 api_set_error(err, kErrorTypeValidation, "builtin=true not implemented"); 1294 return (Dict)ARRAY_DICT_INIT; 1295 } 1296 return commands_array(NULL, arena); 1297 } 1298 1299 buf_T *buf = find_buffer_by_handle(buffer, err); 1300 if (opts->builtin || !buf) { 1301 return (Dict)ARRAY_DICT_INIT; 1302 } 1303 return commands_array(buf, arena); 1304 }